What is the difference between .where(nil) and .all? - ruby-on-rails

I have been using Product.all but a lot of code that I have been seeing is using Product.where(nil) when populating a variable. this site has an example that I found for using where(nil). I have searched through documentation and found where where(nil) replaced scoped but can't make heads or tails of what it does.

I believe there used to be a difference, but from Rails 4 there no longer is. This is because from Rails 4 .all returns a relation whereas it used to return an array. So previously:
Product.all
immediately fires a query to the database to return all records, which would get loaded into an array in memory. Basically you are telling Rails that you want the data NOW. See this question for more info.
Product.where(nil)
creates a query (actually an anonymous scope that returns an ActiveRecord:Relation).
The query will only execute when you try to access the data for it. Since it is a scope, you can chain other scopes (without hitting the database each time) and have the entire chain execute as one query when you access the data.
In the Justin Weiss article linked to in the question, we see this code:
def index
#products = Product.where(nil) # creates an anonymous scope
#products = #products.status(params[:status]) if params[:status].present?
#products = #products.location(params[:location]) if params[:location].present?
#products = #products.starts_with(params[:starts_with]) if params[:starts_with].present?
end
That code will execute one database call when the index method ends and returns the data.
But if you changed the first line to:
#products = Product.all
that query would execute immediately. Then the scopes are added, and another query would fire when index method ends. This is more inefficient.
EDIT
I believe the database call would actually happen when you first try to access some data in #products (not when the index method ends). Eg. if you did #products.first then the query would execute.

Related

Pluck data from query results without making another query

Ok, I got some records from a query, something like this.
works = Work.do_a_long_query_with_scope(....)
In my controller, I want to find something else base on this works' ids, then return this works in response
def index
works = Work.do_a_long_query_with_scope(....)
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
end
This works fine except that it created 2 query , 1 with SELECT works.id FROM works ... for that task function, 1 with SELECT works.* from works ... for serializer response.
So can I use works.ids without making another request to DB ?
You could just use #load to tell the relation to load everything from the database:
works = Work.do_a_long_query_with_scope(....)
works.load
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
After works.load, the works.ids call will pull the ids out of the already loaded Works instances rather than going back to the database.
Also, if your Task.find_not_finished_tasks_of_user call is doing something like:
where(id: array_of_work_ids)
then you could instead pass in the whole works relation and say:
where(id: works.select(:id))
to use a subquery instead of sending all the ids back to the database. The first one will do something like:
where id in (a_big_list_of_ids)
but the second will do:
where id in (select id from ...)
There may or may not be any noticeable difference depending on how big works.ids is. There's also a possibility that the subquery will give you different ids if something is changing the database between your load call and when the subquery executes.

Rails: Can you impose multiple sort orders, in the same line?

For instance:
#examples = #user.examples.mostrecent.paginate(page: params[:page])
Where "mostrecent" is defined as:
def self.mostrecent
self.order('created_at DESC')
end
So basically the first call to the database is pull every User's example, and then on top of that, order them by most recent first. It seems like this should be doable, but for some reason I can't get it to work.
There is no defined order scope in the model I'm working with, and other calls to order work just fine. By checking the development.log I can see only the first database pulling example by users is respected. The mostrecent order is never called.
Is there a Rails way of doing this all in one line?
You could use a scope, as in:
scope :by_recent, lambda
{ |since_when| order("created_at") }

How do I effectively search across encrypted fields?

I am using Ruby on Rails 4.1, Ransack and attr_encrypted. I have sensitive data being stored in my database and I want to protect it using the gem attr_encrypted.
As I expected, I got zero results when searching encrypted test data with Ransack.
I tried the following solution to but it didn't seem to work for me. I was under the impression that the load function was used to return the decrypted value.
ReportsController
def index
#report_list = Report.all.load
#q = #report_list.search(params[:q])
#reports = #q.result(distinct: true).order('created_at DESC')
end
Has anyone had any experience searching across encrypted data and could help me generate a working solution?
load will cause the Active Record Collection to execute a query and retrieve the results matching your query (in addition to running after_create call backs which I believe is where the decrypt you were expecting is happening).
def index
#returns all records in DB
#report_list = Report.all.load
#I'm surprised these aren't throwing undefined method search of Array (or something similar)
#q = #report_list.search(params[:q])
#reports = #q.result(distinct: true).order('created_at DESC')
end
I would like to precede this with, I normally do this thing manually and am not familiar with attr_encrypted or Ransack, but I believe these concepts are general enough they could be applied to any setup. So, as to your question, 2 possibilities.
If your ok searching for exact values:
Model.where(encrypted_field: encrypt(params[:value])).first
where encrypt is a method that encrypts and returns the passed string.
Secondly (and painfully)
Model.all.delete_if{|m| !m.encrypted_field.include?(params[:value]) }
This will literally pull, decrypt, and scan every entry in your database.
I would highly recommend not doing this, but you need to do what you need to do.
If you absolutely need to have the information encrypted but still need to be able to do searches like this. I would highly recommend adding tags of some sort to your model. This would allow you to remove sensitive information but still search by some attributes.

SQL command execution sequence in Ruby

I am displaying a list of items on my view. My controller class executes a sql in order to get the list. Also I am updating some values in table once the list if fetched. The problem is the values are being set before the select statement. Below is the controller code:
#orders = List.select("itemname,tableno,quantity,itmstatus").where(:tableno => "01")
List.where(:tableno => "01").update_all(:ordstatus => 'displayed',:itmstatus => 'displayed')
My view displays different fields retrieved in #orders. Now based on itemstatus value I need to set the text color code in my view. So once my select statement is executed, I set the itmstatus value to some other value. But in my view the #orders has the updated value (which I am doing after select). I checked on server side and the select statement is executed after the update statement which I think might be the case for having updated value in #orders. Is there any way through which I can have the update statement execute after select. I tried below and couple of other options but no luck.
if #orders
#orders = List.select("itemname,tableno,quantity,itmstatus").where(:tableno => "01")
List.where(:tableno => "01").update_all(:ordstatus => 'displayed',:itmstatus => 'displayed')
end
Please advise. Thanks.
The thing is that the code #orders = List.select("itemname,tableno,quantity,itmstatus").where(:tableno => "01") is lazily evaluated when the view enumerates the #orders instance variable. That is, it's an ActiveRecord::Relation that only really gets evaluated (the SQL executed) at the time the view is rendered.
One way to prevent this—to fully execute the query and retrieve all the rows before the update statement later on is to call to_a on the ActiveRecord::Relation.
#orders = List.select(...).where(...).to_a
One thing to look out for is if you're using Kaminari for pagination then the regular Kaminari pagination extensions won't work—you'll have to use Kaminari::paginate_array.
Another thing to consider is if your query can potentially return a large number of records. By calling to_a you're telling ActiveRecord to retrieve all those records into memory all at once, which can degrade performance.
Note that, in Rails 3 it's also possible to use the .all method (as in, List.select().where().all) to execute and evaluate the query. However, in Rails 4, Model.all is now equivalent to Model.scoped and is lazily evaluated, hence, .to_a
Alternatively, you might want to look at the ActiveRecord::Relation#load method:
Causes the records to be loaded from the database if they have not been loaded already.
You can use this if for some reason you need to explicitly load some records before
actually using them. The return value is the relation itself, not the records.
Admittedly, I've never actually used that but it might be more appropriate in this case.

Querying the cache in rails still runs a call against the DB

I'm probably missing something very simple here, but can't understand what.
I'm trying to cache a simple active record query but every time I touch the cache, it runs the query against the DB again.
Controller Code:
products = Rails.cache.read("search_results")
if products
render :text => products[0].id
else
products = Product.where('name LIKE ?", 'product_name?')
Rails.cache.write("search_results", products)
end
I can see that in my second call I get to the if block and not the else, but any time I'm trying to touch products (like rendering it) I also see an active record call to the DB.
What am I missing?
The line
products = Product.where('name LIKE ?", 'product_name?')
returns an ActiveRecord::Relation, but does not hit the database unless a kicker method is called on it.
While I would still recommend using fetch as mentioned in my comment above, try changing the line to:
products = Product.where('name LIKE ?", 'product_name?').all
which will force the database hit, and save the actual results of the query into the cache, instead of the relation.

Resources