can I add an includes extension to a belongs_to association? - ruby-on-rails

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},

Related

has_many through => find not matching records

I want to be able to find unpopulated hives, but don't find any solution.
Can you help me please ?
The goal is to be able to do Hive.unpopulated
The main problem is the most_recent, butins ok for me to work with a raw SQL, but I don't find the right query.
Here are my classes :
class Hive < ApplicationRecord
has_many :moves, dependent: :destroy
has_many :yards, through: :moves
has_many :populations, -> { where(:most_recent => true) }
has_many :colonies, through: :populations
validates :name, uniqueness: true
def hive_with_colony
"#{name} (colony #{if self.colonies.count > 0 then self.colonies.last.id end})"
end
def self.populated
Hive.joins(:populations)
end
def self.unpopulated
end
end
class Population < ApplicationRecord
belongs_to :hive
belongs_to :colony
after_create :mark_most_recent
before_create :mark_end
class Colony < ApplicationRecord
has_many :populations, -> { where(:most_recent => true) }
has_many :hives, through: :populations
has_many :visits
has_many :varroas
has_many :most_recents_populations, -> { where(:most_recent => true) }, :class_name => 'Population'
scope :last_population_completed, -> { joins(:populations).where('populations.most_recent=?', true)}
I think you can do a simple query to select Hives which are not in populated list, so:
def self.unpopulated
where.not(id: populated.select(:id))
end
Another option is a LEFT OUTER JOIN and picking the lines that have no population id set on the right side.
def self.unpopulated
left_outer_joins(:populations).where(populations: { id: nil })
end
It depends on your data if Thanh's version (which compares a potentially huge list of ids) or this version (which makes a sightly more complex join but doesn't need to compare against a list of ids) is more performant.

Ruby apply association on each item of collection

I am trying to access specific objects through associations applied one after the other on a collection. For example, one of my database request would be :
get_current_user.readable_projects.cards.find(params[:card_id]).tasks
get_current_user returns a unique User, readable_projects a collection of projects that this user can read. So far, so good. However, each project has many cards, and I'd like to retrieve all the cards from each project I have in the collection, so that in this result I can do a find for a specific card, and then retrieve its tasks, the association between Card and Task being also a has_many.
I could iterate through the projects with a each, but the problem is I want to use the default behavior of a find, so that if in all the cards from all the projects I don't find the one I was looking for, the default RecordNotFound routine is triggered. I could also use find_by and raise the exception manually, doing something like that :
#projects = get_current_user.readable_projects
#projects.each do |p|
#found = p.cards.find_by(id: params[:card_id])
break if #found.present?
end
if #found.present?
#tasks = #found.tasks
else
raise ActiveRecord::RecordNotFound
end
However my main objective is to get this card in a way anyone reading the code could easily understand what I am doing here.
All my model relationships are what follow :
User.rb :
has_many :reader_projects, -> { where "memberships.status = #{Membership::ACTIVE} AND memberships.role_id >= #{Membership::READER} " },
through: :memberships, :class_name => 'Project', :source => :project
has_many :contributor_projects, -> { where "memberships.status = #{Membership::ACTIVE} AND memberships.role_id >= #{Membership::CONTRIBUTOR} " },
through: :memberships, :class_name => 'Project', :source => :project
has_many :admin_projects, -> { where "memberships.status = #{Membership::ACTIVE} AND memberships.role_id >= #{Membership::ADMIN} " },
through: :memberships, :class_name => 'Project', :source => :project
def readable_projects
self.reader_projects + self.contributable_projects
end
def contributable_projects
self.contributor_projects + self.administrable_projects
end
def administrable_projects
self.admin_projects
end
Project.rb :
has_many :cards, inverse_of: :project, dependent: :destroy
Card.rb :
has_many :tasks, inverse_of: :card, dependent: :destroy
My question is : is there a way to do such kind of request in one very understandable line ?
Thank you in advance for your help.

Rails: Ordering comments in a post [duplicate]

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, -> {}.

Deprecated warning for Rails 4 has_many with order

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, -> {}.

ActiveRecord query, joins (Rails 3.1)

How would I go about getting a JSON object with
[
{
Property.field_1,
Property.field_n,
PropAssignmConsumer.field_1,
PropAssignmConsumer.field_n
},
{
Property.field_1,
Property.field_n,
PropAssignmConsumer.field_1,
PropAssignmConsumer.field_n
},
...,
{
Property.field_1,
Property.field_n,
PropAssignmConsumer.field_1,
PropAssignmConsumer.field_n
}
]
sorted by some key (can be a field in either Property or PropAssignmConsumer) for a given user_entity object? i.e. get all properties linked to a given consumer/user_entity, extracting fields from both properties and prop_assignm_consumers, sorting by a field in the properties or prop_assignm_consumer table.
These are my models:
class Property < ActiveRecord::Base
has_many :prop_assignm_consumers, :dependent => :restrict
end
class PropAssignmConsumer < ActiveRecord::Base
belongs_to :consumer
belongs_to :property
end
class Consumer < UserEntity
has_many :prop_assignm_consumers, :dependent => :destroy
has_many :properties, :through => :prop_assignm_consumers
end
I am currently doing
properties = user_entity.properties.find(:all, :order => "#{sort_key} #{sort_ord}")
properties.each do |p|
a = p.prop_assignm_consumers.find_by_consumer_id(current_user.user_entity.id)
... do something with a and p....
end
but this seems inefficient....
Any help would be appreciated.
Maybe I'm missing something. Why doesn't your property also reference consumers? You have many to many, you just didn't complete it. Just adding has_many :consumers, :through => :prop_assignm_consumer would then let you do
properties = user_entity_properties.all(:include => :consumers)
properties.each do |p|
p.consumers.where(:id => current_user.user_entity.id)
end
Though now that we write that, and given that you're doing find_by and not find_all_by, it's pretty clear there's going to be only 1. So you can go the other way.
consumer = Consumer.where(:id => current_user.user_entity.id).includes(:properties).first
consumer.properties.each do |p|
... do something with p and consumer
end
ref
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Resources