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.
Related
An application I'm working on, is trying to use the concept of polymorphism without using polymorphism.
class User
has_many :notes
end
class Customer
has_many :notes
end
class Note
belongs_to :user
belongs_to :customer
end
Inherently we have two columns on notes: user_id and customer_id, now the bad thing here is it's possible for a note to now have a customer_id and a user_id at the same time, which I don't want.
I know a simple/better approach out of this is to make the notes table polymorphic, but there are some restrictions, preventing me from doing that right now.
I'd like to know if there are some custom ways of overriding these associations to ensure that when one is assigned, the other is unassigned.
Here are the ones I've tried:
def user_id=(id)
super
write_attribute('customer_id', nil)
end
def customer_id=(id)
super
write_attribute('user_id', nil)
end
This doesn't work when using:
note.customer=customer or
note.update(customer: customer)
but works when using:
note.update(customer_id: 12)
I basically need one that would work for both cases, without having to write 4 methods:
def user_id=(id)
end
def customer_id=(id)
end
def customer=(id)
end
def user=(id)
end
I would rather use ActiveRecord callbacks to achieve such results.
class Note
belongs_to :user
belongs_to :customer
before_save :correct_assignment
# ... your code ...
private
def correct_assignment
if user_changed?
self.customer = nil
elsif customer_changed?
self.user = nil
end
end
end
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
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 have a bunch of activerecord classes that look way to similar for my liking!
can I extend a base class to DRY the methods up, or is that going to confuse rails?
maybe i can share some stuff but not others?
if not - what's the best way to proceed?
many thanks ;)
class Stage < ActiveRecord::Base
acts_as_taggable
has_many :services, :as => :serviceable
belongs_to :event
belongs_to :user
after_save :tag!
def t(s)
self.tag_list.add s
self.event.tag_list.add s
end
# injected to after_save -> http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
def tag!
s = self
if s.id > 0 then s.t "id-greater-than-0" end
if s.id > 0 then s.t "some-stage-specific-stuff" end
self.tag_list
end
end
class Sound < ActiveRecord::Base
acts_as_taggable
has_many :services, :as => :serviceable
belongs_to :event
belongs_to :user
after_save :tag!
def t(s)
self.tag_list.add s
self.event.tag_list.add s
end
# injected to after_save -> http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
def tag!
s = self
if s.id > 0 then s.t "some-sound-specific-stuff" end
self.tag_list
end
end
You can use Single Table Inheritance for this problem. Essentially you will have two separate models, but they'll be saved in the same table. Along with that you can extract out the common functionality into a parent class, and leave the specifics for the child classes. You can read more about STI here: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance
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