class Message
has_many :threads, :class_name=>"Message", :conditions => "`#{Message.table_name}`.conversation_id = #{self.send(:conversation_id)}"
end
m = Message.first
NoMethodError: undefined method `conversation_id' for #<Class:0xc5021dc>
I even tried with single quote:
class Message
has_many :threads, :class_name=>"Message", :conditions => '`#{Message.table_name}`.conversation_id = #{self.send(:conversation_id)}'
end
m = Message.first
m.threads
This gave me Mysql::Error: You have an error in your SQL syntax
It seems it's not considering the #{...} thing while generating the condition sql
i could do it with scopes
scope :threads, lambda {|conv_id| where(:conversation_id => conv_id) }
and access it Message.where("some condition").threads()
but am looking for a neat association like
m = Message.find(1000)
m.threads should give all the conversation threads which it belongs to
You cannot use dynamic conditions in has_many. However, in your particular case it seems you need primary_key and foreign_key instead:
class Message
has_many :threads, :class_name=>"Message", :primary_key => 'conversation_id', :foreign_key => 'conversation_id'
end
You may also be interested by one of the gems that adds tree structure to ActiveRecord.
Related
In Rails 5, given a relationship between two tables that involves joining them on multiple shared attributes, how can I form an association between the models corresponding to these tables?
SQL:
SELECT *
FROM trips
JOIN stop_times ON trips.guid = stop_times.trip_guid AND trips.schedule_id = stop_times.schedule_id
I tried the following configuration, which works in general...
class Trip < ApplicationRecord
has_many :stop_times, ->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) }, :inverse_of => :trip, :primary_key => :guid, :foreign_key => :trip_guid, :dependent => :destroy
end
class StopTime < ApplicationRecord
belongs_to :trip, :inverse_of => :stop_times, :primary_key => :guid, :foreign_key => :trip_guid
end
Trip.first.stop_times.first #> StopTime object, as expected
Trip.first.stop_times.first.trip #> Trip object, as expected
... but when I try to use it in more advanced queries, it triggers ArgumentError: The association scope 'stop_times' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported....
Trip.joins(:stop_times).first #=> the unexpected ArgumentError
StopTime.joins(:trip).first #> StopTime object, as expected
I understand what the error is referencing, but I'm unsure of how to fix it.
EDIT:
I was hoping a single association would be sufficient, but it has been noted two different associations can do the job:
class Trip < ApplicationRecord
has_many :stop_times,
->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) },
:primary_key => :guid,
:foreign_key => :trip_guid # use trip.stop_times instead of trip.joined_stop_times to avoid error about missing attribute due to missing join clause
has_many :joined_stop_times,
->{ where("stop_times.schedule_id = trips.schedule_id") },
:class_name => "StopTime",
:primary_key => :guid,
:foreign_key => :trip_guid # use joins(:joined_stop_times) instead of joins(:stop_times) to avoid error about instance-specific association
end
Trip.first.stop_times
Trip.eager_load(:joined_stop_times).to_a.first.joined_stop_times # executes a single query
If anyone reading this knows how to use a single association, please at-mention me.
I don't think it is the right solution, but it can help. You can add another similar instance independent association that will be used for preloading only. It will work with :joins and :eager_load but not with :preload.
Note that :includes might internally use either :eager_load or :preload. So, :includes will not always work with that association. You should explicitly use :eager_load instead.
class Trip < ApplicationRecord
has_many :preloaded_stop_times,
-> { where("stop_times.schedule_id = trips.schedule_id") },
class_name: "StopTime",
primary_key: :guid,
foreign_key: :trip_guid
end
# Usage
trips = Trip.joins(:preloaded_stop_times).where(...)
# ...
# with :eager_load
trips = Trip.eager_load(:preloaded_stop_times)
trips.each do |trip|
stop_times = trip.preloaded_stop_times
# ...
end
I have some model classes like this:
class Organisation < ActiveRecord::Base
has_many :dongles
has_many :licences_on_owned_dongles, :through => :dongles, :source => :licences,
:include => [:organisation, :user, :owner_organisation, :profile, :dongle,
{:nested_licences => [:profile]} ]
end
class Dongle < ActiveRecord::Base
has_many :licences
belongs_to :organisation
end
class Licence < ActiveRecord::Base
belongs_to :dongle
# tree-like structure. I don't remember why this had to be done but the comment says
# "find a way to make the simpler way work again" and I tried using the simpler way
# but tests still fail. So obviously the SQL awfulness is necessary...
default_scope :conditions => { :parent_licence_id, nil }
has_many :nested_licences, :class_name => 'Licence', :dependent => :destroy,
:autosave => true,
:foreign_key => :parent_licence_id,
:finder_sql => proc {
"SELECT l.* FROM licences l WHERE l.parent_licence_id = #{id}" },
:counter_sql => proc {
"SELECT COUNT(*) FROM licences l WHERE l.parent_licence_id = #{id}" }
end
Now I can do this:
test "getting licences on owned dongles" do
org = organisations(:some_other_corp)
assert_equal [licences(:licence_4)], org.licences_on_owned_dongles
end
That happily passes. Since it's an association, you might thing you can find() on it:
test "getting licences on owned dongles and then filtering further" do
org = organisations(:some_other_corp)
conditions = { :owner_organisation_id => nil }
assert_equal [licences(:licence_4)],
org.licences_on_owned_dongles.find(:all, :conditions => conditions)
end
But this gives:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: dongles.organisation_id: SELECT "licences".* FROM "licences" WHERE "licences"."parent_licence_id" IS NULL AND (("dongles".organisation_id = 72179513)) AND ("licences".parent_licence_id = 747059259)
test/unit/organisation_test.rb:123:in `test_getting_licences_on_owned_dongles_and_then_filtering_further'
In fact, this even occurs when all you call is find(:all). It isn't just SQLite either, because I noticed this in production (oops) on MySQL.
So I don't know. It's really too mysterious to investigate further. I might shelve it as a "Rails just can't do find() on an association", use a block to filter it and leave it at that. But I wanted to put it out, just in case there is a better option.
(Actually if you look at the query Rails is generating, it is complete nonsense. Somehow it has ended up generating a query where something has to be NULL and equal to a value at the same time. Even if the query worked, this will return 0 rows.)
Don't use find in a Rails 3 app.
org.licences_on_owned_dongles.find(:all, :conditions => conditions)
should be
org.licences_on_owned_dongles.where(conditions)
Edit: Read up on it here.
I think you're looking for .where:
org.licenses_on_owned_dongles.where(conditions)
I'm implementing a method, that will be used in many places of a project.
def do association
end
"association" is a symbol, like :articles, :tags, :users etc.
When the association is :articles, I need to work with the Article model.
When the association is :users, I need to work with the User model.
Etc.
I know, that I can write a helper method, that returns model class, depending on the provided symbol. But is there a ready to use method for that?
Rails provides a method called classify on the String class for such purpose.
:users.to_s.classify.constantize
#User
:line_items.to_s.classify.constantize
#LineItem
Edit:
If you are trying to retrieve the class associated with an association, use this approach:
Author.reflect_on_association(:books).klass
# => Book
This will address the scenario where the association name doesn't match the class name.
E.g:
class Order
has_many :line_items
has_many :active_line_items, :class_name => "LineItem",
:conditions => {:deleted => false}
end
In the example above, :active_line_items will result in ActiveLineItem and our original code will throw error.
Read more about this here.
This will work
(:users.to_s.singularize.capitalize.constantize).find :all, :conditions => ["name = ?", "john"]
And with your example
association.to_s.singularize.capitalize.constantize
I am trying to retrieve an activerecord object from my db. My models are
class User < ActiveRecord::Base
belongs_to :account
has_many :domains, :through => :account
end
And
class Account < ActiveRecord::Base
has_many :domains
has_many :users
end
And
class Domain < ActiveRecord::Base
belongs_to :account
end
Now I would like to retrieve a user based on the username and a domain name (lets assume that these are attributes of the User and the Domain classes respectively). i.e. something along the lines of
User.find(:first, :conditions =>{:username => "Paul", :domains => { :name => "pauls-domain"}})
I know that the above piece of code will not work since I do have to mention something about the domains table. Also, the association between users and domains is a one-to-many (which probably further complicates things).
Any ideas on how should this query be formed?
If you're using Rails 3.x, the following code would get the query result:
User.where(:username => "Paul").includes(:domains).where("domains.name" => "paul-domain").limit(1)
To inspect what happen, you can append .to_sql to above code.
If you're using Rails 2.x, you'd better write the raw sql query.
The following piece of code did the trick:
User.joins(:account).joins('INNER JOIN "domains" ON "accounts"."id" = \
"domains"."account_id"').where(:users => {"username" => "Paul"}).
where(:domains => {"name" => "paul-domain"})
Sorry about the formatting of this long line of code
I have two models, user and group. I also have a joining table groups_users.
I have an association in the group model:
has_many :groups_users
has_many :users, :through=> :groups_users
I would like to add pending_users which would be the same as the users association but contain some conditions. I wish to set it up as an association so that all the conditions are handled in the sql call. I know there's a way to have multiple accessors for the same model, even if the name is not related to what the table names actually are. Is it class_name?
Any help would be appreciated, thanks
Use named_scopes, they're your friend
Have you tried using a named_scope on the Group model?
Because everything is actually a proxy until you actually need the data,
you'll end up with a single query anyway if you do this:
class User < ActiveRecord::Base
named_scope :pending, :conditions => { :status => 'pending' }
and then:
a_group.users.pending
Confirmation
I ran the following code with an existing app of mine:
Feature.find(6).comments.published
It results in this query (ignoring the first query to get feature 6):
SELECT *
FROM `comments`
WHERE (`comments`.feature_id = 6)
AND ((`comments`.`status` = 'published') AND (`comments`.feature_id = 6))
ORDER BY created_at
And here's the relevant model code:
class Feature < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :feature
named_scope :published, :conditions => { :status => 'published' }
This should be pretty close - more on has_many.
has_many :pending_users,
:through => :groups_users,
:source => :users,
:conditions => {:pending => true}
:pending is probably called something else - however you determine your pending users. As a side note - usually when you see a user/group model the association is called membership.
In the User model:
named_scope :pending, :include => :groups_users, :conditions => ["group_users.pending = ?", true]
That's if you have a bool column named "pending" in the join table group_users.
Edit:
Btw, with this you can do stuff like:
Group.find(id).users.pending(:conditions => ["insert_sql_where_clause", arguments])