I'm working on my first rails project, I'm stuck trying to get two of my models to work together. Here are my models:
class Ecn < ActiveRecord::Base
has_many :revisions, :dependent => :destroy
has_many :drawings, through: :revisions
accepts_nested_attributes_for :revisions, :reject_if => lambda { |attrs| attrs.all? { |key, value| value.blank? }}, :allow_destroy => true
belongs_to :user
class Drawing < ActiveRecord::Base
has_many :revisions
class Revision < ActiveRecord::Base
belongs_to :drawing, foreign_key: :drawing_number
belongs_to :ecn
What I am trying to achieve is a search for Ecns that uses the revision foreign_key :drawing_number. When I create an Ecn, I add multiple revisions to the Ecn, which have a field for :drawing_number, which is a property of Drawings. I have an Ecn search form, which has multiple fields to search for, one of which is :drawing_number. I would like the query to find all Revisions that include the given :drawing_number, and find the Ecns that include those revisions.
The scope I have in my Ecn model looks like this:
scope :by_drawing_number, lambda { |drawing_number| Ecn.joins(:drawings).where("drawings.drawing_number LIKE ?", "%#{drawing_number}%") unless drawing_number.nil? }
This scope does not throw any errors, but the search comes up with zero results.
Following should be possible:
scope :by_drawing_number, lambda { |drawing_number| Revision.where("drawing_number LIKE ?", "%#{drawing_number}%").map(&:ecn)
This will return a collection of Ecns.
Related
I'm working on a podcast player and wish to display a list of recently updated feeds, along with details of the play time remaining for the most recently published entry.
So the view looks something like:
#feeds.each do |f|
puts #feed.rss_image.url
puts #feed.most_recent_entry.published_at
if play = #feed.most_recent_entry.most_recent_play_by(#user)
puts play.remaining
end
end
My models are as follows:
class Feed < ApplicationRecord
has_one :rss_image, as: :rss_imageable
has_many :entries, dependent: :destroy
has_one :most_recent_entry, -> { order(published_at: :desc) }, class_name: "Entry"
has_many :plays, dependent: :destroy
end
class Entry < ApplicationRecord
belongs_to :feed, touch: true
has_many :plays, dependent: :destroy
has_one :most_recent_play, -> { order(updated_at: :desc) }, class_name: "Play"
def most_recent_play_by(user)
plays.by(user).order(updated_at: :desc).first
end
end
class Play < ApplicationRecord
belongs_to :entry
belongs_to :feed
belongs_to :user
scope :by, ->(user) { where(user: user) }
def self.most_recent_by(user)
by(user).order(updated_at: :desc).first
end
end
My query is:
#feeds = Feed
.joins(:entries)
.includes(:rss_image, most_recent_entry: :most_recent_play)
.where(most_recent_entry: {plays: {user: #user}})
.group(:id)
.order("max(entries.published_at) DESC")
.limit(10)
But this errors with:
PG::GroupingError: ERROR: column "rss_images.id" must appear in the GROUP BY clause or be used in an aggregate function
Is it possible to achieve this without N+1 queries?
Thanks!
Take a look to Bullet gem, it helps to reduce the number of queries and eliminate n+1. In this case it should suggest you how to modify you query, eg. adding .includes(:entries) ....
Statically-typed, compile-time-checked programmer brain, here, struggling with a rails function call.
In a model when we specify has_many, we can specify the sort order like this has_many :requirements, -> { order(created_at: :asc) }, :dependent => :destroy.
But when we have a polymorphic association such as this
has_many :text_fields, as: :describable, :dependent => :destroy how can we add the sorting lambda?
I have tried every possible permutation of syntax except, apparently, the correct one.
You have clarified that it is the child records (text_fields) that you want ordered. You could use a default_scope, like so:
class ParentModel < ApplicationRecord
has_many :text_fields, as: :describable, :dependent => :destroy
end
class TextField < ApplicationRecord
default_scope { order(created_at: :asc) }
end
But think hard before doing so because default_scope is evil. Among other things, you can't override an order that is defined in a default_scope. I am not familiar with requirements, but you may not be able to override the order defined within requirements either.
I think you are much better served creating a scope like so:
class ParentModel < ApplicationRecord
has_many :text_fields, as: :describable, :dependent => :destroy
end
class TextField < ApplicationRecord
scope :default_order, -> { order(created_at: :asc) }
end
Then call it explicitly when you want your records ordered that way:
TextField.all.default_order
or
parent_record.text_fields.default_order
I have the following models:
class Publication < ActiveRecord::Base
has_many :reviews
has_many :users, :through => :owned_publications
has_many :owned_publications
end
class User < ActiveRecord::Base
has_many :publications, :through => :owned_publications
has_many :owned_publications
end
class OwnedPublication < ActiveRecord::Base
belongs_to :publication
belongs_to :user
has_one :review, :conditions => "user_id = #{self.user.id} AND publication_id = #{self.publication.id}"
end
In the third model, I'm trying to set a condition with a pair of variables. It seems like the syntax works, except that self is not an instance of OwnedPublication. Is it possible to get the current instance of OwnedPublication and place it into a condition?
The solution requires the use of :through and :source options, as well as a proc call:
has_one :review, :through => :publication, :source => :reviews,
:conditions => proc { ["user_id = ?", self.user_id] }
Proc is the trick to passing in dynamic variables to ActiveRecord association conditions, at least as of Rails 3.0. Simply calling:
has_one :conditions => proc { ["publication_id = ? AND user_id = ?",
self.publication_id, self.user_id] }
will not work, though. This is because the association will end up searching the reviews table for a 'reviews.owned_publication_id' column, which does not exist. Instead, you can find the proper review through publication, using publication's :reviews association as the source.
I think your best bet is to just have the Review record belong_to an OwnedPublication, and setup your Publication model to get the reviews via a method:
def reviews
review_objects = []
owned_publications.each do |op|
review_objects << op
end
review_objects
end
Might be a more efficient way if you use a subquery to get the information, but it removes the concept of having unnecessary associations.
I have the following polymorphic associations:
class Invite < ActiveRecord::Base
belongs_to :inviteable, :polymorphic => true
class Event < ActiveRecord::Base
has_many :invites, :as => :inviteable, :dependent => :destroy
class Team < ActiveRecord::Base
has_many :invites, :as => :inviteable, :dependent => :destroy
I'm trying to create scopes on Invites specific to events, and to future events:
(in Invite.rb)
scope :to_events, where('inviteable_type = ?', "Event")
scope :to_future_events, lambda { self.to_events.joins("join events").where('events.starttime > ?', Time.now) }
The to_events scope works fine; the to_future_events returns the correct subset of invites, but it returns many copies of each. Any idea why? Is there a cleaner/Rails-ier way to accomplish what i'm trying to accomplish?
You have to provide an ON statement for the join:
scope :to_future_events, lambda { self.to_events.joins("join events on events.id = invites.inviteable_id").where('events.starttime > ?', Time.now) }
Otherwise it joins every invite record with every events record.
I have two models in a has_many relationship such that Log has_many Items. Rails then nicely sets up things like: some_log.items which returns all of the associated items to some_log. If I wanted to order these items based on a different field in the Items model is there a way to do this through a similar construct, or does one have to break down into something like:
Item.find_by_log_id(:all,some_log.id => "some_col DESC")
There are multiple ways to do this:
If you want all calls to that association to be ordered that way, you can specify the ordering when you create the association, as follows:
class Log < ActiveRecord::Base
has_many :items, :order => "some_col DESC"
end
You could also do this with a named_scope, which would allow that ordering to be easily specified any time Item is accessed:
class Item < ActiveRecord::Base
named_scope :ordered, :order => "some_col DESC"
end
class Log < ActiveRecord::Base
has_many :items
end
log.items # uses the default ordering
log.items.ordered # uses the "some_col DESC" ordering
If you always want the items to be ordered in the same way by default, you can use the (new in Rails 2.3) default_scope method, as follows:
class Item < ActiveRecord::Base
default_scope :order => "some_col DESC"
end
rails 4.2.20 syntax requires calling with a block:
class Item < ActiveRecord::Base
default_scope { order('some_col DESC') }
end
This can also be written with an alternate syntax:
default_scope { order(some_col: :desc) }
Either of these should work:
Item.all(:conditions => {:log_id => some_log.id}, :order => "some_col DESC")
some_log.items.all(:order => "some_col DESC")
set default_scope in your model class
class Item < ActiveRecord::Base
default_scope :order => "some_col DESC"
end
This will work
order by direct relationship has_many :model
is answered here by Aaron
order by joined relationship has_many :modelable, through: :model
class Tournament
has_many :games # this is a join table
has_many :teams, through: :games
# order by :name, assuming team has this column
def teams
super.order(:name)
end
end
Tournament.first.teams # are returned ordered by name
For anyone coming across this question using more recent versions of Rails, the second argument to has_many has been an optional scope since Rails 4.0.2. Examples from the docs (see scopes and options examples) include:
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
As previously answered, you can also pass a block to has_many. "This is useful for adding new finders, creators and other factory-type methods to be used as part of the association." (same reference - see Extensions).
The example given there is:
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
In more modern Rails versions the OP's example could be written:
class Log < ApplicationRecord
has_many :items, -> { order(some_col: :desc) }
end
Keep in mind this has all the downsides of default scopes so you may prefer to add this as a separate method:
class Log < ApplicationRecord
has_many :items
def reverse_chronological_items
self.items.order(date: :desc)
end
end