diff --git a/README.md b/README.md
index 7db80e4..3fda671 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,116 @@
-# README
+# ZSTerminator -
-This README would normally document whatever steps are necessary to get the
-application up and running.
-Things you may want to cover:
+## Setup
-* Ruby version
+* Ruby version: 3.2.4
+* Rails version: 7.1.5+
+* Database: SQLite3
-* System dependencies
+## Getting Started
-* Configuration
+1. Install dependencies:
+ ```bash
+ bundle install
+ ```
-* Database creation
+2. Setup database:
+ ```bash
+ rails db:create
+ rails db:migrate
+ rails db:seed
+ ```
-* Database initialization
+3. Start the server:
+ ```bash
+ rails server
+ ```
-* How to run the test suite
+4. Visit http://localhost:3000 and login with:
+ - Username: `admin`
+ - Password: `password123`
-* Services (job queues, cache servers, search engines, etc.)
+## User Management (Rake Tasks)
-* Deployment instructions
+This project includes Rake tasks for managing users via command line:
-* ...
+### Available Commands
+
+```bash
+# List all users
+rails users:list
+
+# Show user details
+rails users:show[username]
+
+# Create a new user
+rails users:create[username,email,password,company_id]
+
+# Change user password
+rails users:change_password[username,new_password]
+
+# Delete a user
+rails users:delete[username]
+
+# Clean up test users (users with 'test' in username/email)
+rails users:cleanup_test_users
+```
+
+### Usage Examples
+
+```bash
+# Create a new user (company_id is optional, uses first company if blank)
+rails "users:create[john,john@example.com,securepass123]"
+
+# Change password
+rails "users:change_password[john,newpassword456]"
+
+# Show user information
+rails users:show[john]
+
+# Delete user (with confirmation)
+rails users:delete[john]
+
+# List all users in a formatted table
+rails users:list
+```
+
+### Automated Testing
+
+**Run complete CRUD test suite:**
+```bash
+# Single command to test all user operations automatically
+rails users:test_crud
+```
+
+This automated test will:
+1. **Create** test users (dodavanje)
+2. **List** created users
+3. **Change** password (mijenjanje sifre)
+4. **Show** user details
+5. **Delete** user (brisanje)
+6. **Clean up** all test data
+
+### Manual Testing Workflow
+
+1. **Create test users:**
+ ```bash
+ rails "users:create[testuser1,test1@example.com,testpass123]"
+ rails "users:create[testuser2,test2@example.com,testpass456]"
+ ```
+
+2. **Test your application** with the created users
+
+3. **Clean up test data:**
+ ```bash
+ # This will find and delete all users with 'test' in username or email
+ rails users:cleanup_test_users
+ ```
+
+### Notes
+
+- All user passwords are encrypted using bcrypt
+- Users must belong to a company
+- Username and email must be unique
+- Minimum password length is 6 characters
+- The cleanup task only removes users with 'test' in their username or email for safety
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7d02033..7a6b9e6 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,5 +1,6 @@
class ApplicationController < ActionController::Base
before_action :set_locale
+ before_action :require_login
private
@@ -8,27 +9,37 @@ class ApplicationController < ActionController::Base
session[:locale] = I18n.locale
end
- # Optional: Make locale persist across requests via URL helpers
def default_url_options
{ locale: I18n.locale }
end
- def set_company
- # This should be handled by your authentication system
- # But for now, we'll use a placeholder
- company_id = session[:company_id]
-
- unless company_id && Company.exists?(company_id)
- # If no company in session or it doesn't exist, use the first company
- company_id = Company.first&.id
- session[:company_id] = company_id
- end
-
- @company = Company.find(company_id) if company_id
+ def current_user
+ @current_user ||= User.find(session[:user_id]) if session[:user_id]
end
-
+
+ def logged_in?
+ !!current_user
+ end
+
+ def require_login
+ return if logged_in?
+
+ flash[:alert] = t('sessions.login_required')
+ redirect_to login_path
+ end
+
+ def set_company
+ return unless logged_in?
+
+ @company = current_user.company
+
+ return if @company
+
+ redirect_to companies_path, alert: 'No company found. Please create a company first.'
+ end
+
def current_company
@company
end
- helper_method :current_company
+ helper_method :current_user, :logged_in?, :current_company
end
diff --git a/app/controllers/reservations_controller.rb b/app/controllers/reservations_controller.rb
index ba47cdf..9751a1a 100644
--- a/app/controllers/reservations_controller.rb
+++ b/app/controllers/reservations_controller.rb
@@ -219,5 +219,10 @@ class ReservationsController < ApplicationController
end
@company = Company.includes(:teams).find(company_id) if company_id
+
+ unless @company
+ redirect_to companies_path, alert: 'No company found. Please create a company first.'
+ return
+ end
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
new file mode 100644
index 0000000..d5c03a0
--- /dev/null
+++ b/app/controllers/sessions_controller.rb
@@ -0,0 +1,25 @@
+class SessionsController < ApplicationController
+ skip_before_action :require_login, only: %i[new create]
+
+ def new
+ end
+
+ def create
+ user = User.find_by_login(params[:login])
+
+ if user&.authenticate(params[:password])
+ session[:user_id] = user.id
+ session[:company_id] = user.company_id
+ redirect_to root_path, notice: t('sessions.login_successful')
+ else
+ flash.now[:alert] = t('sessions.invalid_credentials')
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ session[:user_id] = nil
+ session[:company_id] = nil
+ redirect_to login_path, notice: t('sessions.logout_successful')
+ end
+end
diff --git a/app/models/company.rb b/app/models/company.rb
index 2cc0110..dfbb100 100644
--- a/app/models/company.rb
+++ b/app/models/company.rb
@@ -2,4 +2,5 @@ class Company < ApplicationRecord
has_many :customers, dependent: :destroy
has_many :reservations, dependent: :destroy
has_many :teams, dependent: :destroy
+ has_many :users, dependent: :destroy
end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 0000000..6df3b84
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,23 @@
+class User < ApplicationRecord
+ has_secure_password
+
+ belongs_to :company
+
+ validates :username, presence: true, uniqueness: { case_sensitive: false }
+ validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP }
+ validates :password, length: { minimum: 6 }, if: -> { new_record? || password.present? }
+ validates :company_id, presence: true
+
+ before_save :downcase_username_and_email
+
+ def self.find_by_login(login)
+ find_by(username: login.downcase) || find_by(email: login.downcase)
+ end
+
+ private
+
+ def downcase_username_and_email
+ self.username = username.downcase.strip if username.present?
+ self.email = email.downcase.strip if email.present?
+ end
+end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 3ebfd04..72a0e24 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -18,6 +18,15 @@
+ <% if logged_in? %>
+
+ <%= current_user.username %>
+ |
+ <%= link_to t('sessions.logout_button'), logout_path, method: :delete,
+ class: "text-blue-600 hover:text-blue-800" %>
+
+ <% end %>
+
<%= yield %>
diff --git a/app/views/reservations/_form.html.erb b/app/views/reservations/_form.html.erb
index ee62d77..1e5ce6c 100644
--- a/app/views/reservations/_form.html.erb
+++ b/app/views/reservations/_form.html.erb
@@ -62,7 +62,7 @@
<%= form.label :team_id, t('reservations.form.team') %>
<%= form.collection_select :team_id,
- @company.teams,
+ @company&.teams || [],
:id,
:name,
{ prompt: t('reservations.form.select_team') },
diff --git a/app/views/reservations/index.html.erb b/app/views/reservations/index.html.erb
index b89d592..142c2cc 100644
--- a/app/views/reservations/index.html.erb
+++ b/app/views/reservations/index.html.erb
@@ -28,10 +28,12 @@
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
new file mode 100644
index 0000000..dfb3f35
--- /dev/null
+++ b/app/views/sessions/new.html.erb
@@ -0,0 +1,24 @@
+
+
<%= t('sessions.login') %>
+
+ <%= form_with url: login_path, method: :post, local: true, class: "space-y-4" do |form| %>
+
+ <%= form.label :login, t('sessions.username_or_email'), class: "block text-sm font-medium text-gray-700 mb-1" %>
+ <%= form.text_field :login, required: true,
+ class: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
+ placeholder: t('sessions.username_or_email_placeholder') %>
+
+
+
+ <%= form.label :password, t('sessions.password'), class: "block text-sm font-medium text-gray-700 mb-1" %>
+ <%= form.password_field :password, required: true,
+ class: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
+ placeholder: t('sessions.password_placeholder') %>
+
+
+
+ <%= form.submit t('sessions.login_button'),
+ class: "w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-200" %>
+
+ <% end %>
+
\ No newline at end of file
diff --git a/config/locales/bs.yml b/config/locales/bs.yml
index 5ea51a5..cf7f187 100644
--- a/config/locales/bs.yml
+++ b/config/locales/bs.yml
@@ -131,4 +131,17 @@ bs:
city: "Grad:"
entity: "Entitet:"
country: "Država:"
+
+ sessions:
+ login: "Prijava"
+ username_or_email: "Korisničko ime ili email"
+ username_or_email_placeholder: "Unesite korisničko ime ili email"
+ password: "Lozinka"
+ password_placeholder: "Unesite lozinku"
+ login_button: "Prijaviť se"
+ logout_button: "Odjavi se"
+ login_successful: "Uspješno ste se prijavili!"
+ logout_successful: "Uspješno ste se odjavili!"
+ invalid_credentials: "Neispravno korisničko ime/email ili lozinka"
+ login_required: "Molimo prijavite se da biste pristupili ovoj stranici"
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a521798..998b46c 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -167,3 +167,16 @@ en:
city: "City:"
entity: "Entity:"
country: "Country:"
+
+ sessions:
+ login: "Login"
+ username_or_email: "Username or Email"
+ username_or_email_placeholder: "Enter username or email"
+ password: "Password"
+ password_placeholder: "Enter password"
+ login_button: "Log In"
+ logout_button: "Log Out"
+ login_successful: "Successfully logged in!"
+ logout_successful: "Successfully logged out!"
+ invalid_credentials: "Invalid username/email or password"
+ login_required: "Please log in to access this page"
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 69f7424..20d6f57 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,8 @@
Rails.application.routes.draw do
+ get '/login', to: 'sessions#new'
+ post '/login', to: 'sessions#create'
+ delete '/logout', to: 'sessions#destroy'
+
root "reservations#index"
resources :customers, param: :composite_key do
get :search, on: :collection
@@ -12,7 +16,7 @@ Rails.application.routes.draw do
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
- # config/routes.rb
+ # config/routes.rb
get "/service-worker.js" => "service_worker#service_worker"
get "/manifest.json" => "service_worker#manifest"
# Defines the root path route ("/")
diff --git a/db/migrate/20250731113948_create_users.rb b/db/migrate/20250731113948_create_users.rb
new file mode 100644
index 0000000..f765e1b
--- /dev/null
+++ b/db/migrate/20250731113948_create_users.rb
@@ -0,0 +1,14 @@
+class CreateUsers < ActiveRecord::Migration[7.1]
+ def change
+ create_table :users do |t|
+ t.string :username
+ t.string :email
+ t.string :password_digest
+ t.references :company, null: false, foreign_key: true
+
+ t.timestamps
+ end
+ add_index :users, :username, unique: true
+ add_index :users, :email, unique: true
+ end
+end
diff --git a/db/migrate/20250731114403_add_email_to_users.rb b/db/migrate/20250731114403_add_email_to_users.rb
new file mode 100644
index 0000000..3b26e34
--- /dev/null
+++ b/db/migrate/20250731114403_add_email_to_users.rb
@@ -0,0 +1,6 @@
+class AddEmailToUsers < ActiveRecord::Migration[7.1]
+ def change
+ add_column :users, :email, :string
+ add_index :users, :email, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 58fb5bc..296a5eb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2025_07_04_092438) do
+ActiveRecord::Schema[7.1].define(version: 2025_07_31_114403) do
create_table "companies", force: :cascade do |t|
t.string "name"
t.string "id_number"
@@ -66,8 +66,21 @@ ActiveRecord::Schema[7.1].define(version: 2025_07_04_092438) do
t.index ["company_id"], name: "index_teams_on_company_id"
end
+ create_table "users", force: :cascade do |t|
+ t.string "username"
+ t.string "password_digest"
+ t.integer "company_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "email"
+ t.index ["company_id"], name: "index_users_on_company_id"
+ t.index ["email"], name: "index_users_on_email", unique: true
+ t.index ["username"], name: "index_users_on_username", unique: true
+ end
+
add_foreign_key "reservations", "companies"
add_foreign_key "reservations", "customers", column: ["customer_first_name", "customer_surname", "customer_original_phone"], primary_key: ["first_name", "surname", "original_phone"]
add_foreign_key "reservations", "teams"
add_foreign_key "teams", "companies"
+ add_foreign_key "users", "companies"
end
diff --git a/db/seeds.rb b/db/seeds.rb
index 4fbd6ed..f9a764f 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -7,3 +7,34 @@
# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
# MovieGenre.find_or_create_by!(name: genre_name)
# end
+# Create a default company if none exists
+default_company = Company.find_or_create_by!(name: 'Default Company') do |company|
+ company.id_number = 'COMP001'
+ company.vat_number = 'VAT001'
+ company.address_line_one = '123 Main Street'
+ company.city = 'Default City'
+ company.country = 'Default Country'
+end
+
+# Create default teams for the company
+teams_data = [
+ { name: 'Team Alpha' },
+ { name: 'Team Beta' },
+ { name: 'Team Gamma' }
+]
+
+teams_data.each do |team_attrs|
+ Team.find_or_create_by!(name: team_attrs[:name], company: default_company)
+end
+
+puts "Seeded default company: #{default_company.name}"
+puts "Seeded #{default_company.teams.count} teams"
+
+# Create default user for the company
+default_user = User.find_or_create_by!(username: 'admin', company: default_company) do |user|
+ user.email = 'admin@company.ba'
+ user.password = 'password123'
+ user.password_confirmation = 'password123'
+end
+
+puts "Seeded default user: #{default_user.username} (#{default_user.email})"
diff --git a/lib/tasks/users.rake b/lib/tasks/users.rake
new file mode 100644
index 0000000..c319359
--- /dev/null
+++ b/lib/tasks/users.rake
@@ -0,0 +1,271 @@
+namespace :users do
+ desc "Create a new user"
+ task :create, %i[username email password company_id] => :environment do |_t, args|
+ username = args[:username] || ask("Username: ")
+ email = args[:email] || ask("Email: ")
+ password = args[:password] || ask("Password: ") { |q| q.echo = "*" }
+ company_id = find_or_validate_company(args[:company_id])
+
+ user = User.new(
+ username: username,
+ email: email,
+ password: password,
+ password_confirmation: password,
+ company_id: company_id
+ )
+
+ if user.save
+ puts "User created successfully!"
+ puts " Username: #{user.username}"
+ puts " Email: #{user.email}"
+ puts " Company: #{user.company.name}"
+ else
+ puts "Failed to create user:"
+ user.errors.full_messages.each { |msg| puts " - #{msg}" }
+ exit 1
+ end
+ end
+
+ desc "Change user password"
+ task :change_password, %i[username new_password] => :environment do |_t, args|
+ username = args[:username] || ask("Username: ")
+ new_password = args[:new_password] || ask("New password: ") { |q| q.echo = "*" }
+
+ user = User.find_by(username: username)
+ unless user
+ puts "User '#{username}' not found."
+ exit 1
+ end
+
+ user.password = new_password
+ user.password_confirmation = new_password
+
+ if user.save
+ puts "Password changed successfully for user '#{username}'"
+ else
+ puts "Failed to change password:"
+ user.errors.full_messages.each { |msg| puts " - #{msg}" }
+ exit 1
+ end
+ end
+
+ desc "Delete a user"
+ task :delete, %i[username] => :environment do |_t, args|
+ username = args[:username] || ask("Username to delete: ")
+
+ user = User.find_by(username: username)
+ unless user
+ puts "User '#{username}' not found."
+ exit 1
+ end
+
+ puts "User details:"
+ puts " Username: #{user.username}"
+ puts " Email: #{user.email}"
+ puts " Company: #{user.company.name}"
+
+ confirm = ask("Are you sure you want to delete this user? (yes/no): ")
+ unless confirm.downcase == 'yes'
+ puts "User deletion cancelled."
+ exit 0
+ end
+
+ if user.destroy
+ puts "User '#{username}' deleted successfully."
+ else
+ puts "Failed to delete user:"
+ user.errors.full_messages.each { |msg| puts " - #{msg}" }
+ exit 1
+ end
+ end
+
+ desc "List all users"
+ task list: :environment do
+ users = User.includes(:company).order(:username)
+
+ if users.empty?
+ puts "No users found."
+ exit 0
+ end
+
+ puts "Users List:"
+ puts "=" * 80
+ printf "%-20s %-30s %-20s %s\n", "USERNAME", "EMAIL", "COMPANY", "CREATED"
+ puts "-" * 80
+
+ users.each do |user|
+ printf "%-20s %-30s %-20s %s\n",
+ user.username,
+ user.email,
+ user.company.name,
+ user.created_at.strftime("%Y-%m-%d")
+ end
+
+ puts "-" * 80
+ puts "Total: #{users.count} users"
+ end
+
+ desc "Run automated CRUD tests for user management"
+ task test_crud: :environment do
+ puts "=" * 60
+ puts "AUTOMATED USER CRUD TEST"
+ puts "=" * 60
+
+ # Test 1: Create users
+ puts "\n1. TESTING USER CREATION (dodavanje):"
+ puts "-" * 40
+
+ test_users = [
+ { username: 'testuser1', email: 'test1@example.com', password: 'testpass123' },
+ { username: 'testuser2', email: 'test2@example.com', password: 'testpass456' }
+ ]
+
+ test_users.each do |user_data|
+ company_id = find_or_validate_company(nil)
+ user = User.new(
+ username: user_data[:username],
+ email: user_data[:email],
+ password: user_data[:password],
+ password_confirmation: user_data[:password],
+ company_id: company_id
+ )
+
+ if user.save
+ puts "✓ Created user: #{user.username} (#{user.email})"
+ else
+ puts "✗ Failed to create #{user_data[:username]}: #{user.errors.full_messages.join(', ')}"
+ end
+ end
+
+ # Test 2: List users
+ puts "\n2. TESTING USER LISTING:"
+ puts "-" * 40
+ users = User.where("username LIKE ?", "%test%").order(:username)
+ users.each do |user|
+ puts "✓ Found user: #{user.username} (#{user.email}) - Company: #{user.company.name}"
+ end
+
+ # Test 3: Change password (mijenjanje sifre)
+ puts "\n3. TESTING PASSWORD CHANGE (mijenjanje sifre):"
+ puts "-" * 40
+ test_user = User.find_by(username: 'testuser1')
+ if test_user
+ test_user.password = 'newpassword789'
+ test_user.password_confirmation = 'newpassword789'
+ if test_user.save
+ puts "✓ Password changed for: #{test_user.username}"
+ else
+ puts "✗ Failed to change password: #{test_user.errors.full_messages.join(', ')}"
+ end
+ end
+
+ # Test 4: Show user details
+ puts "\n4. TESTING USER DETAILS:"
+ puts "-" * 40
+ if test_user
+ puts "✓ User: #{test_user.username}"
+ puts " Email: #{test_user.email}"
+ puts " Company: #{test_user.company.name}"
+ puts " Created: #{test_user.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
+ end
+
+ # Test 5: Delete user (brisanje)
+ puts "\n5. TESTING USER DELETION (brisanje):"
+ puts "-" * 40
+ delete_user = User.find_by(username: 'testuser2')
+ if delete_user
+ username = delete_user.username
+ if delete_user.destroy
+ puts "✓ Deleted user: #{username}"
+ else
+ puts "✗ Failed to delete user: #{delete_user.errors.full_messages.join(', ')}"
+ end
+ end
+
+ # Test 6: Final cleanup
+ puts "\n6. CLEANUP:"
+ puts "-" * 40
+ remaining_test_users = User.where("username LIKE ? OR email LIKE ?", "%test%", "%test%")
+ deleted_count = remaining_test_users.count
+ remaining_test_users.destroy_all
+ puts "✓ Cleaned up #{deleted_count} remaining test users"
+
+ puts "\n" + ("=" * 60)
+ puts "CRUD TEST COMPLETED SUCCESSFULLY!"
+ puts "All operations (dodavanje, mijenjanje sifre, brisanje) tested."
+ puts "=" * 60
+ end
+
+ desc "Clean up test users (users with 'test' in username or email)"
+ task cleanup_test_users: :environment do
+ test_users = User.where("username LIKE ? OR email LIKE ?", "%test%", "%test%")
+
+ if test_users.empty?
+ puts "No test users found to clean up."
+ return
+ end
+
+ puts "Found #{test_users.count} test users to delete:"
+ test_users.each do |user|
+ puts " - #{user.username} (#{user.email})"
+ end
+
+ confirm = ask("Delete all test users? (yes/no): ")
+ if confirm.downcase == 'yes'
+ deleted_count = test_users.count
+ test_users.destroy_all
+ puts "Deleted #{deleted_count} test users successfully."
+ else
+ puts "Cleanup cancelled."
+ end
+ end
+
+ desc "Show user details"
+ task :show, %i[username] => :environment do |_t, args|
+ username = args[:username] || ask("Username: ")
+
+ user = User.find_by(username: username)
+ unless user
+ puts "User '#{username}' not found."
+ exit 1
+ end
+
+ puts "User Details:"
+ puts " Username: #{user.username}"
+ puts " Email: #{user.email}"
+ puts " Company: #{user.company.name} (ID: #{user.company.id})"
+ puts " Created: #{user.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
+ puts " Updated: #{user.updated_at.strftime('%Y-%m-%d %H:%M:%S')}"
+ end
+
+ private
+
+ def find_or_validate_company(company_id)
+ if company_id.blank?
+ company = Company.first
+ unless company
+ puts "No companies found. Please create a company first."
+ exit 1
+ end
+ puts "Using company: #{company.name} (ID: #{company.id})"
+ company.id
+ else
+ company = Company.find_by(id: company_id)
+ unless company
+ puts "Company with ID #{company_id} not found."
+ exit 1
+ end
+ company_id
+ end
+ end
+
+ def ask(prompt, &_block)
+ require 'io/console'
+ print prompt
+ if block_given?
+ yield.call
+ else
+ $stdin.gets.chomp
+ end
+ end
+end