model Post
# ActiveRecord associations have tons of options that let
# you do just about anything like:
has_many :comments
has_many :spam_comments, :conditions => ['spammy = ?', true]
# In Rails 3, named scopes are ultra-elegant, and let you do things like:
scope :with_comments, joins(:comments)
end
Is there any way to use AREL, or an otherwise leaner syntax, to define custom associations as elegantly as named scopes?
update
I've decided it's not a good idea to put that sort of detail into an association anyway, because associations should always/mostly define the basic relationships between models.
One of the solutions is to put spammy scope on Comments:
model Post
has_many :comments
scope :with_comments, joins(:comments)
end
model Comment
scope :spammy, where(:spammy => true)
end
This looks a bit cleaner with respect to model responsibilities. Performance-wise it's exactly the same:
p.comments.spammy.to_sql
# → SELECT "comments".* FROM "comments"
# WHERE ("comments".post_id = 2) AND ("comments"."spammy" = "t")
The added benefit: you can get spammy comments from any other associations.
Well, there may be a better way, but I know that you can use actual Arel conditions (as opposed to ActiveRecord::Relations) in associations by using Arel's to_sql feature.
has_many :spam_comments, :class_name => 'Comment', :conditions => Comment.arel_table[:spammy].eq(true).to_sql
You'll notice that actual Arel code isn't as lean as ActiveRecord Relations.
I did find a comment in the Rails master branch that referred to passing an Arel predicate as a condition but that code doesn't seem to be in the 3.0 branch. At least not that I could find.
Related
I have a product model setup like the following:
class Product < ActiveRecord::Base
has_many :product_atts, :dependent => :destroy
has_many :atts, :through => :product_atts
has_many :variants, :class_name => "Product", :foreign_key => "parent_id", :dependent => :destroy
end
And I want to search for products that have associations with multiple attributes.
I thought maybe this would work:
Product.joins(:product_atts).where(parent_id: params[:product_id]).where(product_atts: {att_id: [5,7]})
But this does not seem to do what I am looking for. This does where ID or ID.
So I tried the following:
Product.joins(:product_atts).where(parent_id: 3).where(product_atts: {att_id: 5}).where(product_atts: {att_id: 7})
But this doesn't work either, it returns 0 results.
So my question is how do I look for a model by passing in attributes of multiple join models of the same model type?
SOLUTION:
att_ids = params[:att_ids] #This is an array of attribute ids
product = Product.find(params[:product_id]) #This is the parent product
scope = att_ids.reduce(product.variants) do |relation, att_id|
relation.where('EXISTS (SELECT 1 FROM product_atts WHERE product_id=products.id AND att_id=?)', att_id)
end
product_variant = scope.first
This is a seemingly-simple request made actually pretty tricky by how SQL works. Joins are always just joining rows together, and your WHERE clauses are only going to be looking at one row at a time (hence why your expectations are not working like you expect -- it's not possible for one row to have two values for the same column.
There are a bunch of ways to solve this when dealing with raw SQL, but in Rails, I've found the simplest (not most efficient) way is to embed subqueries using the EXISTS keyword. Wrapping that up in a solution which handles arbitrary number of desired att_ids, you get:
scope = att_ids_to_find.reduce(Product) do |relation, att_id|
relation.where('EXISTS (SELECT 1 FROM product_atts WHERE parent_id=products.id AND att_id=?)', att_id)
end
products = scope.all
If you're not familiar with reduce, what's going on is it's taking Product, then adding one additional where clause for each att_id. The end result is something like Product.where(...).where(...).where(...), but you don't need to worry about that too much. This solution also works well when mixed with scopes and other joins.
I'm sure there's an easy way to do this but have googled myself dry...
I have a model that has a has_many association
Object.rb
has_many :things
I want to basically write
Object.include? :things
And get a query response which contains all the objects that have at least one thing.
Also the same with a has_one..
Any ideas?
Try this:
Using where in Rails 3.2:
# When *has_many* association:
Object.includes(:things).where("things.id IS NOT NULL")
# When *has_one* association:
Object.includes(:thing).where("things.id IS NOT NULL")
Note that, in where clause you need to use the actual table name.
Rails(Active Record) 4.0 and above adds where.not so you can do this:
Object.includes(:things).where.not('things.id' => nil)
# or
Object.includes(:things).where.not(things: {id: nil})
You can try like this too
Object.includes(:things).where("things.id IS NOT NULL")
If I just including nested model in query such this
#projects = current_user.projects.all(include: :reviews)
everything ok. But Review model has some scope, that I need implement in query above. I trying this
#projects = current_user.projects.all(include: :reviews.unreaded)
and gets error. What is the right way to do this?
One option would be to create an association based on the scope, roughly:
#projects = current_user.projects.all(include: :unread_reviews)
Then create an unread_reviews association, roughly:
class Project < ...
has_many :unread_reviews, :conditions => ['read=?', true], :class_name => "Review"
(Replace the above has_many with your association particulars, obviously.)
This technique is discussed in the association docs.
I am a rookie in Thinking Sphinx for Rails.
When Sphinx found a record, it will give all the fields in the table. How can i select only the needed fields?
And in my case, i also need reference to another table. how can i do that?
Thanks
This is an old thread, but as I found it whilst looking for the same information, I thought I'd share my answer.
It's not (as far as I can tell) clearly defined on the Thinking Sphinx homepage, but the search function on a model accepts the option :select - and in answer to your second question, it also accepts :joins, so if you had two related models:
class Project < ActiveRecord::Base
attr_accessible :name
has_many :tasks
end
class Task < ActiveRecord::Base
attr_accessible :name
belongs_to :project
end
You should be able to search your tasks like so:
Task.search "Fix Bug",
:select => 'tasks.id, tasks.name, projects.name as project_name',
:joins => [:project]
There's no doubt a slightly cleaner way to do this, so I'm happy to be corrected - the general idea works though!
EDIT (for thinking sphinx v3)
:select is used as a sphinx parameter in version 3, and instead you should add :select and :joins to a :sql hash. Otherwise you get some really strange errors that aren't that obvious!
The above example then becomes:
Task.search "Fix Bug",
:sql => { :select => 'tasks.id, tasks.name, projects.name as project_name',
:joins => [:project] }
I have a Rails 3 project. With Rails 3 came Arel and the ability to reuse one scope to build another. I am wondering if there is a way to use scopes when defining a relationship (e.g. a "has_many").
I have records which have permission columns. I would like to build a default_scope that takes my permission columns into consideration so that records (even those accessed through a relationship) are filtered.
Presently, in Rails 3, default_scope (including patches I've found) don't provide a workable means of passing a proc (which I need for late variable binding). Is it possible to define a has_many into which a named scope can be passed?
The idea of reusing a named scope would look like:
Orders.scope :my_orders, lambda{where(:user_id => User.current_user.id)}
has_many :orders, :scope => Orders.my_orders
Or implicitly coding that named scope in the relationship would look like:
has_many :orders, :scope => lambda{where(:user_id => User.current_user.id)}
I'm simply trying to apply default_scope with late binding. I would prefer to use an Arel approach (if there is one), but would use any workable option.
Since I am referring to the current user, I cannot rely on conditions that aren't evaluated at the last possible moment, such as:
has_many :orders, :conditions => ["user_id = ?", User.current_user.id]
I suggest you take a look at "Named scopes are dead"
The author explains there how powerful Arel is :)
I hope it'll help.
EDIT #1 March 2014
As some comments state, the difference is now a matter of personal taste.
However, I still personally recommend to avoid exposing Arel's scope to an upper layer (being a controller or anything else that access the models directly), and doing so would require:
Create a scope, and expose it thru a method in your model. That method would be the one you expose to the controller;
If you never expose your models to your controllers (so you have some kind of service layer on top of them), then you're fine. The anti-corruption layer is your service and it can access your model's scope without worrying too much about how scopes are implemented.
How about association extensions?
class Item < ActiveRecord::Base
has_many :orders do
def for_user(user_id)
where(user_id: user_id)
end
end
end
Item.first.orders.for_user(current_user)
UPDATE: I'd like to point out the advantage to association extensions as opposed to class methods or scopes is that you have access to the internals of the association proxy:
proxy_association.owner returns the object that the association is a part of.
proxy_association.reflection returns the reflection object that describes the association.
proxy_association.target returns the associated object for belongs_to or has_one, or the collection of associated objects for has_many or has_and_belongs_to_many.
More details here: http://guides.rubyonrails.org/association_basics.html#association-extensions
Instead of scopes I've just been defining class-methods, which has been working great
def self.age0 do
where("blah")
end
I use something like:
class Invoice < ActiveRecord::Base
scope :aged_0, lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) }
end
You can use merge method in order to merge scopes from different models.
For more details search for merge in this railscast
If you're just trying to get the user's orders, why don't you just use the relationship?
Presuming that the current user is accessible from the current_user method in your controller:
#my_orders = current_user.orders
This ensures only a user's specific orders will be shown. You can also do arbitrarily nested joins to get deeper resources by using joins
current_user.orders.joins(:level1 => { :level2 => :level3 }).where('level3s.id' => X)