Rails .count and .each give a different number of results - ruby-on-rails

I'm working with a simple rails app locally for my own learning.
In my view, I have the following code;
Reviews (<%= #reviews.count %>)
<% if #reviews.any? %>
<% #reviews.each do |review| %>
This is one review <br />
<% end %>
<% end %>
Despite this seeming quite simple, the output is as follows;
Reviews (2)
This is one review
This is one review
This is one review
This seems to contradict itself on what should be a very simple pair of operations on the same array.
For reference, the array is built in the controller, using the following code;
class PlacesController < ApplicationController
def show
#place = Place.find(params[:id])
#reviews = #place.reviews
#title = #place.name
end
end
What might be the problem?

I would venture to answer: try to change code inside controller to:
#reviews = #place.reviews.uniq
And check the result.

Seems to fix it. Any idea why? – Matthew Higgins
SQL Inner joins took a place here :)
They produces a duplicate entities. You can ensure, by modifying your controller:
def show
#place = Place.find(params[:id])
sql_query = #place.reviews.to_sql
render text: sql_query
end
You'll see sql query in browser. Then execute this query in you database manager, and you'll see the duplicated results set.

I don't like answering my own question, but I think it's important to explain what I found out incase anyone else ends up in the same place.
As Vitalyp suggested, replacing #reviews = #place.reviews.uniq with #reviews = #place.reviews.uniq produced the correct number of rows, but I was struggling to work out why, when opening the table clearly showed there were only two records.
It turned out there was another model, one I had previously tried using, to create a many-to-many association, which was confusing matters. It would appear that as I hadn't fully removed it after deciding not to use it, and when I completely destroyed the model, it's tables and anything that referenced it, things went back to normal.
It would appear that review 1 was associated with the place twice, once directly and once via the old many-to-many table, so it appeared twice.
It doesn't make a huge amount of sense when I had deleted the has_many:, but I guess it is a peculiarity of how Rails works.

Related

Rails adds extra empty element to end of ordered list when iterating through array

I have a list of 4 comments I want to iterate into an ordered list to create 4 lines of comments.
My ERB partial:
<ol>
<% #post.comments.each do |c| %>
<li><%= c.body %></li>
<% end %>
</ol>
However, when it is being rendered on the page it looks as follows:
1. Foo
2. Bar
3. Baz
4. Foobar
5.
It seems to be adding an extra empty element at the end of the loop cycle.
What seems to be the issue with how I'm iterating through the array?
Please check how many comments you have associated with the post like below,
#post.comments.count
There will be 5 records. Delete the last one and add validation in comment model by below,
validates_presence_of :body
That should keep it from producing blank records. Let me know if that is not the case.
After some digging, I found out my answer.
I failed to mention in my question that I also had a form_for a new comment on the same page that listed comments.
On my Post controller, I had it set up as #comment = #post.comments.new on that action. Which, in turn created an empty, unsaved comment on that view for #post. So, on that action I changed it to a generic #comment = Comment.new and then manually passed in the ids that I needed to make the association in the end. I don't know if that's the "best" way, but it is the way it worked for me.

Rails iterate over only the persisted records associated to an object

I have something like this in my show action:
def show
#blog = Blog.find(params[:id])
#new_comment = #blog.comments.build
end
Now in the view I do two things:
I render the _form.html.erb for the Comment so that the user can add a comment to this blog
I want to be able to iterate over the existing comments for this blog in order to display them.
I am having problems with number 2. The issue is that as I iterate over the associated comments with this:
<% #blog.comments.each do |comment| %>
It is grabbing that #new_comment while iterating over the associated blogs. How can I exclude that built-but-not-yet-persisted #new_comment?
It would be nice if something like #blog.comments.each_with_id worked, or even #blog.comments.persisted.each. Ultimately I only want to iterate over the persisted comments associated to this blog.
I would prefer not to have to nest a conditional that asks if the comment is persisted?. I'm hoping there is an iterator out there for my current situation that just grabs the persisted records.
How about if you create the new comment without using the association to the blog instance? Then it won't show up when you iterate through #blog.comments.
def show
#blog = Blog.find(params[:id])
#new_comment = Comment.new(blog: #blog)
end
in your view loop, you can skip if comment.new_record?
<% #blog.comments.each do |comment| %>
<% next if comment.new_record? %>
EDIT per your comment:
if you dont want filter out during the iteration, you can reject new records before iterating. however, i wouldn't recommend this approach, as youre creating an entirely new array of records for little reason. Your performance shoundnt really take a hit assuming blogs dont have thousands of comments, but its still not a great practice.
<% #blog.comments.reject(&:new_record?).each do |comment| %>
if you truly want to separate the logic from the view and controller, you can make another variable entirely dedicated to blog comments, prior to building a new one, so that its not included during the iteration.
# controller
def show
#blog = Blog.find(params[:id])
#current_comments = #blog.comments
#new_comment = #blog.comments.build
end
#view
<% #current_comments.each do |comment| %>
for what its worth, i'd still recommend the first apprach
You could add a class method to your Comment model such as this:
def self.persisted
reject { |comment| comment.new_record? }
end
Then call
#blog.comments.persisted
The downside is that it's not after this you don't have an ActiveRecord::Relation anymore and might break your scopes chaining. Make sure you're using it last in your ActiveRecord queries.

Ruby Find last record value in has many

I'm trying to find the last Econ_Result that belongs to a Econ_Report. I want to display the last record of the Econ_Result (ordered by "release_date") for each Econ_Report on the index view. In the controller I tried to take the list of all reports and find the last result using the following:
#econ_reports = EconReport.all
if #econ_reports.econ_results.size >= 1
#last_result = #econ_report.econ_results.last.release_date
end
econ_report.econ_results.size works on the index view when I place it in for each loop. When I try to call the value of the last record I run into issues with the fact that some reports don't yet have results (a temporary issue) so I threw in the if then check in the controller which is currently failing.
Thanks in advance for the rookie help.
Since #econ_reports is a collection of EconReport objects, you can't call an instance method like .econ_results on it. Instead, you can only call it on instances within the collection:
#econ_reports.each do |econ_report|
if econ_report.econ_results.any?
last_result = econ_report.econ_results.last
end
end
However, this can be terribly inefficient for a large collection of #econ_reports: both lines with econ_report.econ_results will query the database separately, meaning that you'll query the database independently for each econ_report in the collection. This is known as the N+1 query problem.
Luckily for you, as discussed in the link, Rails has a built-in solution to optimize this code so you'll only query the database once:
<% #econ_reports.includes(:econ_results).each do |econ_report| %>
<% if econ_report.econ_results.any? %>
<% last_result = econ_report.econ_results.last %>
# do something to display last_result
<% end %>
<% end %>
If you just want the release date you might try:
#last_result = #econ_report.econ_results.order('release_date DESC').limit(1).pluck(:release_date).first
It's worth noting that a Ruby if statement generally looks like:
if condition
end
The then is almost always omitted even though it is allowed.

Is this way of calling object supposed to be bad practice when considering loading speed?

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

Ruby on Rails: Adding a user for login to my existing app killed my index

I have an app that I've been developing that has so far only been a single user environment. I finally got to the point where I needed to add multi-user capabilities so I followed the railscast on authlogic to get a simple login.
I then added a has_many :items and accepts_nested_attributes_for :items and a belongs_to :user to the correct models. I then dropped the database and setup and then migrated it. I also added a user_id column to all my nested models.
After that, when I click on the "Create new item" link, I go to the new page and create a new item. When I go back to the item_index page, it's not showing up anymore. I can go to localhost/item/1 and see the record, so I know that it's being created, but when I try to view it in my item_index.html.erb it doesn't show up anymore.
Here's the basic loop that was working before I added the user. (It's rendering into a table)
<% for item in #items %>
<%= link_to item.name, item %>
<% end %>
I imagine that the loop is what's wrong, but I'm not entirely sure.
Thanks
edit: Here's what's happening in my index method in my item controller:
def index
#items = Item.search params[:search]
if #items.nil?
#items = Item.all
end
end
I have the weird if nil? thing because I'm using thinking-sphinx and it was failing sometimes if the index was empty.
edit2:
If I change the index to have just
def index
#items = Item.all
end
Everything shows up. So that means that it has to do with thinking sphinx messing with my render
edit3: in thinking-sphinx fashion, I did some things unrelated to it, and it magically works again.
Thinking Sphinx is going to be returning an empty result set [], when you try to iterate over this empty set you're not going to get any items shown.
To my knowledge, Thinking Sphinx will never return nil for a search result.
Perhaps try this instead:
if #items.empty?
#items = Item.all
end

Resources