Currently have a very inefficient view partial that lists groups of users by their predicted score.
group controller
def show
#sfs_ordered = ScoreFootballSimple.order("home_score DESC, away_score ASC")
#live_games = Game.find(:all, :conditions => ['kickoff < ? AND completed != true AND game_state is NOT NULL', Time.now])
group#show (relevant section)
<% #live_games.each do |game| %>
<% #sfs_ordered.each do |sfs| %>
<% got_this_score = Array.new %>
<% game_points = nil %>
<% #group.members.each do |member| %>
<% if pred = member.prediction_set.predictions.where('game_id = ?',game.id).first %>
<% game_points = pred.points if !pred.points.nil? && pred.score_type == sfs.score_type %>
<% got_this_score << member.user.user_detail.display_name if pred.score_type == sfs.score_type %>
<% end %>
<% end %>
<% if got_this_score.count > 0 %>
<tr><td><%= sfs.home_score %>-<%=sfs.away_score%></td>
<td><% if !game_points.nil? %>
<div class="preds-show-points-div"><%= game_points %>pts</div>
<% else %>
-
<% end%></td>
<td><%= got_this_score.to_sentence %></td></tr>
<% end%>
<% end %>
<% end %>
Obviously this is loops within loops that means the that for every #sfs_ordered (round 50 records) it is iterating over every group member (for the largest group about 5000) and this means the page is taking seconds to load.
Don't flame me, this was the POC to show how it could look but it has exposed my lack of ability with ActiveRecord. Now I could go around creating a hash of users, predictions sets etc but I wondered if anyone could point me to a better way of selecting information with more precision using Rails queries.
The entity relationship is something like this
Group has many Member
Member belongs to User and PredictionSet
PredictionSet has many Prediction
Prediction belongs to Game and ScoreType
ScoreTypeSimple has one ScoreType
The predicted score is in ScoreTypeSimple - this is how I want the list organised
e.g 1:1 - Joe, Fred and Jane
1:0 - Sue, Dave and Helen
Then I want to grab the Group.member.user.name for the Group.member.prediction_set.prediction - where the prediction.game.id == game.id AND score_type == sfs.score_type
I know I can improve this by pure SQL joins and INs and build hashes but I wondered if any one could give me any pointers if there was a Rails/Ruby efficient way of doing this. I know the answer is probably out there in lambda but my ActiveRecord knowledge is stretch to the limit here!
Any help gratefully received.
Peter
You might be able to benefit from eager loading, since you're displaying all of some associated objects. This is done using the .includes method on a relation. For instance, your view works with all the members of a group, so when you fetch the group from the database (I don't see the line that does this in your code, but it probably looks something like
#group = Group.where('some condition').first
If you instead use this:
#group = Group.includes(:members => [:user]).where('some condition').first
Then loading the group, all it's members, and all their user objects is 3 database queries, rather than (in your extreme case of 5000 members) 10,001.
I'd say this is, at best, a small part of your solution, but it may help.
Edit: Here is the RailsCast on eager loading, and there's some docs about half way down this page.
Related
I don't even know how to ask this correctly, so pardon me if my question is confusing. I have a Table called Colors -
I would like to iterate a list of all the colors in the table, but presorted by 'color-family' so the result would look something like this:
Colors:
Reds -
Maroon
Stop Sign
Yellows -
Canary
Blues -
Sky Blue
Royal Blue
Greens -
Neon Green
But I have no idea the sql syntax to collect and sort that kind of information.
Surprisingly, this code didn't work ;)
<% #group = Colors.presort_by("color-family") %>
<% #color = Colors.all %>
<% #group.each do |group| %>
<%= group.color-family %>s - <br>
<% if #color.name.has_a_column_that_is_also_the_#group.color-family %>
<%= #color.name %>
<% end %>
<% end %>
lol. As you can see, I have no real idea where to start. Any help would be greatly appreciated. I don't have any sql query experience whatsoever.
Thanks again!
I'm not sure if there's a way to do this using just ActiveRecord query methods. But it can easily be done in-memory using Ruby's Enumerable#group_by, and the performance will be good unless it's a large data set (in which case you'd probably want to cache the results, or look into a more complex SQL query)
#colors = Color.all
grouped = #colors.group_by { |color| color.color_family }
# or .group_by &:color_family
This will return a hash where keys are color family (strings) and values are arrays of Color records.
I have a model called Note. Each note belongs_to :call_reason. And call_reason has_many :notes.
What I want to do in a view is display a list of call_reasons and a total count of each next to it so we can see what the most popular call reasons are.
Here's what I have so far:
dashboard_controller:
def index
#notes = Note.all
end
dashboard view:
<% #notes.each do |n| %>
<%= n.call_reason.reason %>
<% end %>
This lists all notes' call_reasons.
I'm stumbling on how to list each call_reason once with a total count next to it. What I have now just lists all the call_reasons per note which is a mess. I think I could scope this out somehow or change the instance variable but I'm having a hard time getting it right.
Any thoughts?
Since you want to list call reasons, you should start with that:
def index
#call_reasons = CallReason.all
end
Then in your view you can do this:
<% #call_reasons.each do |call_reason| %>
<%= call_reason.reason %> <%= call_reason.notes.count %>
<% end %>
Note that this will perform a query for every call reason in your database. To prevent this you can use a counter cache. Check out the section on counter cache on Rails Guides too.
Still new to Rails. I'll try to provide as much detail as possible.
I have a form that lets me update multiple records at one time.
It's based off the 'Editing Multiple Individually' Railscast episode.
<%= form_tag(auction_clerk_path(#auction), :method => :put) do %>
<% #lots.each do |lot| %>
<%= fields_for "lots[]", lot do |f| %>
<%= f.number_field :sale_price %>
<% end %>
<% end %>
<% end %>
(Simplified to just include a single input for each instance)
An Auction contains multiple Lots (items for sale).
The auction_clerk_path is the route I'm using to just show all lots on one auction.
Everything is working just fine... until I use try to customize my lot paths...
I've added the following to my lot.rb file to be able to use:
/auctions/:auction_id/lots/:lot_number
instead of /auctions/:auction_id/lots/:id
def to_param
lot_number
end
So, in the form mentioned earlier, the fields render with name="lots[12][sale_price]" where 12 is the id.
However with the to_param change, now the fields render with name="lots[1][sale_price]" where 1 is the lot_number.
When I save, the submitted parameters are lot_numbers instead of ids.
So obviously when it tries to update, it won't find the correct records.
My method definition looks like this:
def save_clerking
#updated_lots = Lot.update(params[:lots].keys, params[:lots].values).reject { |l| l.errors.empty? }
if #updated_lots.empty?
flash[:notice] = "Lots updated"
redirect_to auction_clerk_path(#auction)
else
render :action => "clerk"
end
end
I either need to change my method definition to lookup by lot number, or change the form to somehow output IDs in the first place... but I don't know how.
Any help is appreciated. Thanks!
Fixed this through some help on another question.
I changed my method def to
#updated_lots = []
params[:lots].each do |lot_number, attributes|
lot = Lot.where("lot_number = ? AND auction_id = ?", lot_number, params[:auction_id]).first
if lot.update_attributes(attributes)
#updated_lots << lot
end
end
You could fetch the ids by lot number in the controller action and feed those to the update method instead of the params keys.
I have a ruby on rails 3 project in which I query for a certain number of objects by using a .limit(3) . Then, in my view, I loop through these objects. After that, if there are 3 objects in the view, I display a "load more" button. Here is the view code:
<% #objects.each do |object| %>
<%= render object._type.pluralize.underscore + '/teaser', :object => object %>
<% end %>
<% if #objects.size(true) == 3 %>
#load more link here
<% end %>
The size(true) is passed a boolean to ensure that mongoID takes into account the .limit and .offset on my query (otherwise it returns the total number of objects that matched, regardless of the limit / offset). Here are the relevant development log lines:
MONGODB project_development['system.indexes'].insert([{:name=>"_public_id_1", :ns=>"project_development.objects", :key=>{"_public_id"=>1}, :unique=>true}])
MONGODB project_development['objects'].find({:deleted_at=>{"$exists"=>false}}).limit(3).sort([[:created_at, :desc]])
#some rendering of views
MONGODB project_development['system.indexes'].insert([{:name=>"_public_id_1", :ns=>"project_development.objects", :key=>{"_public_id"=>1}, :unique=>true}])
MONGODB project_development['$cmd'].find({"count"=>"objects", "query"=>{:deleted_at=>{"$exists"=>false}}, "limit"=>3, "fields"=>nil})
My question is: does MongoID do a separate query for my #objects.size(true)? I imagine the ['$cmd'] might indicate otherwise, but I'm not sure.
I don't think so, there was a pull request month ago to add aliases for :size, :length to :count to avoid re-running queries. You can check that.
I've spent 3 days trying to write scopes in models. At this point i don't care about producing the optimum solution... i just want to get this to work. In a rails 3 app i have the following code in the controller.
#questions = Question.all
#ans = Answer.where(user_id = current_user.id)
#answers = #questions.map { |q| [q, #ans.find_by_question_id(q.id)] }
Each answer record has a question_id field so it can be linked to the appropriate question. I am trying to get the answers array to list out in the same order as the questions.
The following code in a view renders the answers but not in the correct order.
<% #ans.each do |q| %>
<%=q.score%><br/>
<% end %>
I then changed the array to the mapped array which should produce the answers in the appropriate order.
<% #answers.each do |q| %>
<%=q.score%><br/>
<% end %>
I get the following error:
undefined method `score' for #<Array:0x10335ef90>
Any help is appreciated. Thanks.
Instead of q.score you probably want q[1].score, since q is really a two-item array of the question and answer. The second item (q[1]) will give you the answer.
This is because q is not an Answer, but an array containing both a Question and an Answer. So you could just change it to q[1].score. A nicer way is to break out the array in the block variables, like so (note the parentheses):
<% #answers.each do |(question, answer)| %>
<%= answer.score %><br/>
<% end %>