I'm having a tough time deciding how to refactor this method in my controller. The idea is that (in this case) it graphs the users that joined (or were created) in the past two weeks.
You might be wondering why I did the #graph_limit thing, and that is because I always want the day that has the most results to be the tallest bar on my bar chart (which in the view are just created with css by making the height of the <div> using css).
Basically I want to dry it up and... ya know just about improve this method as much as possible:
# Controller
def index
two_weeks_ago = Date.today - 13.days
#users_graphed = User.count(:conditions=>["created_at >= ?", two_weeks_ago], :order => 'DATE(created_at) DESC', :group => ["DATE(created_at)"])
two_weeks_ago.upto(Date.today) do |day|
#graph_limit = 100/#users_graphed.values.max.to_f
#users_graphed[day.to_s] ||= 0
end
end
Also I should mention, that you guys are probably going to rip my code to shreds... so I'm bracing for the outcome.
# View
<% #users_graphed.sort.reverse.each do |user| %>
<li>
<% content_tag :div, :style => "height: #{number_with_precision(user[1] * #graph_limit, :precision => 2)}px; ", :class => "stat_bar" do %>
<%= content_tag(:span, user[1]) unless user[1] == 0 %>
<% end %>
</li>
<% end %>
Ultimately and what my real goal here is to put this into my application controller and be able to chart any models by it's create_at times. maybe something like tasks.chart_by(2.weeks). How would you guys get this separated out into something I can use throughout the whole app?
I agree with Joseph that your controller here is doing a lot of work that should be done in the model. Any time you're specifying multiple find parameters in your controller, ask yourself whether or not that should be in your model instead.
You're doing a lot of iterating here that seems needless. Firstly, You shouldn't be calculating #graph_limit inside the loop. You're recalculating it 14 times, but the value is going to be the same every time. Do that outside the loop.
Secondly, that sort.reverse in your view sticks out. You're already sorting in your finder (:order => 'DATE(created_at) DESC'), and then you're sorting again in your view and then reversing it? You should instead be asking the database for the values in the final order you want them. Then to make your zero-filling code work you can just reverse it, doing Date.today.downto(two_weeks_ago) instead of upto.
I would say that you should really be doing this all in SQL, but unfortunately (as perhaps you've discovered) MySQL makes it difficult to fill in missing days without creating a calendar table to join against.
Thanks Jordan, per your ideas (which were really great by the way) I've created a helper that is like such:
def graph_by_time(klass, time_ago)
time_range_start = Date.today - time_ago
#elements_graphed = klass.count(:conditions=>["created_at >= ?", time_range_start], :order => 'DATE(created_at) DESC', :group => ["DATE(created_at)"])
#graph_limit = 100/#elements_graphed.values.max.to_f
time_range_start.upto(Date.today) do |element|
#elements_graphed[element.to_s] ||= 0
end
return #elements_graphed.sort.reverse
end
The biggest issue here is zero filling the days which have no records associated with them, your method of switching to from upto to downto didnt work and only returned the records which did result in a integer other than zero.
Related
I'm using the gem ranked model : https://github.com/mixonic/ranked-model
I want to use it to allow my users to order articles in a back office.
From what i understand from the gem the stored value in DB is different from the _position who is the real rank if you start counting your elements.
Now if i have an array of images, i display them and i want to increase or decrease their position of one rank. i would do a row_order_postion +1 or -1. But i cannot know their row_order_postion (it returns nil), only their row_order.
My question is : what use is there to be able to change by row_order_position if i cannot know my row_order_position ?
I too find this a little weird, but if you are iterating over an array, one work-around is to do a each_with_index. This way you can store the position somewhere (in a hidden field for instance), and when performing the rearrangement of the articles pass the new position + or - 1.
# Something like this
<% Article.each_with_index do |article, index| %>
<%= render :partial => 'article', :locals => {:article => article, :position => index} %>
<% end %>
My way
controller pattern 1 (note: Here, it's calling all users!!)
#users = User.confirmed.joins(:profile)
view pattern 1 (note: Here, it only shows first 10 users but it show the number of all users!!)
<%= "ALL ("+ #users.count.to_s + " users)" %>
<% #users.limit(10).each do |users| %>
<%= render 'users/user', :user => users %>
<% end %>
Should it be just like this below if I'm considering page loading speed?
Or it won't be changed?
controller pattern 2 (note: I added limit(10), and #users_count to count all users)
#users = User.confirmed.joins(:profile).limit(10)
#users_count = User.confirmed.joins(:profile).count
view pattern 2 (note: I took it off limit(10) and use #users_count for count)
<%= "ALL ("+ #users_count.to_s + " users)" %>
<% #users.each do |users| %>
<%= render 'users/user', :user => users %>
<% end %>
If you have lazy loading disabled, then the second approach would be faster because Rails doesn't need to fetch all records from the database. You should really fetch only the records you need when performing queries.
If you have lazy loading enabled (by default), then it is the same, because the data is fetched when it is needed, so the effect will be the same. You can also put two variables in controller and write the same query as you did in the view and the data will be fetched only if and when it is needed.
#users = User.confirmed.joins(:profile)
#users_count = #users.count
#users = #users.limit(10)
You can check sql generated by the app in your rails console and then decide.
Also, if you are using profile in user.html.erb, consider using includes instead of join. Join can cause n+1 problem if you need associated records. If you don't, you do not want to fetch records you don't need. You can read more about it here, in 12 Eager Loading Associations.
The two options are exactly the same. Neither of them loads all the Users because you're just chaining scopes. The query is only run when you call .each in the view, at which point you've applied the .limit(10) anyway. I'd go with the first option because the code is cleaner.
#users.count does one query to get the count, it doesn't instantiate any User objects.
#users.limit(10).each ... does one query (actually two because you've used includes) with a limit, so it will instantiate 10 objects plus your includes.
you can try #users.find_in_batches
Please take a look
Find in batches
Please let me know
If you want speed loading
I can suggest you memcache Memcache
I have these arrays:
#users = [[1,'Mark'],[2,'Bill'],[3,'John']]
#projects = [[1,'Change the world'],[2,'Build a computer'],[3,'Run in a circle']]
#points_due = [[1,1,"40"],[1,3,"80"],[2,1,"20"]]
I have a few solutions but I am looking for the most performance efficient way of iterating all these items as if the results were given to me via ActiveRecord.
I want to display a <UL> with a Project, and a <LI> with the user name and the points due to that particular user. I came up with some solutions converting the arrays into hashes using inject and map, however, I feel there is a better way to do such a thing.
I would like to do something like:
#projects.each do |project|
<%= project.name %>
<ul>
project.each do |user|
<li><%= user.name %> | <%= user.points_due %></li>
end
</ul>
end
To do things like what you're talking about (iterating over model X and then showing something for each model Y), a nested .each loop (more or less like the one you've sort-of included in your question) is a pretty standard method. If the display starts getting complicated, you might extract it into one or more layers of partials.
Worrying about the performance of this or other methods is likely premature at best. The performance of different ways of manipulating data structures is not going to be a significant factor in the overall performance of your page. Once you have something working, see if it's actually slow, then worry about how to make it faster.
Edit, based on your comments: An Array of Hashes (with symbols for keys) would probably be better than an Array of Arrays for this data. Not because they're more performant, but because it will make your code more understandable (to yourself, even if no one else ever works on it).
Consider:
Using AR objects, you would do the following to print the names of each Project:
#projects.each do |project|
puts project.name
end
If you simulate this behavior using a Hash:
#projects.each do |project|
puts project[:name]
end
Whereas using Arrays:
#projects.each do |project|
puts project[1]
end
Much harder to remember, and you have to make sure your code puts attributes in the proper order, or that might be some other aspect of the project, instead of the name.
Assuming you've agreed with me and are using this structure (#projects = [{:title => 'foo', 'id' => 1}, {:title => 'bar', :id => 2}] etc.), here's a way to perform the iteration you mention, simply printing out (what I believe to be) the desired data - the number of points listed in #points_due for each user that has some in that project:
#projects.each do |project|
puts project[:name]
#points_due.select{|pd| pd[:project_id] == project[:id] }.each do |project_points|
user = #users.select{|u| u[:id] == project_points[:user_id]}.first
puts "#{user[:name]} has #{project_points[:points]} points due on this project."
end
end
I hope this approximates the output you want, but if not, it should be fairly easy to use it as an example - judicious use of .select will probably get it done.
Edit the second: Converting Arrays to Hashes:
#projects_as_arrays = [[1,'Change the world'],[2,'Build a computer'],[3,'Run in a circle']]
#projects_as_hashes = #projects_as_array.map do |p_arr|
{:id => p_arr[0], :name => p_arr[1]}
end
You should be able to do something along these general lines for each of your arrays.
I'm curious about the efficiency of the Rails current_page? helper method. I'm using it in a view, roughly as shown below:
<% if current_page?(:action => "foo") %>
<dt>Label 1:</dt>
<% else %>
<dt>Label 2:</dt>
<% end %>
and
<% if current_page?(:action => "foo") || current_page(:action => "bar") %>
<dt>Label 1:</dt>
<% else %>
<dt>Label 2:</dt>
<% end %>
But would it be more efficient to make this switch some other way? For instance, would it be more efficient to set an instance variable in my controller actions for foo and bar, then check <% if #foo || #bar %>?
And is there any difference in the efficiency between Rails 2 and Rails 3?
I can't speak to the difference between Rails 2 and Rails 3, but looking at the source code in ActionPack, it doesn't look like the method is doing anything particularly complicated. url_for could be slightly inefficient (I don't know offhand), but unless you're doing a huge number of loops over this partial, optimizing this is probably not going to save you a noticeable amount of time.
That said, it would be easy to do some basic benchmarks -- t = Time.now; loop 1000 times over one version; v1_time = t - Time.now; t = Time.now; rinse and repeat with the other version. If you do it, let me know what you find.
All that said, it seems to me that it would be probably be cleaner conceptually to have your controller methods set appropriate flags, if the flags could be expressed as concepts not directly related to the view. I'd be curious what others think.
Background:
I'm creating a dashboard as a project and I have a query that I think is going to be a big performance issue:
<% for outlet in #outlets %>
<% if Monitoring.where(:outlet_id => outlet.id).where('date(created_at) = ?', Date.today).exists? %>
<li>
<a class="done" href="<%= outlet_url(outlet) %>" rel="tooltip" title="<%= outlet.name %>"></a>
</li>
<% else %>
<li>
</li>
<% end %>
<% end %>
What I'm trying to achieve is a series of dots on a page. If the anchor tag has a class of done, it will display as green, if not it will be red (done through CSS).
Aside from the obvious DRY issues here, this query is very heavy, so I'm looking at ways to improve it.
An Outlet is Monitored at least once a day (An Outlet has_many :monitorings). For each outlet I need to check if it has been monitored on that particular day, and output the HTML accordingly.
If anyone could help me with this it would be fantastic.
(Also, any advice on caching this would be appreciated).
Cheers in advance :).
You might make a conditioned association for current monitors, then use includes to fetch the associated current monitorings on the original query.
class Outlet
has many :current_monitorings, :class_name => "Monitoring",
:conditions => proc { [ 'monitorings.created_at > ?', Time.now.midnight ] }
end
#outlets = Outlet.includes(:current_monitorings)
#outlets.each do |outlet|
if outlet.current_monitorings.empty?
# the no monitor today case
else
# it's been monitored today
end
end
At the Postgres level, you'll likely benefit from an index on monitorings(outlet_id,created_at) to support the outer join implied by #includes.
BTW, it's bad style to be executing database queries in your view. Put the domain logic in your models, and have your controller execute the query and supply the results to the presentation layer.
Maybe try:
<% #outlets.includes(:monitorings).each do |outlet| %>
<% css_class = outlet.monitorings.any? { |m| m.created_at == Date.today } ? 'done' : '' %>
<li><%= link_to '', outlet_url(outlet), :class => css_class, :rel => "tooltip", :title => outlet.name %></li>
<% end %>
It'll perform 1 big query.
A good way to cache this would be denormalizing your database with the use of callbacks. In your Outlet model, you could add a field called last_monitored_on; anytime a Monitor is saved, update the appropriate Outlet model with the date. Then, you wouldn't have to query Monitors at all.
You could also consider caching that page fragment, and let it expire daily.