So in my app I have notifications and different record counts that are used in the overall layout, and are therefore needed on every page.
Currently in my application_controller I have a lot of things like such:
#status_al = Status.find_by_name("Alive")
#status_de = Status.find_by_name("Dead")
#status_sus = Status.find_by_name("Suspended")
#status_hid = Status.find_by_name("Hidden")
#status_arc = Status.find_by_name("Archived")
#balloon_active = Post.where(:user_id => current_user.id, :status_id => #status_al.id )
#balloon_dependent = Post.where(:user_id => current_user.id, :status_id => #status_de.id )
#balloon_upcoming = Post.where(:user_id => current_user.id, :status_id => #status_sus.id )
#balloon_deferred = Post.where(:user_id => current_user.id, :status_id => #status_hid.id )
#balloon_complete = Post.where(:user_id => current_user.id, :status_id => #status_arc.id )
..
Thats really just a small piece, I have at least double this with similar calls. The issue is I need these numbers pretty much on every page, but I feel like I'm htting the DB wayyyy too many times here.
Any ideas for a better implementation?
Scopes
First off, you should move many of these into scopes, which will allow you to use them in far more flexible ways, such as chaining queries using ActiveRecord. See http://edgerails.info/articles/what-s-new-in-edge-rails/2010/02/23/the-skinny-on-scopes-formerly-named-scope/index.html.
Indexes
Second, if you're doing all these queries anyway, make sure you index your database to, for example, find Status quickly by name. A sample migration to accomplish the first index:
add_index :status (or the name of your Status controller), :name
Session
If the data you need here is not critical, i.e. you don't need to rely on it to further calculations or database updates, you could consider storing some of this data in the user's session. If you do so, you can simply read whatever you need from the session in the future instead of hitting your db on every page load.
If this data is critical and/or it must be updated to the second, then avoid this option.
Counter Caching
If you need certain record counts on a regular basis, consider setting up a counter_cache. Basically, in your models, you do the following:
Parent.rb
has_many :children
Child.rb
belongs_to :parent, :counter_cache => true
Ensure your parent table has a field called child_count and Rails will update this field for you on every child's creation/deletion. If you use counter_caching, you will avoid hitting the database to get the counts.
Note: Using counter_caching will result in a slightly longer create and destroy action, but if you are using these counts often, it's usually worth going with counter_cache.
You should only need 1 database query for this, something like:
#posts = Post.where(:user_id => current_user.id).includes(:status)
Then use Enumerable#group_by to collect the posts into the different categories:
posts_by_status = #posts.group_by do {|post| post.status.name }
which will give you a hash:
{'Alive' => [...], 'Dead' => [...]}
etc.
Related
I have the following code:
results = Report.where(:car => 'xxxx').group(:date, :name, :car).select('date, name ,car, info, MAX(price) AS max_price')
for customer in customers
result = results.where(:date => customer.date, :name => customer.name, :car => customer.car).first
.... rest of the code ....
end
I have a database with many records ~20,000, so I want to optimize the code and cache results in memory.
Once again: my overall intention is make this code more efficient in terms of time. I want it to run faster than it is now and I want to reduce amount of database calls.
I am thinking of making my inital results object an array. I have a remote database so each .where query takes sometime. When I make results an array by adding .to_a - I load it to memory. So I think, it should be better(but not really sure)
Something like:
results = Report.where(:car => 'xxxx').group(:date, :name, :car)
.select('date, name ,car, info, MAX(price) AS max_price')
.to_a
for customer in customers
result = results.select {|result| result.date == customer.date and result.name == customer.name and result.car == customer.car }
.first
end
Well, the best things to have an association to fetch all reports for customers. In the case when you can not do so, I would recommend making only one query instead of n+1(as stated in the question) like this:
results = Report.where(:car => 'xxxx').group(:date, :name, :car)
.select('date, name ,car, info, MAX(price) AS max_price')
.where(:date => customers.map(&:date), :name => customers.map(&:name), :car => customers.map(&:car))
Assuming customers is an array of objects which respond to :name, :car, and :date methods.
One thing that should be noted is it does not guarantee that it will fetch reports of an exact customer. For that, you'd have to verify it by iterating through the results object yourself.
I have a profile card of user that have registration in my forum.
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
:avatar => params[:person][:avatar].original_filename,
:city => params[:person][:city]},
{:id => params[:id]})
This is query for updating data in database. But here is a small problem - this will work only in a situation, if I the user send through form avatar (image). If not send avatar - that means the user already have uploaded avatar and the form send only name, sex, age and city. So in this case I'll get error in line :avatar => params[:person][:avatar].original_filename, -- and I would like to ask you for, if exist some elegant way, how to treat this moment.
I thought something like this:
if params[:person][:avatar]
avatar = ':avatar => params[:person][:avatar].original_filename,'
end
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
avatar
:city => params[:person][:city]},
{:id => params[:id]})
But unfortunately, this doesn't work... How you're solving similar situation?
Thank you.
Well, it seems, like your params[:person] keys are similar to your model fields. So why don't you just pass params[:person] to update_all?
Alternatively, you could create a hash person, initialize it the way you want and then pass it to update_all
person = { :name => params[:person][:name] ,
...
if params[:person][:avatar]
person[:avatar] = params[:person][:avatar].original_filename
end
Person.update(params[:id], person)
I've changed update_all to update, because update_all is used to update all the records (that match the condition), while update find's the record by it's ID.
But again, it's a bad practice and you have to type a lot of unnecessary code.
One more thing. update_all makes a direct DB call, which doesn't involve validations, callbacks etc.
So, if you don't have some special reason for this, you'd better do something like this:
#person = Person.find params[:id]
#person.update_attributes params[:person]
I really think, you should check this book out
Updated once again :)
You see, such things belong to your models, not controllers. You could define a setter in the model:
def avatar=(value)
write_attribute(:avatar, value.original_filename)
end
I think it's safe to say everyone loves doing something like this in Rails:
Product.find(:all, :conditions => {:featured => true})
This will return all products where the attribute "featured" (which is a database column) is true. But let's say I have a method on Product like this:
def display_ready?
(self.photos.length > 0) && (File.exist?(self.file.path))
end
...and I want to find all products where that method returns true. I can think of several messy ways of doing it, but I think it's also safe to say we love Rails because most things are not messy.
I'd say it's a pretty common problem for me... I'd have to imagine that a good answer will help many people. Any non-messy ideas?
The only reliable way to filter these is the somewhat ugly method of retrieving all records and running them through a select:
display_ready_products = Product.all.select(&:display_ready?)
This is inefficient to the extreme especially if you have a large number of products which are probably not going to qualify.
The better way to do this is to have a counter cache for your photos, plus a flag set when your file is uploaded:
class Product < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :product, :counter_cache => true
end
You'll need to add a column to the Product table:
add_column :products, :photos_count, :default => 0
This will give you a column with the number of photos. There's a way to pre-populate these counters with the correct numbers at the start instead of zero, but there's no need to get into that here.
Add a column to record your file flag:
add_column :products, :file_exists, :boolean, :null => false, :default => false
Now trigger this when saving:
class Product < ActiveRecord::Base
before_save :assign_file_exists_flag
protected
def assign_file_exists_flag
self.file_exists = File.exist?(self.file.path)
end
end
Since these two attributes are rendered into database columns, you can now query on them directly:
Product.find(:all, :conditions => 'file_exists=1 AND photos_count>0')
You can clean that up by writing two named scopes that will encapsulate that behavior.
You need to do a two level select:
1) Select all possible rows from the database. This happens in the db.
2) Within Ruby, select the valid rows from all of the rows. Eg
possible_products = Product.find(:all, :conditions => {:featured => true})
products = possible_products.select{|p| p.display_ready?}
Added:
Or:
products = Product.find(:all, :conditions => {:featured => true}).select {|p|
p.display_ready?}
The second select is the select method of the Array object. Select is a very handy method, along with detect. (Detect comes from Enumerable and is mixed in with Array.)
I need to update a single field across a large set of records. Normally, I would just run a quick SQL update statement from the console and be done with it, but this is a utility that end users need to be able to run in this app.
So, here's my code:
users = User.find(:all, :select => 'id, flag')
users.each do |u|
u.flag = false
u.save
end
I'm afraid this is just going to take a while as the number of users increases (current sitting at around 35k, adding 2-5k a week). Is there a faster way to do this?
Thanks!
If you really want to update all records, the easiest way is to use #update_all:
User.update_all(:flag => false)
This is the equivalent of:
UPDATE users SET flag = 'f'
(The exact SQL will be different depending on your adapter)
The #update_all method also accepts conditions:
User.update_all({:flag => false}, {:created_on => 3.weeks.ago .. 5.hours.ago})
Also, #update_all can be combined with named scopes:
class User < ActiveRecord::Base
named_scope :inactive, lambda {{:conditions => {:last_login_at => 2.years.ago .. 2.weeks.ago}}
end
User.inactive.update_all(:flag => false)
You could use ActiveRecord's execute method to execute the update SQL. Something like this:
ActiveRecord::Base.connection.execute('UPDATE users SET flag=0')
i got this error:
SQLite3::SQLException: no such column: apis.name: SELECT * FROM examples WHERE ("apis"."name" = 'deep')
my code
Api.find :all, :from => params[:table_name], :conditions => {:name => 'deep' }
I need to make a back end rails application which will be used by a silverlight application. one of the requirements is to fetch simple data from the database. i need to be able to query different tables with the same code.(my app has 2000 tables!)
i think it does not make sense for rails to put in "apis" in the WHERE clause. is there any speciic reason for this?
It does that so when joins are performed, the where clauses will line up with the right tables' columns. This is handy most of the time, but in your particular case causes issues.
What you could do is use the other conditions syntax, which will not add rails table names to the attributes, but still sanitize the inputs properly.
Api.find :all, :from => params[:table_name], :conditions => ['name = ?','deep']