I am trying to get an activerecord association through 2 layers of has_one associations and cannot quite figure it out.
I have 3 models:
class Dialog < ActiveRecord::Base
belongs_to :contact
end
class Contact < ActiveRecord::Base
has_many :dialogs
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :contacts
end
I would like to create a scope in the Dialog model that allows me to pass in the id of a Location and get all Dialogs created by Contacts from the given Location... something like:
Dialog.from_location(Location.first.id)
I can get a non-activerecord array of the desired result using select:
Dialog.all.select{|s| s.contact.location_id == Location.first.id }
But I need this to return an activerecord array so that other scopes or class methods can be called on the result. I have tried using joins and includes, but am confused after reading the rails guides on how to use them.
Can anyone help me figure out how to construct a scope or class method that will accomplish this?
Thanks in advance
Just adding a note to the answer you accepted, quoting from the ruby on rails guides website:
Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects ( link )
So in your condition, instead of doing a scope with an argument, define a method :
class Contact < ActiveRecord::Base
has_many :dialogs
belongs_to :location
def self.from_location(id)
where(location_id: id)
end
end
You can define your scopes as follows:
class Dialog < ActiveRecord::Base
belongs_to :contact
scope :from_location, -> (id) do
where(contact_id: Contact.from_location(id).select(:id))
end
end
class Contact < ActiveRecord::Base
has_many :dialogs
belongs_to :location
scope :from_location, ->(id) do
where(location_id: id)
end
end
Related
I have a number of associated tables in an application
class Listing < ActiveRecord::Base
belongs_to :house
belongs_to :multiple_listing_service
end
class House < ActiveRecord::Base
has_one :zip_code
has_one :primary_mls, through: :zip_code
end
I wanted to create a scope that produces all the Listings that are related to the Primary MLS for the associated House. Put another way, the scope should produce all the Listings where the multiple_listing_service_id = primary_mls.id for the associated house.
I've tried dozens of nested joins scopes, and none seem to work. At best they just return all the Listings, and normally they fail out.
Any ideas?
If I understand correctly, I'm not sure a pure scope would be the way to go. Assuming you have:
class MultipleListingService < ActiveRecord::Base
has_many :listings
has_many :zip_codes
end
I would go for something like:
class House < ActiveRecord::Base
...
def associated_listings
primary_mls.listings
end
end
Update 1
If your goal is to just get the primary listing then I would add an is_primary field to the Listing. This would be the most efficient. The alternative is a 3 table join which can work but is hard to optimize well:
class Listing < ActiveRecord::Base
...
scope :primary, -> { joins(:houses => [:zip_codes])
.where('zip_codes.multiple_listing_service_id = listings.multiple_listing_service_id') }
I have the following situation in my Rails app. There are two models, let's say User and Company:
class User < ActiveRecord::Base
belongs_to :company
default_scope -> {where(removed_at: nil)}
end
and
class Company < ActiveRecord::Base
has_many :users
end
What I want now is loading an Company record and include the Users
Company.unscoped.all.includes(:users)
What will result in a query to the users table which includes the default-scope.
So I get the Company record with all not removed users prefetched. But in this case I do also want the Users where removed_at is not null (=> the removed User records). The "unscoped" method is only applied to the Company model, not to the User model.
Is there any way to do this? Thanks for any ideas!
Here is the solution I got working in my Rails 4.0 application
class User < ActiveRecord::Base
belongs_to :company
default_scope -> {where(removed_at: nil)}
end
class UserUnscoped < User
self.default_scopes = []
end
class Company < ActiveRecord::Base
has_many :users, class_name: "UserUnscoped"
end
Company.unscoped.all.includes(:users)
This method accepts a block. All queries inside the block will not use the default_scope:
User.unscoped { Company.includes(:users).all }
or:
User.unscoped do
Company.includes(:users).all
end
The relation is not preserving this state, when accessing it later (Rails 6.0)
A possible workaround for some cases is forcing the resolution, e.g.:
User.unscoped { Company.includes(:users).to_a }
to_a resolves the relation while in the block, so the scope is effectively removed.
I had a greatly-branched STI models set. So the only approach, which works for me (Rails 4.2.5) is the following:
class UndeadUser < ActiveRecord::Base
self.table_name = 'users'
end
class User < UndeadUser
# real logic here
end
class Employee < User; end
class Manager < Employee; end
# grouping model
class Organization < ActiveRecord::Base
has_many :employees
has_many :users, class: UndeadUsers
before_destroy :really_destroy_users_and_data! # in case of using acts_as_paranoid or paranoia gems it might be helpful
end
Solution from #abstractcoder doesn't work for me, because rails try to add WHERE users.type IN ('UndeadUser') when UndeadUser inherited from User.
I am totally confused about how I should go about "the rails way" of effectively using my associations.
Here is an example model configuration from a Rails 4 app:
class Film < ActiveRecord::Base
# A movie, documentary, animated short, etc
has_many :roleships
has_many :participants, :through => :roleships
has_many :roles, :through => :roleships
# has_many :writers........ ?
end
class Participant < ActiveRecord::Base
# A human involved in making a movie
has_many :roleships
end
class Role < ActiveRecord::Base
# A person's role in a film. i.e. "Writer", "Actor", "Extra" etc
has_many :roleships
end
class Roleship < ActiveRecord::Base
# The join for connecting different people
# to the different roles they have had in
# different films
belongs_to :participant
belongs_to :film
belongs_to :role
end
Given the above model configuration, the code I wish I had would allow me to add writers directly to a film and in the end have the join setup correctly.
So for example, I'd love to be able to do something like this:
## The Code I WISH I Had
Film.create!(name: "Some film", writers: [Participant.first])
I'm not sure if I'm going about thinking about this totally wrong but it seems impossible. What is the right way to accomplish this? Nested resources? A custom setter + scope? Something else? Virtual attributes? thank you!
I created a sample app based on your question.
https://github.com/szines/hodor_filmdb
I think useful to setup in Participant and in Role model a through association as well, but without this will work. It depends how would you like to use later this database. Without through this query wouldn't work: Participant.find(1).films
class Participant < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
class Role < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
Don't forget to give permit for extra fields (strong_parameters) in your films_controller.rb
def film_params
params.require(:film).permit(:title, :participant_ids, :role_ids)
end
What is strange, that if you create a new film with a participant and a role, two records will be created in the join table.
Update:
You can create a kind of virtual attribute in your model. For example:
def writers=(participant)
#writer_role = Role.find(1)
self.roles << #writer_role
self.participants << participant
end
and you can use: Film.create(title: 'The Movie', writers: [Participant.first])
If you had a normal has_and_belongs_to_many relationship i.e. beween a film and a participant, then you can create a film together with your examples.
As your joining model is more complex, you have to build the roleships separately:
writer= Roleship.create(
participant: Participant.find_by_name('Spielberg'),
role: Role.find_by_name('Director')
)
main_actor= Roleship.create(
participant: Participant.find_by_name('Willis'),
role: Role.find_by_name('Actor')
)
Film.create!(name: "Some film", roleships: [writer, main_actor])
for that, all attributes you use to build roleships and films must be mass assignable, so in a Rails 3.2 you would have to write:
class Roleship < ActiveRecord::Base
attr_accessible :participant, :role
...
end
class Film < ActiveRecord::Base
attr_accessible :name, :roleships
...
end
If you want to user roleship_ids, you have to write
class Film < ActiveRecord::Base
attr_accessible :name, :roleship_ids
...
end
Addendum:
Of cause you could write a setter method
class Film ...
def writers=(part_ids)
writer_role=Role.find_by_name('Writer')
# skiped code to delete existing writers
part_ids.each do |part_id|
self.roleships << Roleship.new(role: writer_role, participant_id: part_id)
end
end
end
but that makes your code depending on the data in your DB (contents of table roles) which is a bad idea.
I have a simple Customer model with a has many relationship with a Purchase model.
class Customer < ActiveRecord::Base
has_many :purchases
end
I am repeatedly finding that I need to order Customer.purchases in my views in the following way:
#customer.purchases.joins(:shop).order("shops.position").order(:position) #yes, two orders chained
In the interest of keeping things DRY, I'd like to put this somewhere centralized so I don't have to repeatedly do it. Ideally, I'd like to make it the default ordering for Customer.purchases. For example:
class Customer < ActiveRecord::Base
has_many :purchases, :order => joins(:shop).order("shops.position").order(:position)
end
Obviously the above doesn't work. How should I do this?
In your customer model you specified joins(:shop) is the value for the key :order. I think here is the problem, So you can use the joins as a key instead of order like below,
class Customer < ActiveRecord::Base
has_many :purchases, :joins => [:shop], :order => "shops.position"
end
I think it may work.
In your purchases model, you can create a class method:
Purchase.rb:
def self.order_by_position
joins(:shop).order("shops.position").order(:position)
end
Then you can say things like:
#customer.purchases.order_by_position
Purchase.order_by_position
You could create a method on Customer that returns ordered purchases:
class Customer < ActiveRecord::Base
has_many :purchases
def ordered_purchases
purchases.joins(:shop).order("shops.position").order(:position)
end
end
and call #customer.ordered_purchases from your views.
Assuming I have 5 tables. Can ActiveRecord handle this? How would you set it up?
The hierarchy:
Account (Abstract)
CorporateCustomer (Abstract)
PrivateCustomer
PublicCustomer
GovernmentCustomer
Edit: In nhibernate and castle activerecord the method needed to enable this scenario is called "joined-subclasses".
You could try something along the following lines.
class Account < ActiveRecord::Base
belongs_to :corp_or_gov_customer, :polymorphic => true
def account_id
self.id
end
end
class GovernmentCustomer < ActiveRecord::Base
has_one :account, :as => :corp_or_gov_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.account.send( symbol, *args )
end
end
class CorporateCustomer < ActiveRecord::Base
has_one :account, :as => :corp_or_gov_customer, :dependent => :destroy
belongs_to :priv_or_pub_customer, :polymorphic => true
def method_missing( symbol, *args )
self.account.send( symbol, *args )
end
end
class PrivateCustomer < ActiveRecord::Base
has_one :corporate_customer, :as => :priv_or_pub_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.corporate_customer.send( symbol, *args )
end
end
class PublicCustomer < ActiveRecord::Base
has_one :corporate_customer, :as => :priv_or_pub_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.corporate_customer.send( symbol, *args )
end
end
I've not tested this code (or even checked it for syntax). Rather it's intended just to point you in the direction of polymorphic relations.
Overriding method_missing to call nested objects saves writing code like
my_public_customer.corporate_customer.account.some_attribute
instead you can just write
my_public_customer.some_attribute
In response to the comment:
The problem is that concepts like "is a", "has many" and "belongs to" are all implemented by foreign key relationships in the relational model. The concept of inheritance is completely alien to RDB systems. The semantics of those relationships has to be mapped onto the relational model by your chosen ORM technology.
But Rails' ActiveRecord library doesn't implement "is_a" as a relationship between models.
There are several ways to model your class hierarchy in an RDB.
A single table for all accounts but with redundant attributes - this is supported by ActiveRecord simply by adding a "type" column to your table. and then creating your class hierarchy like this:
class Account < ActiveRecord::Base
class GovernmentCustomer < Account
class CorporateCustomer < Account
class PublicCustomer < CorporateCustomer
class PrivateCustomer < CorporateCustomer
Then if you call PrivateCustomer.new the type field will automatically be set to "PrivateCustomer" and when you call Account.find the returned objects will be of the correct class.
This is the approach I would recommend because it's by far the simplest way to do what you want.
One table for each concrete class - As far as I know there is no mapping provided for this in ActiveRecord. The main problem with this method is that to get a list of all accounts you have to join three tables. What is needed is some kind of master index, which leads to the next model.
One table for each class - You can think of tables that represent the abstract classes as a kind of uniform index, or catalogue of objects that are stored in the tables for the concrete classes. By thinking about it this way you are changing the is_a relationship to a has_a relationship e.g. the object has_a index_entry and the index_entry belongs_to the object. This can be mapped by ActiveRecord using polymorphic relationships.
There is a very good discussion of this problem in the book "Agile Web Development with Rails" (starting on page 341 in the 2nd edition)
Search for ActiveRecord Single Table Inheritance feature.
Unfortunately I can't find a more detailed reference online to link to. The most detailed explanation I read was from "The Rails Way" book.
This is one way (the simplest) of doing it:
class Account < ActiveRecord::Base
self.abstract_class = true
has_many :corporate_customers
has_many :government_customers
end
class CorporateCustomer < ActiveRecord::Base
self.abstract_class = true
belongs_to :account
has_many :private_customers
has_many :public_customers
end
class PrivateCustomer < ActiveRecord::Base
belongs_to :corporate_customer
end
class PublicCustomer < ActiveRecord::Base
belongs_to :corporate_customer
end
class GovernmentCustomer < ActiveRecord::Base
belongs_to :account
end
NOTE: Abstract models are the models which cannot have objects ( cannot be instantiated ) and hence they don’t have associated table as well. If you want to have tables, then I fail to understand why it needs to an abstract class.
Assuming most of the data is shared, you only need one table: accounts. This will just work, assuming accounts has a string type column.
class Account < ActiveRecord::Base
self.abstract_class = true
end
class CorporateCustomer < Account
self.abstract_class = true
has_many financial_statements
end
class PrivateCustomer < CorporateCustomer
end
class PublicCustomer < CorporateCustomer
end
class GovernmentCustomer < Account
end
Google for Rails STI, and in particular Rails STI abstract, to get some more useful info.