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.
Related
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.
I'm a bit confused by active record, it just seems to fire the query at any time you stop, ie.
#model.where( :store_id => #sid )
Which is fine, but what if I want to build a query like this:
query = #model.where( :store_id => #sid )
if(some_condition)
query.offset(50)
and then execute the query (not actually what I'm doing but a very simple example). Is there a way to put together the query in steps and then tell it to execute?
Actually, ActiveRecord will do exactly what you want. It's called lazy loading. You might be getting confused by the rails console, which calls .inspect behinds the scenes on the result of the line.
Check out this question: Lazy loading in Rails 3.2.6
This already works like you want it too.
where() returns an instance of ActiveRecord::Relation.
The relation won't execute it's database call until it needs to. The reason you might be experiencing otherwise is that you're testing it in the console, which prints the output of each statement (thus loading the relation). You can test whether a relation has been loaded via the loaded() method.
Try this on the console:
m = #model.where(:store_id => #sid); # the semicolon will silence the output
m.loaded? # nil
m # executes db call, will print out the contents of the relation
m.loaded? # true
In our rails 3.2 app, we need to retrieve all customer records out of customer table and assign them to a variable customers and do query (such as .where(:active => true) on variable customers late on. There are 2 questions here:
what's the better way to retrieve all records?
Customer.all works. However according to rails document, it may have performance issue when Customer table gets large. We tried Customer.find_each and it has error "no block given (yield)".
How to make the variable customers query_able?
When performing query on variable customers (like customers.where(:active => true)), there is an error: undefined methodwhere' for #. It seems that thecustomersis an array object and can't takewhere. How can we retrievecustomers` in such a way it can be query-able?
Thanks for help.
In Rails < 4 .all makes database call immediately, loads records and returns array. Instead use "lazy" scoped method which returns chainable ActiveRecord::Relation object. E.g.:
customers = Customer.scoped
...
customers = customers.where(:active => true)
customers = customers.where(...)
etc...
And at the moment when you will need to load records and iterate over them you can call find_each:
customers.find_each do |customer|
...
end
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.
I have an orders object with a status field. I would like to retrieve all orders with status=1 (so I know what is being modified) and then update them to have a status=2. My code is as follows:
#new_orders=Order.where("status=1")
Order.where("status=1").update_all :status=>2
The problem is that #new_orders is not set until the view uses the variable. I am guessing this has to do with lazy loading. How do I get around this so I can display all records that have been modified?
Try adding .all or .to_a to the end of your relation:
#new_orders = Order.where(:status => 1).all