Rails 4 custom method in Model with no Controller - ruby-on-rails

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

Related

Simpler way to update big decimal column in 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

How to sum up all associated records from join table in Ruby on Rails?

I have the following models in my Ruby on Rails app :
class Invoice < ActiveRecord::Base
has_many :allocations
has_many :payments, :through => :allocations
end
class Allocation < ActiveRecord::Base
belongs_to :invoice
belongs_to :payment
end
class Payment < ActiveRecord::Base
has_many :allocations, :dependent => :destroy
has_many :invoices, :through => :allocations
end
My problem is that in the Allocation class I would like to use the total_amount of all associated invoices, ideally in a before_save callback.
This isn't possible right now, however, because at the time an allocation object gets saved it is only associated with one particular invoice object.
How can this be done with a minimum of database queries?
Invoice.where(asdfasdfasdf).map(&:allocations).flatten.map(&:total_amount).compact.inject(:+)
Because this is rails the database call is nothing. To sum up an array of numbers you can use this:
ary = [0,12,2,6,nil]
ary.compact.inject(:+)
#=> 20
You could clean this up a bit:
class Invoice
#...
def total_amount
allocations.map(&:total_amount).inject(:+) #throws error if there is 1 nil 'total_amount data' value
end
def self.sum_allocation_amounts(sql)
where(sql).map(&:total_amount).inject(:+)
end
end
Its not possible to call self.map(&:allocations) inside of an Invoice class method without errors so I'm passing in the some basic sql as a workaround. Ideally I'd make it possible to directly call this method on a daisy chain of activerecord where calls for Invoice but that's not working out for me right now ("undefined method map' for Class")
Invoice.sum_allocation_amounts("democol = 'demo params'")

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.

How do I automatically assign the latest location tax to a product?

I have 3 models; Product, Tax and Location. Whenever a product gets created I want to assign the latest tax of the location if it has a tax.
class Location < ActiveRecord::Base
belongs_to :user
has_many :products
has_many :taxes
end
class Tax < ActiveRecord::Base
attr_accessible :date # I use this to get the latest tax
belongs_to :location
has_many :products
end
class Product < ActiveRecord::Base
attr_accessible :tax_id
belongs_to :location
belongs_to :tax
end
Now I tried this in my Product model:
after_create :assign_latest_location_tax
private
def assign_latest_location_tax
if self.location.tax.present?
self.tax_id = self.location.tax.order("date DESC").first.id
end
end
But this gives me the error:
NoMethodError in ProductsController#create
undefined method `tax' for #<Location:0x4669bf0>
What is the correct way to do this?
Location has_many taxes, therefore the method it exposes to access it's taxes is taxes, not tax.
The following should work:
self.tax_id = self.location.taxes.order("date DESC").first.id
And if you use the after_create callback you must call save again on the end of it. To avoid this, you may use the before_create callback.
This code should work:
def assign_latest_location_tax
if self.location.taxes.count > 0
self.tax_id = self.location.taxes.order("date DESC").first.id
end
end

Using ActiveRecord belongs_to with two keys

I have two ActiveRecord models with a hasMany / belongsTo association:
class User < ActiveRecord::Base
has_many :letters
end
class Letter < ActiveRecord::Base
belongs_to :user
end
The User model has a revision_number attribute, to which I would like to scope the belongs_to association, so the letter is associated to a User by both user.id and user.revision_number.
I tried using the :conditions key as documented in the API docs:
class Letter < ActiveRecord::Base
belongs_to :user, :conditions => "revision_number = #{client_revision}"
end
but this attempts to call client-revision on the Letter class, not the instance of Letter. Could anyone point me in the right direction for scoping the belongs_to association correctly?
I'm using the acts-as-revisable plugin to version the User model.
I am having a hard time understanding why you would want to scope the belongs_to in this way. Correct me if I am wrong, but it might be better to do something like this. I am assuming you want some sort of version control system:
class User < ActiveRecord::Base
has_many :letters
end
class Letter < ActiveRecord::Base
has_many :revisions, :class_name => "LetterVersion"
belongs_to :current, :class_name => "LetterVersion"
end
class LetterVersion < ActiveRecord::Base
belongs_to :letter
end
Finally figured out what I needed was something like composite keys, which Rails ActiveRecord doesn't support. The solution (for now at least) was to write custom client accessors on the Letter to support the composite keys (id and revision_number):
class Letter < ActiveRecord::Base
def client
Client.find_by_id(self.client_id).try(:find_revision, self.client_revision)
end
def client=(c)
self.client_id = c.id
self.client_revision = c.revision_number
end
end
class Client < ActiveRecord::Base
acts_as_revisable
has_many :letters
end
With this setup, Client#1.letters will retrieve an array of both letters, but Letter#2.client will retrieve Client#1r2, whilst Letter#2.client will retrieve Client#1r4:
Client id: 1 1 1 1 1 1
rev_number: 1 2 3 4 5 6
Letter id: 1 2
client_id: 1 1
client_revision: 2 5
Still not sure if this is the best approach to this problem, but it seems to work for now.

Resources