I have a chain model that has many facilities
I have a company model has many company_mappings. Through company_mappings a comapny can have many facilities either through chains or directly from a facility, both of them through a polymorphic association in the companies_mapping model.
The Company_Mapping Model
belongs_to :company
belongs_to :company_associations, polymorphic: true
The Chain Model
has_many :company_mappings, as: :company_associations
has_many :facilities
The Facility Model
has_many :company_mappings, as: :company_associations
belongs_to :chain
Now i have the company model has two different queries to get its assoiciated facilities. I would like to have one query that fetches all facilities associated from a company
The company model
has_many :company_mappings
has_many :chains, through: :company_mappings, source: :company_associations, source_type: "Chain"
has_many :facilities, through: :company_mappings, source: :company_associations, source_type: "Facility"
has_many :facilities_from_chains, through: :chains,source: :facilities, class_name: 'Facility'
I would like to combine facilities and _facilities_from_chains_ into a single query or able to somehow merge them. I tried using .merge but that gives an error.
One way to do this would be to provide an instance method which queried both relations separately, fetched the results and combined them. Try adding this to your company model:
def all_facilities
facilities + facilities_from_chains
end
Combining the results together into a single array like this will cause your results to be converted from a relation object (which you could call further .where clauses on for example) to an array which you cannot chain further AR statements to. It's worth keeping that in mind.
There may be another way to do this with a single SQL query, but this seems like the easiest implementation at the potential expense of DB performance.
I found a way just in case somebody what like to know how.
scope :company_facilities, -> (chain_ids,company_id){
includes(:company_mappings)
.where("chain_id IN (?) OR (company_mappings.company_associations_type=? and company_mappings.company_id=?)", chain_ids , "Facility", company_id)
.references(:company_mappings)
}
The idea is to include Company_Mapping which would do a left outer join. Then for each facility record find if it is part of any of the chain the Company is associated to else if its company_mappings.company_id equals to calling Company's id.
Related
I'm trying to list the model instances that do not have the association with another model created yet.
Here is how my models are related:
Ticket.rb:
has_one :purchase
has_one :user, through: :purchase
User.rb:
has_many :purchases
has_many :tickets, through: :purchases
Purchase.rb:
belongs_to :ticket
belongs_to :user
I have an SQL query but have troubles when translating it to rails:
SELECT id FROM tickets
EXCEPT
SELECT ticket_id FROM purchases;
It works great as it returns all ids of the tickets that are not purchased yet.
I've tried this:
Ticket.joins('LEFT JOIN ON tickets.id = purchases.ticket_id').where(purchases: {ticket_id: nil})
but it seems not to be the right direction.
If you're just trying to get the list of Ticket records with no associated purchases, use .includes instead. In my experience a join will fail with no associated records, and this will keep you from needing to write any actual SQL.
Ticket.includes(:purchase).where(purchases: { ticket_id: nil} )
The generated SQL query is a bit more difficult to read as a human, but I've used it several times and not seen any real difference in performance.
I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.
I am trying to figure out the right way to chain my associations effeciently. I basically have the following hierarchy:
School > Course > Lecture > Attachment
Users can have many of any of these, which indicates not that they own them but rather that they are enrolled in or have access to them. We then use cancan for authorization.
My challenge is that users can be enrolled in a school and then separately enroll in each course, OR they can be enrolled in a "school-wide" package that gives them access to all courses. Before the addition of the latter, our user model includes the following:
belongs_to :school
has_many :active_sales, -> { where active: true }, class_name: "Sale"
has_many :courses, through: :active_sales, source: :sale
has_many :lectures, through: :courses
has_many :attachments, through: :lectures
In trying to add the school-wide sales (sales is polymorphic as "enrollable" with both courses and schools), I can't figure out how to chain the associations together properly. Basically, it's like the chain forks and then rejoins itself. It would be nice if I could do something like
has_many :courses, through: :active_sales, source: :sale, -> {where (id: active_sale.enrollable_id && active_sale.enrollable_type: 'Course') || (school_id: active_sale.enrollable_id && active_sale.enrollable_type: 'School') }
...but I'm pretty sure the where method doesn't take associations and not clear if it takes strung together conditions either. I tried messing with association extensions, but was having trouble there too.
Is there a best way to do this?
Vanilla AtiveRecord can be hard to use in case of complex queries unless you write the SQL statement yourself. The only way to do an OR statement would be to write the whole SQL statement as a string and use it as the where clause.
But you could also use Arel to shape your query instead.
https://github.com/rails/arel
In your case, I think it would make your code cleaner if you created a scope for the active_sales_courses or a class method, instead of trying to put everything in an has_many statement.
def self.active_sales_courses
courses = Arel::Table.new(:courses)
active_sales = Arel::Table.new(:active_sales)
query = courses.joins(active_sales).on(courses[:id].eq(active_sales[:enrollable_id])).
where((courses[:id].eq(active_sales[:enrollable_id]).
and(active_sales[:enrollable_type].eq('Course'))).
or(courses[:school_id].eq(active_sales[:enrollable_id]).
and(active_sales[:enrollable_type].eq('School'))))
query.to_sql
end
I can't guarantee this code works as I have no way of testing it but it is provided as an indication.
CONTEXT:
In my setup Users have many Communities through CommunityUser, and Communities have many Posts through CommunityPost. If follows then, that Users have many Posts through Communities.
User.rb
has_many :community_users
has_many :communities, through: :community_users
has_many :posts, through: :communities
Given the above User.rb, Calling "current_user.posts" returns posts with one or more communities in common with current_user.
QUESTION:
I'm looking to refine that association so that calling "current_user.posts" returns only those posts whose communities are a complete subset of the current_user's communities.
So, given a current_user with community_ids of [1,2,3], calling "current_user.posts" would yield only those posts whose "community_ids" array is either 1, [2], [3], [1,2], [1,3], [2,3], or [1,2,3].
I've been researching scopes here, but can't seem to pinpoint how to accomplish this successfully...
Nice question...
My immediate thoughts:
--
ActiveRecord Association Extension
These basically allow you to create a series of methods for associations, allowing you to determine specific criteria, like this:
#app/models/user.rb
has_many :communities, through: :community_users
has_many :posts, through: :communities do
def in_community
where("community_ids IN (?)", user.community_ids)
end
end
--
Conditional Association
You could use conditions in your association, like so:
#app/models/user.rb
has_many :posts, -> { where("id IN (?)", user.community_ids) }, through: :communities #-> I believe the model object (I.E user) is available in the association
--
Source
I originally thought this would be your best bet, but looking at it more deeply, I think it's only if you want to use a different association name
Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, through: :subscriptions will look
for either :subscribers or :subscriber on Subscription, unless a
:source is given.
Being honest, this is one of those questions which needs some thought
Firstly, how are you storing / calling the community_ids array? Is it stored in the db directly, or is it accessed through the ActiveRecord method Model.association_ids?
Looking forward to helping you further!
You don't show the model and relationship definitions for for Community or CommunityPost so make sure you have a has_many :community_posts and a has_many :posts, through: :community_posts on your Community model and a belongs_to :community and a belongs_to :post on CommunityPost. If you don't need to track anything on ComunityPost you could just use a has_and_belongs_to_many :posts on Community and a join table communities_posts containing just the foreign keys community_id and post_id.
Assuming you have the relationships setup as I describe you should be able to just use current_user.posts to get a relation that can be further chained and which returns all posts for all communities the user is associated with when you call .all on it. Any class methods (such as scope methods) defined your Post model will also be callable from that relation so that is where you should put your refinements unless those refinements pertain to the Community or CommunityPost in which case you would put them on the Community or CommunityPost models respectively. Its actually rare to need an AR relationship extension for scopes since usually you also want to be able to refine the model independently of whatever related model you may use to get to it.
Rails has a has_one :through association that helps set up a one-to-one association with a third model by going through a second model. What is the real use of that besides making a shortcut association, that would otherwise be an extra step away.
Taking this example from the Rails guide:
class Supplier < ActiveRecord::Base
has_one :account
has_one :account_history, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ActiveRecord::Base
belongs_to :account
end
might allow us to do something like:
supplier.account_history
which would otherwise be reached as:
supplier.account.history
If it's only for simpler access then technically there could be a one-to-one association that connects a model with some nth model going through n-1 models for easier access. Is there anything else to it that I am missing besides the shortcut?
Logic, OK it might sound a bit weak for this but it would be logical to say that "I have a supplier who has an account with me, I want to see the entire account history of this supplier", so it makes sense for me to be able to access account history from supplier directly.
Efficiency, this for me is the main reason I would use :through, simply because this issues a join statement rather than calling supplier, and then account, and then account_history. noticed the number of database calls?
using :through, 1 call to get the supplier, 1 call to get account_history (rails automatically uses :join to retrieve through account)
using normal association, 1 call to get supplier, 1 call to get account, and 1 call to get account_history
That's what I think =) hope it helps!
I'm surprised no one has touched on Association Objects.
A has_many (or has_one) :through relationship facilitates the use of the association object pattern which is when you have two things related to each other, and that relation itself has attributes (ie a date when the association was made or when it expires).
This is considered by some to be a good alternative to the has_and_belongs_to_many ActiveRecord helper. The reasoning behind this is that it is very likely that you will need to change the nature of the association or add to it, and when you are a couple months into a project, this can be very painful if the relationship were initially set up as a has_and_belongs_to_many (the second link goes into some detail). If it is set up initially using a has_many :through relationship, then a couple months into the project it's easy to rename the join model or add attributes to it, making it easier for devs to respond to changing requirements. Plan for change.
Inverse association: consider the classic situation user-membership-group. If a user can be a member in many groups, then a group has many members or users, and a user has many groups. But if the user can only be a member in one group, the group still has many members: class User has_one :group, :through => :membership but class Group has_many :members, :through => memberships. The intermediate model membership is useful to keep track of the inverse relationship.
Expandability: a has_one :through relationship can easy be expanded and extended to a has_many :through relationship