Same method between model & controller - ruby-on-rails

To practice, I'm trying to create a small web game with Ruby On Rails, however, I need some information in my model and also in my controller.
I explain myself. For example, in my model, I have the following method:
def cost(building)
{
wood: BUILDING[building]['wood'] * (BUILDING[building]['factor'] ** self.level(building)),
stone: BUILDING[building]['stone'] * (BUILDING[building]['factor'] ** self.level(building)),
iron: BUILDING[building]['iron'] * (BUILDING[building]['factor'] ** self.level(building))
}
end
Then in my view I use the following to display the cost of upgrading the building :
<%= #buildings.cost(building[0])[:wood] %>
But I also have an "upgrade" button in my view that allows to upgrade this building, except that on the controller side I also need to get the costs of the building but I'm not sure of the right approach.
[EDIT] :
To give more informations :
#buildings = #buildings = Building.find_by(kingdom_id: current_kingdom.id) #Inside BuildingsController
building[0] It is a string in a yaml that corresponds to the name of a building
level it recovers the current level of the building for the player
EDIT 2 :
models/building.rb :
class Building < ApplicationRecord
def cost(building)
{
wood: BUILDING[building]['wood'] * (BUILDING[building]['factor'] ** self.level(building)),
stone: BUILDING[building]['stone'] * (BUILDING[building]['factor'] ** self.level(building)),
iron: BUILDING[building]['iron'] * (BUILDING[building]['factor'] ** self.level(building))
}
end
def consumption(building)
BUILDING[building]['coal']
end
def time(building)
resources_needed = cost(building)[:wood] + cost(building)[:stone] + cost(building)[:iron]
time = (resources_needed / (2500 * 4 * SERVER['rate']).to_f * 3600).round
if time >= 3600
"#{time / 60 / 60} h #{time % 3600 / 60} min"
elsif time >= 60
"#{time / 60} min #{time % 60 } sec"
else
"#{time} sec"
end
end
def level(building)
self[building.to_sym]
end
def upgrade?(building, kingdom_resources)
cost(building)[:wood] <= kingdom_resources[:wood] &&
cost(building)[:stone] <= kingdom_resources[:stone] &&
cost(building)[:iron] <= kingdom_resources[:iron]
end
end
buildings_controller.rb :
class BuildingsController < ApplicationController
def index
#buildings = Building.find_by(kingdom_id: current_kingdom.id)
#kingdom_resources = kingdom_resources
#kingdom_queue = BuildQueue.where(kingdom_id: current_kingdom.id)
end
def add_to_queue
building = params[:building]
# If we can upgrade
# Add building to the queue
end
private
def cost(building)
{
wood: BUILDING[building]['wood'] * (BUILDING[building]['factor'] ** self.level(building)),
stone: BUILDING[building]['stone'] * (BUILDING[building]['factor'] ** self.level(building)),
iron: BUILDING[building]['iron'] * (BUILDING[building]['factor'] ** self.level(building))
}
end
def building_level(building)
Building.find_by(kingdom_id: current_kingdom.id)[building.to_sym]
end
def time(building)
resources_needed = cost(building)[:wood] + cost(building)[:stone] + cost(building)[:iron]
(resources_needed / (2500 * 4 * SERVER['rate']).to_f * 3600).round
end
def upgrade?(building)
cost(building)[:wood] <= kingdom_resources[:wood] &&
cost(building)[:stone] <= kingdom_resources[:stone] &&
cost(building)[:iron] <= kingdom_resources[:iron]
end
end
method inside app/controllers/application_controller.rb,
To get the current_kingdom :
def current_kingdom
return nil unless current_user
return #_kingdom if #_kingdom
#_kingdom = Kingdom.find_by(user_id: current_user.id)
end
And current_user :
def current_user
return nil if !session[:auth] || !session[:auth]['id']
return #_user if #_user
#_user = User.find_by_id(session[:auth]['id'])
end
And current kingdom_resources :
def kingdom_resources
return #kingdom if #kingdom
#kingdom = {
wood: current_kingdom.wood,
stone: current_kingdom.stone,
iron: current_kingdom.iron,
coal: current_kingdom.coal,
food: current_kingdom.food
}
end
Thank's in advance,
Regards

fA user has a kingdom. A kingdom has buildings. We can set that up simple enough. A kingdom apparently also has build queues and resources.
class User < ApplicationRecord
has_one :kingdom
has_many :buildings, through: :kingdom
end
create_table :kingdoms do |t|
t.belongs_to :user, foreign_key: true
t.name :string, null: false
t.wood :integer, null: false
t.stone :integer, null: false
t.iron :integer, null: false
t.coal :integer, null: false
t.food :integer, null: false
end
class Kingdom < ApplicationRecord
belongs_to :user
has_many :buildings
has_many :build_queues
def resource(material)
self[material]
end
end
class BuildQueue < ApplicationRecord
belongs_to :kingdom
belongs_to :building
end
Now you can directly ask a player for its buildings: current_user.buildings and a kingdom for its build queues: kingdom.build_queues.
If you want to find a building by name: current_user.buildings.find_by(name: building_name).
Your Building model is strange. It seems like a single Building object represents all buildings. And information about the cost of a building is stored in a global.
Instead, the information about each building should instead be stored in a table row.
create_table :buildings do |t|
t.belongs_to :kingdom, foreign_key: true
t.string :name, null: false
t.cost_wood :integer, null: false
t.cost_stone :integer, null: false
t.cost_iron :integer, null: false
t.consumption_coal :integer, null: false
t.cost_factor :float, default: 1, null: false
t.level :integer, default: 1, null: false
end
class Building < ApplicationRecord
belongs_to :kingdom
has_one :player, through :kingdom
MATERIALS = [:wood, :stone, :iron].freeze
private def base_cost(material)
self[:"cost_#{material}"]
end
def cost(material)
base_cost(material) * cost_factor ** level
end
def build_cost
MATERIALS.sum { |m| cost(m) }
end
def build_time
# ActiveSupport::Duration will do the formatting for you.
ActiveSupport::Duration.build(
(build_cost / (2500 * 4 * SERVER['rate']).to_f * 3600).round
)
end
end
Now the BuildingsController can use what's been set up in the models.
class BuildingsController < ApplicationController
def index
# A user already has associations to its kingdom and buildings
# This is so simple there's no need for a current_kingdom in
# the ApplicationController
#kingdom = current_user.kingdom
#buildings = current_user.buildings
end
def show
if params[:id].present?
#building = current_user.buildings.find_by(id: params[:id])
else
#building = current_user.buildings.find_by(name: params[:name])
end
end
end
If you want to take action that involves multiple models, make a little object to do it. Like queuing a building upgrade.
# BuildingUpgradeQueuer.new(
# building: building,
# kingdom: kingdom
# ).queue_upgrade
class BuildingUpgradeQueuer
include ActiveModel::Model
MATERIALS = [:wood, :stone, :iron].freeze
attr_accessor :buiding, :kingdom
def queue_upgrade
return unless upgrade?
kingdom.build_queues.create!(building: building)
end
def upgrade?
MATERIALS.all? { |material|
building.cost(material) <= kingdom.resources(material)
}
end
end
You could do this in the Building or Kingdom object, but if you do that your models get complex and fat.

Related

rails Calculation (price * quantity)

Hi Im creating an ec site in my rails.
My migration:
(Item) has :name and :price.
(Basket_Item) has :item_id(fk), :basket_id(fk) and :quantity.
The system User will add some items to their basket.
So Basket_items is JOIN Table between (Item) and (Basket)
see like below.
What I want to do:
Get a price of Item and get a quantity from Basket_Items which is selected by user. Then I want to create #total_price = item_price * item_quantity.
Can anyone help me to create the #total_price please.
This is my a try code but it doesn't work on rails console.
Basket_items
class CreateBasketItems < ActiveRecord::Migration[5.2]
def change
create_table :basket_items do |t|
t.references :basket, index: true, null: false, foreign_key: true
t.references :item, index: true, null: false, foreign_key: true
t.integer :quantity, null: false, default: 1
t.timestamps
end
end
end
///
Items
class CreateItems < ActiveRecord::Migration[5.2]
def change
create_table :items do |t|
t.references :admin, index: true, null: false, foreign_key: true
t.string :name, null: false, index: true
t.integer :price, null: false
t.text :message
t.string :category, index: true
t.string :img
t.string :Video_url
t.text :discription
t.timestamps
end
end
end
///
This is my try a code but it doesn't work on rails console.
basket = current_user.prepare_basket
item_ids = basket.basket_items.select(:item_id)
items = basket.items.where(id: item_ids)
items_price = items.select(:price)
items_quantity = basket.basket_items.where(item_id: item_ids).pluck(:quantity)
def self.total(items_price, items_quantity)
sum(items_price * items_quantity)
end
#total_price = basket.total(items_price, item_quantity)
There are a few issues with your code:
You are trying to call a class method on an instance of the class. That's not gonna work, second you are passing in arrays into the calculation.
basket = current_user.prepare_basket
item_ids = basket.basket_items.select(:item_id)
items = basket.items.where(id: item_ids)
items_price = items.select(:price) # => Array of prices from the items in the basket
items_quantity = basket.basket_items.where(item_id: item_ids).pluck(:quantity) # => Array of quantities from the items in the basket
def self.total(items_price, items_quantity)
sum(items_price * items_quantity) # => So this line will do: sum(['12,95', '9.99'] * [1, 3])
end
#total_price = basket.total(items_price, item_quantity)
As you can see, that ain't gonna work. First you need to change the method and remove the self.
def total(items_price, items_quantity)
# ...
end
Now you can call the total method on a basket object: basket.total(items_price, items_quantity)
And inside the total method you need to loop through each items to do the calculation and add all the results.
def total(items_price, items_quantity)
total_price = 0
items_price.each_with_index do |price, index|
total_price += price * items_quantity[index]
end
total_price
end
But this solution could also fail, because you don't know sure that the order in the items_price is matching with the order of items_quantity. So a better approach would be to do the calculation for each basket_item seperate.
# Basket model
def total
total_price = 0
basket_items.each do |basket_item|
total_price += basket_item.total_price
end
total_price
end
# BasketItem model
def total_price
quantity * item.price
end
Now you can call it like this: basket.total

How to calculate total price in def create, API

Can you help me? I dont understand how to do it.
How to calculate TOTAL PRICE when i created the room?
I am dont understand how to do it right.
Here is my code:
room_controller.rb
def create
parameters = room_params.to_hash
parameters[:created_by] = #current_user.id
parameters[:account_id] = #current_user.account_id
#room = #section.rooms.create!(parameters)
update_room(#room, params)
end
...
def update
params = room_params.to_hash
update_room(#room, params)
json_response(#room)
end
def update_room(room, json)
return unless room
unless json.key?('total_price')
if json.key?('price') || json.key?('square')
square = json.key?('square') ? json['square'].to_f : room.square
price = json.key?('price') ? json['price'].to_f : room.price
json['total_price'] = price * square
end
end
unless json.key?('price')
if json.key?('total_price') || json.key?('square')
square = json.key?('square') ? json['square'].to_f : room.square
total_price = json.key?('total_price') ? json['total_price'].to_f : room.total_price
json['price'] = total_price / square
end
end
room.update(json)
end
def room_params
params.permit(
:level, :square, :total_price, :price, :number, :room_type,
:plan_image, :plan_coordinate, :view_image, :interior_image,
:rooms_count, :status, :marked
)
end
schema.rb
create_table "rooms", force: :cascade do |t|
...
t.float "square", default: 0.0
t.float "price", default: 0.0
t.float "total_price", default: 0.0
If total_price is square multiplied by price you can do that in the model.
class Room
before_save :calculate_total
private
def calculate_total
self.total_price = square * price
end
end

Rails Name Error - Undefined local variable or method `booking'

I'm building an Events app in Rails and I've hit the error above which relates to this method in my Model -
def validate_availability
errors.add(:base, 'event is fully booked') if booking.count >= event.number_of_spaces
end
The purpose of the method is to avoid over-booking of an event whereby a specific number of spaces are available. In my Controller I have the following code -
Controller#Create
def create
#event = Event.find(params[:event_id])
#booking = #event.bookings.new(booking_params)
#booking.user = current_user
if
#booking.set_booking
flash[:success] = "Your place on our event has been booked"
redirect_to event_booking_path(#event, #booking)
else
flash[:error] = "Booking unsuccessful"
render "new"
end
if #event.is_free?
#booking.save(booking_params)
end
if booking.count >= #event.number_of_spaces
flash[:error] = "Sorry, this event is now fully booked"
render "new"
end
end
I need to define booking.count in my controller but not sure what would work - tried a few things but nothings working. I have the following in my schema -
create_table "bookings", force: :cascade do |t|
t.integer "event_id"
t.integer "user_id"
t.string "stripe_token"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity", default: 1
t.integer "total_amount"
t.string "stripe_charge_id"
t.string "booking_number"
end
The booking.count would rely upon the quantity of spaces/bookings a user wishes to make versus the number of spaces remaining but how do I express this? Do I need a total_bookings column in my table or a separate method?
UPDATE -
Booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :user
before_create :set_booking_number
validates :quantity, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :total_amount, presence: true, numericality: { greater_than_or_equal_to: 0 }
validate(:validate_booking)
validate(:validate_availability)
def set_booking_number
self.booking_number = "MAMA" + '- ' + SecureRandom.hex(4).upcase
end
def set_booking
if self.event.is_free?
self.total_amount = 0
save!
else
self.total_amount = event.price_pennies * self.quantity
begin
charge = Stripe::Charge.create(
amount: total_amount,
currency: "gbp",
source: stripe_token,
description: "Booking created for amount #{total_amount}")
self.stripe_charge_id = charge.id
save!
rescue Stripe::CardError => e
# if this fails stripe_charge_id will be null, but in case of update we just set it to nil again
self.stripe_charge_id = nil
# we check in validatition if nil
end
end
end
def validate_booking
# stripe_charge_id must be set for not free events
unless self.event.is_free?
return !self.stripe_charge_id.nil?
end
end
private
def validate_availability
errors.add(:base, 'event is fully booked') if event.bookings.count >= event.number_of_spaces
end
end
For the counts of booking table, you should have a booking_count field in events table. Use the counter cache for this. For more details check http://guides.rubyonrails.org/association_basics.html. This is very helpful when records are large.
Your migration for adding column should be similar as below:
def change
add_column :events, :bookings_count, :integer, default: 0
Event.reset_column_information
Event.all.each do |e|
Event.update_counters e.id, :bookings_count => e.bookings.length
end
end

Update a status field comparing dates after an action

this is the schema and my model for Visit (visit's status can be: Confirmed, Current, Expired and To be approved)
schema.rb
create_table "visits", force: true do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.date "start"
t.date "end"
t.integer "idVisit"
t.integer "employee_id"
t.integer "visitor_id"
t.string "status", default: "Confirmed"
end
Visit.rb
class Visit < ActiveRecord::Base
belongs_to :employee
belongs_to :visitor
default_scope -> { order(:created_at) }
validates :start, presence: true, uniqueness: {scope: [:end, :visitor_id]}
validates :end, presence: true
validates :visitor_id, presence: true
validates :employee_id, presence: true
validate :valid_date_range_required
def valid_date_range_required
if (start && end) && (end < start)
errors.add(:end, "must be after start")
end
end
end
Now my problem is that I need to compare for each visit, after each time I do show action in employees_controller.rb, the start and end date to Date.today (except for To be approved status); according to it I will change the status of visits in the database.
Here is what I did but probably there will be some mistakes since for now an error occurs at least, so I hope you could help me to fix it.
In Visit.rb I created this:
def check_visit_status(visit)
if visit.status != 'To be confirmed'
if visit.start <= Date.today && visit.end >= Date.today
visit.status = 'Current'
end
if visit.end < Date.today
visit.status = 'Expired'
end
end
end
Now in employees_controller.rb I have (I won't post it all):
class EmployeesController < ApplicationController
after_action :update_status, only: :show
def show
if logged_in?
#employee = Employee.find(params[:id])
#indirizzimac = current_employee.indirizzimacs.new
#visitor = current_employee.visitors.new
#visit = current_employee.visits.new
#visits = current_employee.visits.all
if params[:act]=='myData'
render 'myData'
elsif params[:act]=='myNetwork'
render 'myNetwork'
elsif params[:act]=='temporaryUsers'
render 'temporaryUsers'
elsif params[:act]=='guestsVisits'
render 'guestsVisits'
elsif params[:act]=='myAccount'
render 'myAccount'
else
render 'show'
end
else
render 'static_pages/errorPage'
end
end
def update_status
if #visits.any?
#visits.each do |visit|
check_visit_status(visit)
end
end
end
end
Thank you a lot in advance
I really have to thank eeeeeean for his immense help.
I figured out my problem so I want to post here my solution in order to help someone looking for the same thing I was asking for.
employees_controller.rb
class EmployeesController < ApplicationController
after_action :update_status, only: :show
def show
[...]
end
def update_status
if #visits.any?
#visits.each do |visit|
visit.check_visit_status
end
end
end
end
Visit.rb
def check_visit_status
if self.status != 'To be confirmed'
if self.start <= Date.today && self.end >= Date.today
self.update_attribute :status, 'Current'
end
if self.end < Date.today
self.update_attribute :status, 'Expired'
end
end
end
You need to call check_visit_status on an instance of Visit, but right now it's being called on self, which in this scope refers to the employees controller. Try this:
visit.rb
def check_visit_status
if self.status != 'To be confirmed'
if self.start <= Date.today && end >= Date.today
self.status = 'Current'
end
if self.end < Date.today
self.status = 'Expired'
end
end
end
Then call it like this:
employees_controller.rb
#visits.each do |visit|
visit.check_visit_status
end
That should get you out of that particular error.

uninitialized constant Payroll_Manager::Teachers

I'm making a payroll system for pay salaries. I only need to choice a month, a year and press "create payrolls", and create payrolls for all the teachers
mockup
I created the method "create" in payroll_controller.rb:
Payroll_Manager.new(params[:month], params[:year]).crear_liquidaciones_del_periodo()
The Payroll_Manager is in the file app/helpers/payroll_manager.rb
class Payroll_Manager < PayrollsController
def initialize(month, year)
#month = month
#year = year
end
def crear_liquidaciones_del_periodo
Teachers.each do |t|
t.payrolls.create(#month, #year)
end
end
end
And finally, I have the codel payroll.rb
class Payroll < ActiveRecord::Base
belongs_to :teacher
has_many :payroll_lines
def period
period = month + " " + year
end
validates :month, presence: true
validates :year, presence: true
class Payroll < ActiveRecord::Base
#gross_total, retention_total, neto_total
before_save :calculate_payroll
private
def calculate_payroll
calculate_gross_total
calculate_retention_total
calculate_net_total
end
def calculate_gross_total
self.gross_total = 0
## Concepto.where(type: 1)
haberes = Concepts.all.select{ |c| c.type == 1 }
haberes.each do |h|
parametros_para_linea = {concept_id: h.id, subtotal: h.amount}
self.payroll_line.create(parametros_para_linea)
self.gross_total += h.amount
end
end
def calculate_retention_total
self.retention_total = 0
## Concepto.where(type: 0)
retencion = Concepts.all.select{ |c| c.type == 0 }
retencion.each do |r|
parametros_para_linea = {concept_id: h.id, subtotal: h.amount}
self.payroll_line.create(parametros_para_linea)
self.retention_total += r.amount
end
end
def calculate_net_total
self.net_total = gross_total - retention_total
end
end
end
...When I click the "create payroll" button, I have the error:
uninitialized constant Payroll_Manager::Teachers
enter image description here
Please, help me.
Your class definition in payroll_manager.rb should be PayrollManager, not Payroll_Manager.

Resources