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
Related
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
Consider a relationship like this:
class BuyableComponent < ActiveRecord::Base
attr_accessible :cost
end
class CartItem < ActiveRecord::Base
attr_accessible :quantity
belongs_to :buyable_component
def total_cost
# This should be buyable_component.cost, but how do I make an alias so
# I can just use 'cost'?
cost * quantity
end
end
I have a buyable_components table and a cart_items table. Like the comment describes, I would like to be able to use cart_item.cost instead of cart_item.buyable_component.cost. alias_attribute seems to be close to what I need, but not quite.
I'm looking for a way to declare this for all attributes of BuyableComponent.
try something like:
class CartItem < ActiveRecord::Base
delegate :cost, :to => :buyable_component
end
this should work I suppose
I'm trying to call a class method (currently a scope) that uses an attribute from its parent (or belongs_to) model, but can't seem to get it working right.
My models:
class Venue < ActiveRecord::Base
attr_accessible :address
has_many :events, :dependent => :destroy
end
class Event < ActiveRecord::Base
belongs_to :venue
scope :is_near, lambda {|city| self(Venue.address).near(city, 20, :units => :km)}
end
I know the syntax is wrong, but I think that illustrates what I'm intending to do. I want to get the address of the venue and call another method on it. I need the scope in the Event class so I can chain other scopes together.
Appreciate any ideas.
Since #address is not a class method but an instance method, you won't be able to do what you want by using a scope.
If you want to get all the events within a 20km range of a venue, create these class methods in Venue instead:
class Venue < ActiveRecord::Base
def self.events_near_city(city)
venues_near_city(city).map(&:events).flatten
end
private
def self.venues_near_city(city)
near(city, 20, :units => :km)
end
end
Then call it by using Venue.events_near_city(session[:city]) since, as you told me in chat, you're storing the city in the session.
As you've defined it above, address is not a class method - it's an instance method. You would have to have an instance of venue (like you do in your view) to call it.
Searching a bit more I found this page that answered the question in another way. This works better for me because it's simpler to call, and I can use it on various relations. In rails how can I delegate to a class method
class Venue < ActiveRecord::Base
attr_accessible :address
def self.is_near(city)
venues_near_city(city).map(&:events).flatten
end
private
def self.venues_near_city(city)
self.near(city, 20, :units => :km)
end
end
class Event < ActiveRecord::Base
belongs_to :venue
class << self
def is_near(*args, &block)
Venue.is_near(*args, &block)
end
end
end
And I call it with event.is_near(session[:city])
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.
I am trying to calculate the average (mean) rating for all entries within a category based on the following model associations ...
class Entry < ActiveRecord::Base
acts_as_rateable
belongs_to :category
...
end
class Category < ActiveRecord::Base
has_many :entry
...
end
class Rating < ActiveRecord::Base
belongs_to :rateable, :polymorphic => true
...
end
The rating model is handled by the acts as rateable plugin, so the rateable model looks like this ...
module Rateable #:nodoc:
...
module ClassMethods
def acts_as_rateable
has_many :ratings, :as => :rateable, :dependent => :destroy
...
end
end
...
end
How can I perform the average calculation? Can this be accomplished through the rails model associations or do I have to resort to a SQL query?
The average method is probably what you're looking for. Here's how to use it in your situation:
#category.entries.average('ratings.rating', :joins => :ratings)
Could you use a named_scope or custom method on the model. Either way it would still require some SQL since, if I understand the question, your are calculating a value.
In a traditional database application this would be a view on the data tables.
So in this context you might do something like... (note not tested or sure it is 100% complete)
class Category
has_many :entry do
def avg_rating()
#entries = find :all
#entres.each do |en|
#value += en.rating
end
return #value / entries.count
end
end
Edit - Check out EmFi's revised answer.
I make no promises but try this
class Category
def average_rating
Rating.average :rating,
:conditions => [ "type = ? AND entries.category_id = ?", "Entry", id ],
:join => "JOIN entries ON rateable_id = entries.id"
end
end