What would be the right (Rails) way to have one model with both a has_many and a has_one relationship? In my case, I want my Device model to keep track of both its current location and all of its previous locations.
This is my attempt and it is functional but is there a better way?
Models
class Location < ActiveRecord::Base
belongs_to :device
end
class Device < ActiveRecord::Base
has_many :locations # all previous locations
belongs_to :location # current location
end
class Location < ActiveRecord::Base
belongs_to :device
end
class Device < ActiveRecord::Base
has_many :locations
def previous_locations
self.locations.order('created_at asc').limit( self.locations.count-1)
end
def current_location # or last_location
self.locations.order('created_at desc').limit(1)
end
# you may like to add this one
def current_location= args
args = Location.new args unless args.is_a? Location
self.locations << args
end
end
Note that all #device.locations, #device.previous_locations, and #device.current_location will return ActiveRecord::Relation
class Device < ActiveRecord::Base
has_and_belongs_to_many :locations
end
Well, the Rails way is that you can create many associations you like. You can name your associations as well based on your logic. Simply pass the :class_name option to your association logic.
class Location < ActiveRecord::Base
belongs_to :device
end
class Device < ActiveRecord::Base
has_many :previous_locations,
:class_name => "Location",
:conditions => ["locations.created_at < ?", DateTime.now]
has_one :location,
:class_name => "Location",
:conditions => ["locations.created_at = ?", DateTime.now]
end
In the "Guide to Active Record Associations", I recommend reading section 2.8: Choosing Between has_many :through and has_and_belongs_to_many
The simplest rule of thumb is that you should set up a has_many
:through relationship if you need to work with the relationship model
as an independent entity. If you don’t need to do anything with the
relationship model, it may be simpler to set up a
has_and_belongs_to_many relationship (though you’ll need to remember
to create the joining table in the database).
You should use has_many :through if you need validations, callbacks,
or extra attributes on the join model.
http://guides.rubyonrails.org/association_basics.html#choosing-between-has_many-through-and-has_and_belongs_to_many
class Location < ActiveRecord::Base
belongs_to :device
has_one :current_location, :class_name => 'Device',
:conditions => { :active => true }
end
class Device < ActiveRecord::Base
has_many :locations # all previous locations
end
Location has a boolean field called 'active' that you set to true/false.
Related
This seems to be a fairly common problem over here, yet there is no definitive solution. To restate once again, say I have a model:
def Model < ActiveRecord::Base
has_many :somethings, ...
has_many :otherthings, ...
end
The question is then how to add a third association :combined that combines the two? I know this can be done with :finder_sql and similar result can be achieved with a scope, but neither of these gives me an actual association. The whole point of this is to be able to use it for another association with :through and things like Model.first.combined.some_scope.count
EDIT: the relevant portions of the actual code
class Donation < ActiveRecord::Base
# either Project or Nonprofit
belongs_to :donatable, :polymorphic => true
belongs_to :account
end
class Project < ActiveRecord::Base
belongs_to :nonprofit
end
class Nonprofit < ActiveRecord::Base
has_many :projects
# donations can be either direct or through a project
# the next two associations work fine on their own
# has_many :donations, :as => :donatable, :through => :projects
# has_many :donations, :as => :donatable
has_many :donations, .... # how do I get both here,
has_many :supporters, :through => :donations # for this to work?
end
Thanks.
If Something and Otherthing are sufficiently similar, use STI:
def Model < ActiveRecord::Base
has_many :somethings
has_many :otherthings
has_many :genericthings
end
def Genericthing < Activerecord::Base
# put a string column named "type" in the table
belongs_to :model
end
def Something < Genericthing
end
def Otherthing < Genericthing
end
I am currently trying to create a custom method on a model, where the conditions used are those of a has_many association. The method I have so far is:
class Dealer < ActiveRecord::Base
has_many :purchases
def inventory
inventory = Vehicle.where(:purchases => self.purchases)
return inventory
end
end
This is not working, due to the fact that Vehicle has_many :purchases (thus there is no column "purchases" on the vehicle model). How can I use the vehicle.purchases array as a condition in this kind of query?
To complicate matters, the has_many is also polymorphic, so I can not simply use a .join(:purchases) element on the query, as there is no VehiclePurchase model.
EDIT: For clarity, the relevant parts of my purchase model and vehicle models are below:
class Purchase < ActiveRecord::Base
attr_accessible :dealer_id, :purchase_type_id
belongs_to :purchase_item_type, :polymorphic => true
end
class Vehicle < ActiveRecord::Base
has_many :purchases, :as => :purchase_item_type
end
class Dealer < ActiveRecord::Base
def inventory
Vehicle.where(:id => purchases.where(:purchase_item_type_type => "Vehicle").map(&:purchase_item_type_id))
end
end
Or:
def inventory
purchases.includes(:purchase_item_type).where(:purchase_item_type_type => "Vehicle").map(&:purchase_item_type)
end
I was able to do this using the :source and :source_type options on the Vehicle model, which allows polymorphic parents to be associated.
I have the models shown below. I need to store some details that are specific a person and a house (first_viewed:date, opening offer:decimal, etc). I feel like these should belong to the PersonHouse model but I'm not certain enough. Any suggestions?
class Person < ActiveRecord::Base
has_many :houses, through: :person_houses
has_one :favorite_house, through: :person_houses
end
class PersonHouse < ActiveRecord::Base
belongs_to :house
belongs_to :person
end
class House < ActiveRecord::Base
has_many :house_people
has_many :people, through: :person_houses
end
I could do something like this to get all the details but perhaps there is a more effient way.
#house = House.find(1)
#house.house_people.each do |hp|
puts hp.person.name
puts hp.first_viewed
puts #house.address
end
I think your assumption is correct. If the data is relevant to the relationship between a person and a house, then yes it belongs on this model. The only recommendation I would make is to rename this model to a name that better describes what the relationship is. It doesn't have to be the concatenation of the two models it joins. I don't know exactly what the model is going to be ultimately used for, but SelectedHouse, HouseProspect or something along those lines might work.
You can also delegate properties to the house or person models:
class PersonHouse < AR::Base
belongs_to :person
belongs_to :house
delegate :address, :to => :house, :prefix => true
delegate :name, :to => :person, :prefix => true
end
person_house.address
person_house.person_name
I have many-to-many relationship between Game and Account models like below:
class Account < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :games, :through => :account_games
end
class Game < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :accounts, :through => :account_games
end
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
end
Now I know let's say I want to create a record something like:
#account = Account.new(params[:user])
#account.games << Game.first
#account.save
But how am I supposed to update some of the attributes in AccountGame while I'm doing that? Lets say that AccountGame has some field called score, how would I update this attribute? Can you tell me about the best way to do that? To add any field in the through table while I'm saving the object.
#account = Account.new(params[:user])
#accountgame = #account.account_games.build(:game => Game.first, :score => 100)
#accountgame.save
Though I'd strongly recommend that if you start adding columns to your join-model that you call it something different eg "subscription" or "membership" or something similar. Once you add columns it stops being a join model and starts just being a regular model.
This should work:
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
attr_accessible :account_id, :game_id #<======= Notice this line
end
A Person can have many Events and each Event can have one polymorphic Eventable record. How do I specify the relationship between the Person and the Eventable record?
Here are the models I have:
class Event < ActiveRecord::Base
belongs_to :person
belongs_to :eventable, :polymorphic => true
end
class Meal < ActiveRecord::Base
has_one :event, :as => eventable
end
class Workout < ActiveRecord::Base
has_one :event, :as => eventable
end
The main question concerns the Person class:
class Person < ActiveRecord::Base
has_many :events
has_many :eventables, :through => :events # is this correct???
end
Do I say has_many :eventables, :through => :events like I did above?
Or do I have to spell them all out like so:
has_many :meals, :through => :events
has_many :workouts, :through => :events
If you see an easier way to accomplish what I'm after, I'm all ears! :-)
You have to do:
class Person < ActiveRecord::Base
has_many :events
has_many :meals, :through => :events, :source => :eventable,
:source_type => "Meal"
has_many :workouts, :through => :events, :source => :eventable,
:source_type => "Workout"
end
This will enable you to do this:
p = Person.find(1)
# get a person's meals
p.meals.each do |m|
puts m
end
# get a person's workouts
p.workouts.each do |w|
puts w
end
# get all types of events for the person
p.events.each do |e|
puts e.eventable
end
Another option of this is to use a Single Table Inheritance (STI) or Multi Table Inheritance (MTI) pattern, but that requires some ActiveRecord/DB Table rework, but this may help others still finding this who are designing it for the first time.
Here is the STI method in Rails 3+:
Your Eventable concept becomes a class and needs a type column (which rails automatically populates for you).
class Eventable < ActiveRecord::Base
has_one :event
end
Then, your other two classes inherit from Eventable instead of AR::Base
class Meal < Eventable
end
class Workout < Eventable
end
And your event object is basically the same, just not polymorphic:
class Event < ActiveRecord::Base
belongs_to :person
belongs_to :eventable
end
This may make some of your other layers more confusing, if you've never seen this before and you're not careful. For example, a single Meal object can be accessed at /meals/1 and /eventable/1 if you make both endpoints available in the routes, and you need to be aware of the class you're using when you pull an inherited object (hint: the becomes method may be very useful if you need to override the default rails behavior)
But this is a much cleaner deliniation of responsibilities as apps scale, in my experience. Just a pattern to consider.