class RelatedList < ActiveRecord::Base
extend Enumerize
enumerize :list_type, in: %w(groups projects)
belongs_to :content
has_many :contents, :order => :position
end
I have this model in my rails app which throws warning when I try to create records in console.
DEPRECATION WARNING: The following options in your
RelatedList.has_many :contents declaration are deprecated: :order.
Please use a scope block instead. For example, the following: has_many
:spam_comments, conditions: { spam: true }, class_name: 'Comment'
should be rewritten as the following: has_many :spam_comments, -> {
where spam: true }, class_name: 'Comment'
. (called from at /Users/shivam/Code/auroville/avorg/app/models/related_list.rb:7)
It seems like Rails 4 has new :order syntax for use in models but I can't seem to find the documentation in Rails Guides.
In Rails 4, :order has been deprecated and needs to be replaced with lambda scope block as shown in the warning you've posted in the question. Another point to note is that this scope block needs to be passed before any other association options such as dependent: :destroy etc.
Give this a try:
has_many :contents, -> { order(:position) } # Order by :asc by default
To specify order direction, i.e. either asc or desc as #joshua-coady and #wsprujit have suggested, use:
has_many :contents, -> { order 'position desc' }
or, using the hash style:
has_many :contents, -> { order(position: :desc) }
Further reference on Active Record Scopes for has_many.
It took me a while to figure out how to do order and include, I eventually found that you chain the scope statements,
has_many :things, -> { includes(:stuff).order("somedate desc") }, class_name: "SomeThing"
Just thought I'd add that if you have any option hash arguments, they have to go after the lambda, like this:
has_many :things, -> { order :stuff }, dependent: :destroy
Took me a minute to figure this out myself - hopefully it helps anyone else coming to this question having the same problem.
This works for me with Rails 4 & MongoDB
has_many :discounts, order: :min_amount.asc
Alternatively, you can put the order clause on the model, for instance:
has_many :options, order: 'name' # In class Answer
Becomes
has_many :options # In class Answer
default_scope { order 'name' } # In class Option
PS: I got ArgumentError: wrong number of arguments (1 for 0) when doing has_many :things, -> {}.
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 am trying to build an active record query using through table associations. Here are my models:
Event.rb:
has_many :event_keywords
User.rb:
has_many :user_keywords
Keyword.rb:
has_many :event_keywords
has_many :user_keywords
EventKeyword.rb:
belongs_to :event
belongs_to :keyword
UserKeyword.rb:
belongs_to :user
belongs_to :keyword
I am trying to build an Event scope that takes a user_id as a param and returns all the Events with shared keywords. This was my attempt but it's not recognizing the user_keywords association:
scope :with_keywords_in_common, ->(user_id) {
joins(:event_keywords).joins(:user_keywords)
.where("user_keywords.user_id = ?", user_id)
.where("event_keywords.keyword_id = user_keywords.keyword_id")
}
How do I resolve this?
Something like this might work. 2-step process. First, get all user's keywords. Then find all events with the same keyword.
scope :with_keywords_in_common, ->(user) {
joins(:event_keywords).
where("event_keywords.keyword_id" => user.user_keywords.pluck(:id))
}
The database seems to be overkill here and firstly I'd simplify by making keywords polymorphic, this would get rid of 2 of your tables here (event_keywords, and user_keywords).
Your associations would then look like this:
# Event.rb:
has_many :keywords, as: keywordable
# User.rb:
has_many :keywords, as: keywordable
# Keyword.rb:
belongs_to :keywordable, polymorphic: true
And finally, your scope:
scope :with_keywords_in_common, -> (user_id) do
joins(:keywords)
.where('keywords.keywordable_type = User AND keywords.word IN (?)', keywords.pluck(:name))
end
class RelatedList < ActiveRecord::Base
extend Enumerize
enumerize :list_type, in: %w(groups projects)
belongs_to :content
has_many :contents, :order => :position
end
I have this model in my rails app which throws warning when I try to create records in console.
DEPRECATION WARNING: The following options in your
RelatedList.has_many :contents declaration are deprecated: :order.
Please use a scope block instead. For example, the following: has_many
:spam_comments, conditions: { spam: true }, class_name: 'Comment'
should be rewritten as the following: has_many :spam_comments, -> {
where spam: true }, class_name: 'Comment'
. (called from at /Users/shivam/Code/auroville/avorg/app/models/related_list.rb:7)
It seems like Rails 4 has new :order syntax for use in models but I can't seem to find the documentation in Rails Guides.
In Rails 4, :order has been deprecated and needs to be replaced with lambda scope block as shown in the warning you've posted in the question. Another point to note is that this scope block needs to be passed before any other association options such as dependent: :destroy etc.
Give this a try:
has_many :contents, -> { order(:position) } # Order by :asc by default
To specify order direction, i.e. either asc or desc as #joshua-coady and #wsprujit have suggested, use:
has_many :contents, -> { order 'position desc' }
or, using the hash style:
has_many :contents, -> { order(position: :desc) }
Further reference on Active Record Scopes for has_many.
It took me a while to figure out how to do order and include, I eventually found that you chain the scope statements,
has_many :things, -> { includes(:stuff).order("somedate desc") }, class_name: "SomeThing"
Just thought I'd add that if you have any option hash arguments, they have to go after the lambda, like this:
has_many :things, -> { order :stuff }, dependent: :destroy
Took me a minute to figure this out myself - hopefully it helps anyone else coming to this question having the same problem.
This works for me with Rails 4 & MongoDB
has_many :discounts, order: :min_amount.asc
Alternatively, you can put the order clause on the model, for instance:
has_many :options, order: 'name' # In class Answer
Becomes
has_many :options # In class Answer
default_scope { order 'name' } # In class Option
PS: I got ArgumentError: wrong number of arguments (1 for 0) when doing has_many :things, -> {}.
I'm re-writing a rails 1.x app I wrote many years ago and am running into problems filtering the activerecord with rails 4.
Models:
Glaze:
has_many :ingredients, dependent: :destroy
has_many :materials, through: :ingredients
Ingredient:
belongs_to :glaze
belongs_to :material
Material:
has_many :ingredients
has_many :glazes, through: :ingredients
There is a boolean checkbox for each material to indicate if it is a colorant or not. I want to filter the materials that are not colorant for each glaze record.
In my old rail 1.x app I used the code:
#batchingredients = #glaze.ingredients.find(:all, :order => 'amt DESC', :include => :material, :conditions => ['materials.colorant = ?', false])
I cannot get it to pass the non-colorant materials through. I am also unsure if this is a task I should make a scope for (I'm not a programmer and I don't think scopes were around the last time I worked with Rails).
probably best to create a scope on your ingredient model:
scope :without_colorant, -> { joins(:material).where(materials: { colorant: false }) }
then your controller would look like this:
#batchingredients = #glaze.ingredients.without_colorant.order(amt: :desc)
I'm having trouble getting a belongs_to association to eager load it's children. I have:
class User < ActiveRecord::Base
has_many :campaigns, -> { includes :campaign_shirts, :arts, :selected_campaign_shirt }
belongs_to :selected_campaign, {class_name: "Campaign", inverse_of: :user}, -> { includes :campaign_shirts, :arts, :selected_campaign_shirt }
end
which results in:
// GOOD
u.campaigns.first.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fc023a9abb0>"
u.campaigns.first.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fc023a9abb0>"
// NOT GOOD
u.selected_campaign.campaign_shirts.first.to_s
(queries db)
=> "#<CampaignShirt:0x007fc023d7c630>"
(queries db)
u.selected_campaign.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fc01af528a0>"
Am I running afoul of this issue? Is there a way to achieve what I want, which is to be able to refer to current_user.selected_campaign and have eager-loaded/frozen current_user.selected_campaign.campaign_shirts.first etc.?
Try moving the lambda scope before other association options like follows:
# app/models/users.rb
belongs_to :selected_campaign, -> { includes :campaign_shirts, :arts, :selected_campaign_shirt }, {class_name: "Campaign", inverse_of: :user},