Is it possible to specify order of a chained scope query - ruby-on-rails

I'm currently doing a chained scope query like this
Enrollment.active.occurs_today
Is it possible to order the query so that .active is queried before .occurs_today and prevent lazy loading? Something similar to below hopefully
Enrollment.active.eval.occurs_today

What's not always appreciated is that the order of chained scope doesn't really matter. The chained scopes are building a single query that includes all the conditions, and then when you action the query (by requesting records or a count, for example) at that point the records are retrieved using the combined conditions.
The exception is the rails console, where each scope is executed as it's entered.
if you have pseudo-scope (something that does a query that returns id and then queries on that) you could implement it as a class method and arrange to pass into the class method other scopes as arguments to execute them first. A possible implementation might be...
def self.occurs_today(*scopes)
result = scopes.inject(all) {|relation, scope| relation.send(scope)}
where(id: result.select { |s| s.occurs_on?(Date.current) }.map(&:id))
end
You could then do
Enrollment.occurs_today(:active)
or several scopes
Enrollment.occurs_today(:active, :priority)
In both cases, the select will be performed on the already scoped relation.
alternatively, I think you could see an improvement in performance (and automatic recognition of scope order) just by changing your existing occurs_today scope.
Instead of selecting on all records...
scope :occurs_today, -> { where(id: all.select { |s| s.occurs_on?(Date.current) }.pluck(:id) }
...select on only the currently scoped records...
scope :occurs_today, -> { where(id: select { |s| s.occurs_on?(Date.current) }.pluck(:id) }

Related

Returning the original relation in a class method called on a relation

I have a class method on a model called order_by_ids(ids) that is called by an ActiveRecord Relation. Below is an example of its usage:
User.where(id: [1,2,3]).order_by_ids([2,1,3])
...will return Users 1, 2, and 3 in the order of: [2,1,3]
I would like for it to return the original relation (essentially doing nothing) if passed an empty array.
The following returns the entire class, not just the relation it's called on:
return self unless ids.present?
The following works 100%, but it seems inelegant. Also, I think it runs an unnecessary query (seems slower in the console anyway):
return where.not(id: nil) unless ids.present?
Is there a quick way to just return the relation it's called on? I could theoretically make it a scope, but I've been taught to avoid scopes with arguments (see this guide for reference).
Note: I am using Rails 3, so all returns an array. I'm essentially looking for a Rails 4 version of all.
The following should preserve the upstream scope chain (returning all, not self):
return all if ids.none?
P.S. Named scopes is a perfectly accepted and conventional way of dealing with queries.

What does ActiveRecord code scope code result in?

I am trying to implement Push notifications in my Ruby project. I found this code, but cannot tell what it does. In my project I am not using ActiveRecord as I am using MongoDB. The two lines I cannot understand are as follows
scope :android, -> { where(device_type: 'android') }
I am not familair with ActiveRecord so is the "-> { where(device_type: 'android') }" a SQL search? What is the result of this code? Wondering how I would implement since I am using MongoDB?
registration_ids= Device.android.map(&:registration_id)
The "Device.android.map" where and how is the .android coming from? I am guessing there is a subclass of Device name android?
CODE:
rails g model Device registration_id:string device_type:string
class User < ActiveRecord::Base
scope :android, -> { where(device_type: 'android') }
...
def self.notify_android(data, collapse_key = nil)
require 'gcm'
gcm = GCM.new(ENV['API_KEY']) # an api key from prerequisites
registration_ids= Device.android.map(&:registration_id) # an array of one or more client registration IDs
options = {
data: data,
collapse_key: collapse_key || 'my_app'
}
response = gcm.send(registration_ids, options)
end
...
end
UPDATE:
Ok, this Android is a commonly used query that can be used as method calls. So now I know what scope does, but not sure how to implement with MongoDB and MongoID.
I think this is correct for rails 4 and MongoID?
scope :android, where(:device_type => 'android')
So if the above is correct then it leaves one question of what does this mean?
&:registration_id
Please read about active record active record to understand what it does. Also have a look at ORM and scope, android is not any subclass it basically
add a class method for retrieving and querying objects.
so you basically have class method called android on device class
When you call Device.android it turns into query SELECT "devices".* FROM "devices" WHERE "devices"."device_type" = 'android' and returns all the records matching where clause.
Before understanding Device.android.map(&:registration_id) please have a look at map.
Map returns a new array with the results of running block once for every element in enum.
So basically you are calling registration_id on every object which matches filter device_type = 'android'. and returns an array of registration ids or in other words we are looping over all objects and collecting registration_id of each object and returning all the collected registration ids.
Read about scope https://apidock.com/rails/ActiveRecord/NamedScope/ClassMethods/scope
A scope represents a narrowing of a database query
In your case
registration_ids= Device.android.map(&:registration_id)
It will map registration_id of Device whose device_type is 'android'
In &:registration_id & symbol is used to denote that the following argument should be treated as the block given to the method. these is not a proc method so it called it's to_proc method
Device.android.map(&:registration_id.to_proc)
which is same as
Device.android.map { |x| x.registration_id }

Return ActiveRecord relation from WHERE NOT EXISTS query

Okay so here's a fun one. The relations are something like:
A Client has_many state_changes, and a StateChange belongs_to one Client.
I have this query:
Client.find_by_sql('SELECT * FROM clients cs WHERE NOT EXISTS (SELECT * FROM state_changes WHERE state_changes.client_id = cs.id AND current = true)')
The problem is that this returns an Array object and not an ActiveRecord Relation. I need to run an update on the state_changes that belong to the returned clients from that query.
So there's two issues essentially, getting the results as an ActiveRecord relation, and then getting all of their state_changes, also as an ActiveRecord relation, so that I can run the update.
I also understand that this might be a convoluted way to go about it...
I also understand that this might be a convoluted way to go about it..
Having easy AR interface - indeed it is convoluted :)
I would probably go with some scopes:
class StateChange
scope :active, -> { where.not(current: false) }
# I believe with your setup it is not equal to `where(current: true)`
end
class Client
scope :some_smart_name, -> { includes(:state_changes).merge(StateChange.active) }
end
This should return you clients who who don't have associated state_changes with current set to false.

How to delimit a rails model with a constraint

Is it possible (and how) to delimit an ActiveRecord model with "a where"? I.E. When I call OrderCommunication.all then the query would do something like select * from ordcomm where type = 'order'. I know it's kind of nasty but our database just can't be modified at all and we can't refactor. Basically I need to declare my model with a where ordcomm = 'order' so I don't do it in all my subsequent queries.
You can use a default scope to get this done.
default_scope { where(type: 'order') }
Now, if you try OrderCommunication.all, you would only get the records with the type of 'order'
If you instead want to fetch all the records, use unscoped
OrderCommunication.unscoped.all

Scope chaining with optionally parameters

currently I have the following type of code
scope :scope1, lambda{|param| where(param1: params)}
scope :scope2, lambda{|param| where(param2: params)}
and in the controller I would do
results = Model.some_other_scope
if !params[:param1].blank?
results = results.scope1(params[:param1])
end
if !params[:param2].blank?
results = results.scope2(params[:param2])
end
this is not very efficient since it does several calls to DB instead of one.
Is there a way to chain these scopes so it will be disabled if the params don't exist?
results = Model.scope1(params[:param1]).scope2(params[:param2])
currently, chaining doesn't work because of the where clause doesn't return anything then the whole chain breaks
Actually, ActiveRecord queries are evaluated lazily, so, simplyfying, this code produces only one DB call. So your problem doesn't really exist.

Resources