Using OR with queries in a scope - ruby-on-rails

In Rails3 I have:
Class Teacher
# active :boolean
has_and_belongs_to_many :subjects
Class Subject
# active :boolean
has_and_belongs_to_many :teachers
I am trying to construct a Teacher scope that returns all Teachers that are active or are associated with a Subject that is active.
These scopes work individually, but how to combine them as a single scope with an OR?
scope :active_teachers, where(active: true)
scope :more_active_teachers, joins(:subjects).where(:subjects => {active: true})
I've tried this without success:
scope :active_teachers, where(active: true).or(joins(:subjects)
.where(:subjects => {active: true}))
UPDATE:
I thought I had a solution, but this no longer lazy loads, hits the database twice and — most importantly — returns an array rather than an AR object!
scope :active_teachers, where(active: true) |
joins(:subjects).where(:subjects => {active: true})

You have Squeel to your rescue. More details here.
Using that, you could define something like:
class Teacher
...
scope :active_teachers, joins{subjects}.where {(active == true) | (subjects.active == true)}
...
end

I think the short answer is you can't.
Oring in the code is going to break lazy loading ... really no way around it as you need the database to make the evaluation. ActiveRecord can't make the evaluations on the scopes without executing each subclause individually.
Something like this the following should work:
joins(:subjects).where("subjects.active = true OR teachers.active = true")
Not quite as elegant, but can be wrapped into a method for reuse.

You can solve this by dropping to AREL. See this SO Question for how to do that.
AREL OR Condition
Or from the AREL Source Code README.md. I think (but haven't verified) that this would translate to the following for your particular example.
teachers.where(teachers[:active].eq(true).or(subjects[:active].eq(true)))
Good Luck!

There's a rails pull request for this (https://github.com/rails/rails/pull/9052), but in the meantime, some one has created a monkey patch that you can include in your initializers that will allow you to do this and still give you an ActiveRecord::Relation:
https://gist.github.com/j-mcnally/250eaaceef234dd8971b
With that, you'll be able to OR your scopes like this
Teacher.active_teachers.or.more_active_teachers
or write a new scope
scope :combined_scopes, active_teachers.or.more_active_teachers

Related

Rails scope with a collection

I work on a project and I bumb into a scope use that I didn't know.
I have those two definitions :
Class Mymodel
scope :active, where({ active: true })
Class SecondModel
has_many :mymodel
And then I used them like :
instance_var = SecondModel.new
instance_var.mymodels.active
This thing actually works my question is if it's a good way of doing it and
how does it work since the scope is equivalent with a class method ?
I use mongoid for the database part.
It works because
instance_var.mymodels can be a Mongoid::Criteria instance.
So it can accept all scopes of Mymodel.
Now the practical difference between scopes and class methods in ActiveRecord (and as far as I remember the same goes for Mongoid) is that scopes must return a relation.
There is no problem in using relations and scopes that way, actually it is very logical and it is the way we normally use them.

HABTM relation find all records, excluding some based on association

I've looked at some of the similar SO posts relating to this but I'm struggling to get my head around it.
I have a habtm relation between Projects and Users. I'm trying to find all the Projects that a particular user does not belong to but I don't know how.
I've tried this sort of thing:
Project.where('project_id != ?', user.id)
But it's also obviously wrong.
I'm using rails 3.2.x
Many of the answers relating to this mention scopes but I haven't come across them before (I'm still very new to Rails).
I just found this post with one answer suggesting: Project.where('id not in (?)', user.projects)
which seems to work, except when user.projects is empty. I'm trying Project.where('id not in (?)', (d.projects.empty? ? '', d.projects))
as is suggested in JosephCastro's answer comment thread but it's giving me a syntax error on the second d.projects.
Edit
Project model snippet that relates to Users
class Project < ActiveRecord::Base
attr_accessible ...
has_and_belongs_to_many :users, :before_add => :validates_unique
and then
class User < ActiveRecord::Base
attr_accessible ...
has_and_belongs_to_many :projects
You can place a scope in your Project model like so:
scope :not_belonging_to, lambda {|user| joins(:projects_users).where('projects_users.user_id <> ?', user.id) }}
This assumes your join table name matches rails convention for HABTM associations
To then get projects that a user doesn't belong to, first find your user, then pass them to the scope like so:
#user = User.find(params[:id]) # example
#unowned_projects = Project.not_belonging_to(#user)
On reflection, that scope won't work as it will find projects that have more than one developer, if one of those is your guy.
Instead, use the following:
scope :not_belonging_to, lambda {|user| where('id NOT IN (?)', user.projects.empty? ? '' : user.projects) }
From Matt's reply above, which was extremely helpful.
I had trouble with this for a while. I attempted to use the following:
scope :not_belonging_to, lambda {|developer| where('id NOT IN (?)', developer.projects.empty? ? '' : developer.projects) }
But I received the following error:
SQLite3::SQLException: only a single result allowed for a SELECT that is part of an expression:
I found I needed to update the scope, adding .ids on the end. See below:
scope :not_belonging_to, lambda {|developer| where('id NOT IN (?)', developer.projects.empty? ? '' : developer.projects.ids) }

Scope for multiple models

I have several objects that all have an approved field.
What would be the best way to implement a scope to use across all models?
For example, I have a sighting object and a comment object. They both have to be approved by an admin before being availalbe to the public.
So how can I create a scope that returns comment.approved as well as sighting.approved respectively without repeating it on each model? Is this where concerns comes into play?
While just declaring a scope in each model is fine if you just want the scoping functionality. Using an ActiveSupport::Concern will give you the ability to add additional methods as well if that's something you think is going to happen. Here's an example:
# /app/models/concerns/approved.rb
module Approved
extend ActiveSupport::Concern
included do
default_scope { where(approved: false) }
scope :approved, -> { where(approved: true) }
end
def unapprove
update_attribute :approved, false
end
end
class Sighting < ActiveRecord::Base
include Approved
end
class Comment < ActiveRecord::Base
include Approved
end
Then you can make calls like Sighting.approved and Comment.approved to get the appropriate list of approved records. You also get the unapprove method and can do something like Comment.approved.first.unapprove.
In this example, I've also included default_scope which will mean that calls like Sighting.all or Comment.all will return only unapproved items. I included this just as an example, it may not be applicable to your implementation.
Although I've noticed the scope pulled from the concerns needs to be the last scope when concatenating scopes. I'm not quite sure why.
Comment.existing.approved
When I tried it as:
Comment.approved.existing
It failed silently.
And I take that back. I was migrating older code and using scopes with conditions instead of lambdas. When I replaced :conditions the scope order no longer mattered.
scope :existing, -> { where("real = 1") }
replaced
scope :existing, :conditions => "real = 1"

Alternative to default_scope in ActiveRecord

I'm facing a problem with changing the functionality of an app and having to re-write about 700 method calls that now need to be scoped.
I've been looking into how default_scope works, and like most people I'm finding that it's close but not quite helpful for me, because I can't override it very easily.
What rails currently offers to override a default_scope is the unscoped method. The problem with unscoped is that it completely removes all scope, not just the default scope.
I'd really like some input from a Rails / ActiveRecord guru on some alternatives.
Given this basic desired functionality...
class Model < ActiveRecord::Base
...
belongs_to :user
...
default_scope where(:foo => true)
scope :baz, where(:baz => '123')
scope :sans_foo, without_default.where(:foo=>true)
...
end
Could you / how could you create a method that could remove the default scope while leaving the other scoping intact? IE, currently if you use...
user.models.unscoped.where(something)
... it's the same as calling
Model.where(something)
Is it possible to define a method that would allow you to instead do something like this...
user.models.without_default.where(something)
...where the result would still be scoped to user but not include the default scope?
I'd very, very much appreciate any help or suggestions on how such a functionality might be implemented.
You have to use with_exclusive_scope.
http://apidock.com/rails/ActiveRecord/Base/with_exclusive_scope/class
User.with_exclusive_scope do
user.models.baz.where(something)
end

How do you scope ActiveRecord associations in Rails 3?

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)

Resources