Scope chaining with optionally parameters - ruby-on-rails

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.

Related

Is it possible to specify order of a chained scope query

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) }

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.

Rails - Returning all records for who a method returns true

I have a method:
class Role
def currently_active
klass = roleable_type.constantize
actor = Person.find(role_actor_id)
parent = klass.find(roleable_id)
return true if parent.current_membership?
actor.current_membership?
end
end
I would like to return all instances of Role for who this method is true, however can't iterate through them with all.each as this takes around 20 seconds. I'm trying to use where statements, however they rely on an attribute of the model rather than a method:
Role.where(currently_active: true)
This obviously throws an error as there is no attribute called currently_active. How can I perform this query the most efficient way possible, and if possible using Active Records rather than arrays?
Thanks in advance
It seems impossible, in your case you have to do iterations. I think the best solution is to add a Boolean column in your table, so you can filter by query and this will be much faster.
After seeing your method after edit, it seems that it's not slow because of the loop, it is slow because Person.find and klass.find , you are doing alot of queries and database read here. (You better use associations and do some kind of eager loading, it will be much faster)
Another work-around is you can use ActiveModelSerializers , in the serializer you can get the attributes on the object based on condition. and after that you can work your logic to neglect the objects that have some kind of flag or attribute.
See here the documentation of active model serializer
Conditional attributes in Active Model Serializers
Wherever possible you better delegate your methods to SQL through activerecord when you're seeking better efficiency and speed and avoid iterating through objects in ruby to apply the method. I understand this is an old question but still many might get the wrong idea.
There is not enough information on current_membership? methods on associations but here's an example based on some guess-work from me:
roleables = roleable_type.pluralize
roleable_type_sym = roleable_type.to_sym
Role.joins(roleables_sym).where(" ? BETWEEN #{roleables}.membership_start_date AND #{roleables}.membership_end_date", DateTime.current).or(Role.joins(:person).where(" ? BETWEEN persons.membership_start_date AND persons.membership_end_date", DateTime.current))
so you might have to re-implement the method you have written in the model in SQL to improve efficiency and speed.
Try the select method: https://www.rubyguides.com/2019/04/ruby-select-method/
Role.all.select { |r| r.currently_active? }
The above can be shortened to Role.select(&:currently_active?)

filter active record results in ruby

Im completely new to Ruby on Rails. Im working on an existing API and I have a sql query which is executed with:
results = ActiveRecord::Base.connection.select_all(query)
The output of the results is:
{"availability"=>"In Stock", "available_to_purchase"=>1}
{"availability"=>"Void", "available_to_purchase"=>0}
{"availability"=>"Out of Stock", "available_to_purchase"=>0}
{"availability"=>"In Stores Only", "available_to_purchase"=>0}
I know want to get all values for availability based on available_to_purchase being 0. I have tried:
res = results.where(available_to_purchase: 0)
But I get an error saying:
undefined method `where'
I also tried:
res = results.select { |result| result.available_to_purchase == 0 }
But i end up with the error:
undefined method `available_to_purchase'
I know I can loop through and check available_to_purchase is 0, then add to a new array. But is there any method to quickly filter active record results based on value of a column?
Going directly through the connection avoids nearly everything ActiveRecord is intended to provide you. First thing, read Active Record Basics. The Rails guides are really well put together and should contain everything you need for your first few weeks using the framework.
The reason you aren't getting the where method you expected is that it's a method on ActiveRecord::Relation, and select_all just returns you a list of hashes. By the time you call results.where, it's too late. You need to instantiate a Product model (or whatever would match your table name), which is as simple as:
class Product < ActiveRecord::Base
end
Rails will look at the products table (again, I'm making up your table name) for the attributes a Product has, and then you'll be able to query based on them with where:
results = Product.where(available_to_purchase: 0)
The model will also have the accessor methods you were trying for, so you can do things like results[0].available_to_purchase == 0 or results[0].availability = 'Already in your house'.
In order to call .where on the results, you need an instance of an active record relation. Regular ActiveRecord queries will return this. select_all, however, returns an array of hashes. Array does not have the method where.
res = results.select { |result| result.available_to_purchase == 0 }
Your second error is because you are attempting to access ruby hash keys incorrectly. Properties must be accessed asresult['available_to_purchase']
Here is the answer to your question. I do agree with others though that you should be using ActiveRecord.
results.select { |result| result['available_to_purchase'] == 0 }

Rails difference in object created from a .find(:id) and .where() methods

What is the difference in the objects created with these 2 methods:
tec = Technique.find(6)
tec2 = Technique.where(:korean => 'Jok Sul')
The data returned for each is exactly the same, yet the first object will respond perfectly to an inherited method like update_attributes while the second object will give an error of method not found.
When I do tec.class and tec2.class one is an ActiveRecord::Relation and the other doesn't give me a class at all, it just prints out the content of the object.
Maybe when you use the .where method you get an array, even if there is only one match and therefore you always have to issue the .each method to get at the contents? But that makes it hard to deal with when you want to update records, etc.
Can someone clarify this for me? Specifically, how to deal with matches found through the .where method.
Thanks.
Try:
tec2 = Technique.where(:korean => 'Jok Sul').first
Good question.
tec_scope = Technique.where(:korean => 'Jok Sul') # create an internal query
Remember, here only the query is created, it is not executed. You can programmatically build on top of this query if you so wished. The scope (or query if you so wish) will be executed in 2 ways. "Implicit" or "Explicit". Implicit way of running the query happens for example in the console, which invokes a method on the scope which automatically runs the query for you. This wont happen in your controllers unless you run it explicitly for .e.g
tec_scope.all # returns array
tec_scope.first # retuns one element
Scopes are just adding where clauses/predicates to your query. It's query building and delaying the execution till it is needed.
However,
tec_objects = Technique.find(6) # explicitly runs a query and returns one object (in this case)
This will explicitly run the query there and then. It is a question of the timing of execution of the query.
The difference is subtle but very important.
This hasnt got anything to do with whether you get one result or an array.
Technique.find([4,5]) # will return an array
Technique.find(4) # will return one object
Technique.where(:some_key => "some value").all # will return an array
Technique.where(:id => 5).first # will return one object
The difference is in timing of the execution of the query. Don't let the console fool you into believing there is no difference. Console is implicitly firing the query for you :)
The find(6) returns a single object, because you're specifying the object ID in the database, which is guaranteed to be unique by convention.
The where call returns a collection, which may be only 1 item long, but it still returns a collection, not a single object.
You can reveal this difference. Using your example code, if you call tec.class vs. tec2.class I think you'll find that they aren't the same class of object, as you expect.
That is, the methods available to a collection of objects is different than the methods available on an instance of that object.

Resources