Simpler way to update big decimal column in rails? - ruby-on-rails

I am building a simple budgeting app, and have a line of code that feels convoluted and overly complex. For context:
class User < ActiveRecord::Base
has_one :month_budget
has_many :expenditures, as: :spendable
end
class MonthBudget < ActiveRecord::Base
belongs_to :user
has_many :expenditures, as: spendable
end
class Expenditure < ActiveRecord::Base
belongs_to :spendable, polymorphic: true
end
Within my Expenditure class, I have defined a class method, add_expenditure:
class Expenditure < ActiveRecord::Base
def self.add_expenditure(user, params) #params passed will be in [:expenditure][*keys], in which possible keys are [:amount] or [:location]
if user.month_budget
user.month_budget.expenditures.create(params)
new_amount = user.month_budget.current_amount += params[:amount].to_d
user.month_budget.update(current_amount: new_amount)
end
end
end
Is there a more efficient way to add a value to the initial month_budget.current_amount column, and then record this new number to the database?
Cheers in advance!

Maybe you could try increment! method (http://apidock.com/rails/v4.2.1/ActiveRecord/Persistence/increment%21).
However, I am not sure if it works well with big decimals.
class Expenditure < ActiveRecord::Base
def self.add_expenditure(user, params)
if user.month_budget
user.month_budget.expenditures.create(params)
user.month_budget.increment!(:current_amount, params[:amount].to_d)
end
end
end

Related

How could I DRY the common methods in different model

I wrote setter and getter for the virtual attribute stock
For the getter it will aggregate the HAM_MANY relation records.
For the setter, it will create a new record and save the record to the right child table.
How could I DRY the two methods in the two models?
How could I avoid have two identical code in different model files? Thanks
Model Flight
has_many :stocks, foreign_key: "flight_sku_fare_id", class_name: "FlightSkuFareStock", dependent: :destroy
def stock
stocks.sum(:amount)
end
def stock=(stock_value)
self.save
stock_delta = stock_value - self.stock
if stock_value >=0 and (stock_delta!=0)
self.stocks.create(amount: stock_delta)
end
end
Model Room
has_many :stocks, foreign_key: "room_sku_id", class_name: "RoomSkuStock", dependent: :destroy
def stock
stocks.sum(:amount)
end
def stock=(stock_value)
self.save
stock_delta = stock_value - self.stock
if stock_value >=0 and (stock_delta!=0)
self.stocks.create(amount: stock_delta)
end
end
You can look into active_support/concern.
app/models/concerns/stock_concern.rb
require 'active_support/concern'
module StockConcern
extend ActiveSupport::Concern
def stock
stocks.sum(:amount)
end
def stock=(stock_value)
self.save
stock_delta = stock_value - self.stock
if stock_value >=0 and (stock_delta!=0)
self.stocks.create(amount: stock_delta)
end
end
end
And in your models,
app/models/flights.rb
class Flight < ActiveRecord::Base
include StockConcern
## other methods
end
app/models/rooms.rb
class Room < ActiveRecord::Base
include StockConcern
## other methods
end
You might have to tweak it a little bit to make it work perfectly.

Rails 4 custom method in Model with no Controller

I have 3 relevant models in a Rails 4 app - Charge:
class Charge < ActiveRecord::Base
belongs_to :rate
belongs_to :shift
def total
self.rate.value * self.quantity
end
end
Rate:
class Rate < ActiveRecord::Base
has_many :charges
end
and Shift:
class Shift < ActiveRecord::Base
has_many :charges
def total_charge
self.charges.sum('total')
end
end
I'm attempting to use shift.total_charge in my view, but I'm getting the error:
SQLite3::SQLException: no such column: total: SELECT SUM(total) FROM "charges" WHERE "charges"."shift_id" = ?
So it seems that it isn't possible to define total in the Charge model in this way, and have it accessible to sum from the Shift model as an actual column would be. I'm struggling to find the appropriate Rails 4 way of doing this - is it possible to do this in the model, or do I need to create a controller for Charge and try to do the calculation there?
sum works only with columns. You could use something like
class Shift < ActiveRecord::Base
has_many :charges
def total_charge
self.charges.map {|c| c.rate.value * c.quantity }.sum
end
end
and to avoid n+1 problem include Rate in Charge
class Charge < ActiveRecord::Base
belongs_to :rate
belongs_to :shift
default_scope {includes :rate}
end

ActiveRecord attribute depends of a calculation of other model

This is my scenario:
class User < ActiveRecord::Base
has_many :things
# attr_accessible :average_rating
end
class Thing < ActiveRecord::Base
belongs_to :user
has_one :thing_rating
end
class ThingRating < ActiveRecord::Base
belongs_to :thing
attr_accessible :rating
end
I want to have an attribute in my User model which has the average calculation of his related ThingsRating.
What would be the best practice to manage this?
Thanks
May be you can use relation not sure but you can try this
class User < ActiveRecord::Base
has_many :things
has_many :thing_ratings, through: :things
# attr_accessible :average_rating
def avg_rating
#avg_rating ||= thing_ratings.average("thing_ratings.rating")
end
end
The easy way :
class User < ActiveRecord::Base
has_many :things
def avg_rating
#avg_rating ||= average(things.map(&:thing_rating))
end
private
def average(values)
values.inject(0.0) { |sum, el| sum + el } / arr.size
end
end
This is fine as a starter. But if you have a bit of trafic, you might find yourself with scaling problems.
You'll then have to refactor this to avoid making an SQL query to the things every time you call the method for a different user.
You could then have several possibilites :
Add a field in your User database, avg_rating, which would be updated by the ThingRating when it's created or updated.
Use a memcached or redis database to cache the value and invalidate the cache every time a ThingRating is updated or created.
These solutions aren't exhaustive of course. And you could find other ones which would better fit your needs.

Two models depends on each other – catch 22

Here is my scenario:
A model called Course has many CourseCodes. A CourseCode belongs to a Course.
A CourseCode can't be created without Course and a Course can't be created without at least one CourseCode.
class Course < ActiveRecord::Base
has_many :course_codes
validate :existence_of_code
private
def existence_of_code
unless course_codes.any?
errors[:course_codes] << "missing course code"
end
end
end
class CourseCode < ActiveRecord::Base
belongs_to :course
validates_presence_of :course
end
The whole scenario feels a bit like catch 22.
Is there a way to create both on the same time?
I'm using Rails 3.2
Solved the problem by using accepts_nested_attributes_for.
Nested attributes allow you to save attributes on associated records through the parent. By default nested.
class Course < ActiveRecord::Base
has_many :course_codes, inverse_of: :course
validate :existence_of_code
accepts_nested_attributes_for :course_codes
private
def existence_of_code
unless course_codes.any?
errors[:course_codes] << "missing course code"
end
end
end
class CourseCode < ActiveRecord::Base
belongs_to :course, inverse_of: :course_codes
validates_presence_of :course
end
Used like this.
Course.create!({
course_codes_attributes: [{ code: "TDA123" }],
# ...
})
Looks good to me. Removing the validates_presence_of :course might make things easier on you, too, as it will tend to get in the way an not add much.
When you create a course, do it like this:
Course.create course_codes: [CourseCode.new(...), CourseCode.new(...)]
ActiveRecord will figure things out.
You could add an unless to whichever model you would plan to create first. For instance:
class CourseCode < ActiveRecord::Base
belongs_to :course
validates_presence_of :course, :unless => lambda { Course.all.empty? }
end

Sharing model for polymorphic associations

Assume a polymorphic association, say 'business' and 'staff', both of which are 'hourable' (meaning they have hours assigned to them). What's the recommended approach to have the 'hour' model performs the same methods on the hours of either a business object or a staff object?
For a simple example, the 'hour' model might contain:
def self.find_hours_for_id(id)
Business.find( id ).hours
end
However, I may want to perform this same method on a Staff member, in which case the same method would instead call the equivalent of
Staff.find( id ).hours
Is it good or bad form to set the model name within the base model class:
class BusinessHour < Hour
#mymodel = "Business"
end
class Hour < ActiveRecord::Base
def self.find_hours_for_id(id)
#mymodel.constantize.find( id ).hours
end
end
Then from the controller, call:
BusinessHour.find_hours_for_id(id)
Is there a better solution?
You can use a module too, like that.
module Hour
def hours_by(model)
class_eval do
def self.find_hours_for_id(id)
model.find(id).hours
end
end
end
end
class BusinessHour < AR
extends Hour
hours_by Business
end
BusinessHour.find_hours_for_id(id)
Assuming, oh for instance, your object model looks like:
class Business < ActiveRecord::Base
has_many :hours, :as => :hourable
end
class Staff < ActiveRecord::Base
has_many :hours, :as => :hourable
end
class Hour < ActiveRecord::Base
belongs_to :hourable, :polymorphic => true
end
class BusinessHour < Hour
# etc
end
Then you already have a reference to the class you need in 'hourable,' and you can add the finder as follows:
class Hour < ActiveRecord::Base
belongs_to :hourable, :polymorphic => true
def self.get_hours_by_hourable_id(id)
hourable.class.find(id).hours
end
end
Just a suggestion, but you might consider putting a validation on the subclasses to guarantee that :hourable is the appropriate type, such as:
class BusinessHour < Hour
validates :hourable_must_be_business
def hourable_must_be_business
unless hourable_type == 'Business'
self.errors.add_to_base("Hourable for BusinessHour must be Business")
end
end
end

Resources