Rails scope with a collection - ruby-on-rails

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.

Related

Rails have ActiveRecord grab more than one association in one go?

The question below had a good answer to grab associated values of an activerecord collection in one hit using Comment.includes(:user). What about when you have multiple associations that you want to grab in one go?
Rails have activerecord grab all needed associations in one go?
Is the best way to just chain these together like below Customer.includes(:user).includes(:sales).includes(:prices) or is there a cleaner way.
Furthermore, when I am doing this on a loop on an index table. Can I add a method on the customer.rb model so that I can call #customers.table_includes etc and have
def table_includes
self.includes(:user).includes(:sales).includes(:prices)
end
For the record I tested the above and it didn't work because its a method on a collection (yet to figure out how to do this).
In answering this, I'm assuming that user, sales, and prices are all associations off of Customer.
Instead of chaining, you can do something like this:
Customer.includes(:user, :sales, :prices)
In terms of creating an abstraction for this, you do have a couple options.
First, you could create a scope:
class Customer < ActiveRecord::Base
scope :table_includes, -> { includes(:user, :sales, :prices) }
end
Or if you want for it to be a method, you should consider making it a class-level method instead of an instance-level one:
def self.table_includes
self.includes(:user, :sales, :prices)
end
I would consider the purpose of creating this abstraction though. A very generic name like table_includes will likely not be very friendly over the long term.

Using OR with queries in a scope

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

creating named scopes: do i need to declare that model before the attribute

When creating named scopes in a model, is it necessary to call the model before the attribute that you are using in your query?
Example
scope :sorted, order('position ASC')
vs
scope :sorted, order('pages.position ASC')
is the latter preferred, or inline with conventions? are there benefits to either? or is it just a matter of clarity or legibility?
You will need to declare the model if the scope will be used with a join with another model, which has a field with the same name.
Say the company has_one :contact, and the Contact has a position. Then
Company.sorted.joins(:contact)
will complain (on the SQL level) that it's unclear which of the position fields should be used for sorting.
Otherwise it's optional.

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