Ruby on Rails website is slow; could it be the SQL queries? - sql
I have been working on a web application made with Ruby on Rails. I've "completed" the site, but it runs very slow and pages sometimes take on the order of ten seconds to load. I've broken this post up into three sections:
Overview
Diagnosis
Possible Ideas
Overview
As a very rough overview, this website displays personal projects, each of which have their own page. Each project (which I call a “post” in the code) is stored in a table. I’m not sure if it’s a bad idea to store a whole post in a database as some posts have a decent amount of text and images in their body. Each entry in the posts table has the following attributes:
# == Schema Information
#
# Table name: posts
#
# id :integer not null, primary key
# title :string
# body :text
# description :text
# slug :string
# created_at :datetime not null
# updated_at :datetime not null
# image_file_name :string
# image_content_type :string
# image_file_size :integer
# image_updated_at :datetime
# thumbnail_file_name :string
# thumbnail_content_type :string
# thumbnail_file_size :integer
# thumbnail_updated_at :datetime
# published :boolean default("f")
# published_at :datetime
#
Diagnosis
I’m using Heroku to host the application. I’ve upgraded to Hobby level dynos, so I could have access to some of the metrics they provide, like throughput, memory usage, etc. I don’t think Heroku is what is making the website slow, but rather my code. In an effort to debug, I've added a third party addon, Scout, that tracks the bottlenecks in the application.
Scout offers a trace feature that quantizes which code paths are taking the most time in the application. You can see below (in the bottom half of the picture) that there are a significant amount of traces that take upwards of ten seconds. Not very good…
When I click on the second line (6/17 2:04 PM), it gives a breakdown of the response time:
Expanding the SQL statements shows that most of the time intensive queries are from acting on the posts database, and sometimes sorting/ordering the posts (see below).
Possible Ideas
Is there anything obvious I am doing wrong here? I am pretty stuck, and not sure how to speed things up. Given what Scout is saying, I have two ideas:
Controller/SQL queries the controller invokes are slow
Embedded Ruby code in HTML is slow.
Controller/SQL queries the controller invokes are slow:
The code below shows the code where I am assigning #posts in the PostsController. The home method is run when the user visits the home page, and the index method is run when the user goes to the posts page. Could these queries be slow because there is a fair amount of data in the database (5 posts worth of text and images)?
class PostsController < ApplicationController
#before_action :authenticate_user!
before_action :set_post, only: [:show, :edit, :update, :destroy, :publish, :unpublish]
def home
#posts = Post.all.published.order('published_at DESC')
end
# GET /posts
# GET /posts.json
def index
if user_signed_in? and current_user.admin
#posts = Post.all.order("id")
else
#posts = Post.all.published
end
end
Embedded Ruby Code in HTML is slow:
I am using Ruby in some of my HTML code to sort the posts by date and to determine the most recent post. For example, in the sidebar of the website (to the left of the home page), there is section that displays “Recent”, and the logic behind that is:
<h4 style="border-bottom: 1px solid #bbb">Recent</h4>
<% #post=Post.all.published.order("published_at").last %>
<% if #post == nil or #post.published_at == nil %>
<div class="temp_sidebar">Coming Soon!</div>
<% else %>
<%= render partial: "layouts/media", locals: {post: #post} %>
<% end %>
Similarly, in the “Archives” section of the sidebar, I’m sorting the posts by date, and am doing this logic:
<% if Post.published.length != 0 %>
<h4>Archives</h4>
<div id="sidebar" class="list-group">
<% #published_posts = Post.published %>
<% archives = Hash.new(0) %>
<% #published_posts.each do |post| %>
<% if archives[post.date] == 0 %>
<% archives[post.date] = 1%>
<% else %>
<% archives[post.date] += 1 %>
<% end %>
<% end %>
<% archives.each do |key, value| %>
<button class="accordion"><%= key %> <span class="badge"> <%= value %></span></button>
<div class="panel">
<% #published_posts.each do |post| %>
<% if post.date == key %>
<p><%= link_to post.title, post_path(#post) %></p>
<% end %>
<% end %>
</div>
<% end %>
</div>
<% end %>
My idea is that maybe iterating over posts is taking a very long time, but I’m not entirely sure. I feel like this is valid code that is ok to be used, but maybe something about it is very slow. Would you guys have any ideas here? It may also be good to note here that the app's memory usage is pretty high: around 500MB. Maybe these queries are slow because of all the data that is getting fetched, but that said, I'm am not quite sure what "a lot" of data is for a web app like this. And of course, my hypothesis as to why this site is slow could be totally wrong, so I'm very open to your thoughts. Lastly, if the SQL queries/code I am using is slow, are there ways I could speed it up/improve its performance? Thank you for any help in advance!
I see two problems: a lack of SQL indexes, and too many calls to Post.all.
Your slow queries involve WHERE published = ?. If posts.published is not indexed, this will have to scan the entire table, not just the published posts. You also tend to sort by posts.published_at and without an index this will also be slow.
To fix this, add an index on posts.published and posts.published_at in a migration.
add_index(:posts, :published)
add_index(:posts, :published_at)
You can read more about indexes in the answers to What is an index in SQL?
Using Post.all or Post.published means loading every post from the database into memory. If you're not using them all, it's a huge waste of time.
For example, it's unwieldy to display every post on your index and home pages. Instead you should use pagination to fetch and display only a page of posts at a time. There are gems for this such as kaminari and will_paginate as well as larger administrative solutions such as ActiveAdmin. And if you don't like page links, if you look around you can find examples of using them for "infinite scroll".
Finally, you can add caching. Since your site isn't going to update very frequently, you can cache at various levels. Have a read through Caching with Rails: An Overview.
But caching brings its own problems. Consider if you need it after you do basic performance optimizations.
Schwern,
Thank you for the pointers! With the help of your post, I was able to minimize the number of calls to Post.all. These were killing my response time big time. I did not realize that calling Post.all loaded the all of the posts and their attributes to memory (it's a learning process haha).
For places where I did not need to load every attribute of a post, I ended up doing Post.select("attribute"). For example, originally in the home method of the PostsController, I had:
def home
#posts = Post.all.published.order('published_at DESC')
end
Running Post.all.published.order('published_at DESC') in the rails console shows that this query takes approximately 4 seconds:
Updating the home method to use Post.select, as opposed to Post.all (as shown below) significantly improved the response time of the query.
def home
#SLOW: loads everything from a post
##posts = Post.all.published.order('published_at DESC')
#FAST: only loads the necessary elements from a post, and does not waste time loading body (body = lots of data)
#posts = Post.select("id", "title", "description", "slug", "created_at", "updated_at", "image_file_name", "thumbnail_file_name", "published", "published_at", "date").published.order('published_at DESC')
end
Running #posts = Post.select("id", "title", "description", "slug", "created_at", "updated_at", "image_file_name", "thumbnail_file_name", "published", "published_at", "date").published.order('published_at DESC') in the rails console shows that this query takes approximately 2ms! Huge improvement! This is because I am not loading the body attribute, among other things of the post, which contain a lot of data, and thus consume a lot of time to load into memory. No point of loading it into memory if you're not going to use it!
In a similar spirit, I was able to improve the performance of the other sections of code (see below for fixes) and ultimately make the website have significantly faster response times!
Controller fix:
class PostsController < ApplicationController
#before_action :authenticate_user!
before_action :set_post, only: [:show, :edit, :update, :destroy, :publish, :unpublish]
def home
#SLOW: loads everything from a post
##posts = Post.all.published.order('published_at DESC')
#FAST: only loads the necessary elements from a post, and does not waste time loading body (body = lots of data)
#posts = Post.select("id", "title", "description", "slug", "created_at", "updated_at", "image_file_name", "thumbnail_file_name", "published", "published_at", "date").published.order('published_at DESC')
end
# GET /posts
# GET /posts.json
def index
if user_signed_in? and current_user.admin
#SLOW loads everything from a post
##posts = Post.all.order("id")
#FAST: only loads the necessary elements from a post, and does not waste time loading body (body = lots of data)
#posts = Post.select("id", "title", "description", "slug", "created_at", "updated_at", "image_file_name", "thumbnail_file_name", "published", "published_at", "date").order('id')
else
#SLOW loads everything from a post
##posts = Post.all.published
#FAST: only loads the necessary elements from a post, and does not waste time loading body (body = lots of data)
#posts = Post.select("id", "title", "description", "slug", "created_at", "updated_at", "image_file_name", "thumbnail_file_name", "published", "published_at", "date").published
end
end
Embedded Ruby Code in HTML fix:
"Recent" section fix:
<h4 style="border-bottom: 1px solid #bbb">Recent</h4>
<!-- LINE BELOW IS SLOW!!! (to test, uncomment line, and embrace in "<% %>") -->
<!-- #post = Post.all.published.order(:published_at).last -->
<!-- FAST! Line below replaces line above and is much faster! -->
<% #post = Post.select("id", "title", "description", "slug", "created_at", "updated_at", "image_file_name", "thumbnail_file_name", "published", "published_at", "date").published.order('published_at DESC').first %>
<% if #post == nil or #post.published_at == nil %>
<div class="temp_sidebar">Coming Soon!</div>
<% else %>
<%= render partial: "layouts/media", locals: {post: #post} %>
<% end %>
"Archives" section fix:
<!-- LINE BELOW IS SLOW!!! (to test, uncomment line, and embrace in "<% %>") -->
<!-- if Post.published.length != 0 -->
<!-- FAST! Line below replaces line above and is much faster! -->
<% if Post.select("id").published.count("id") != 0 %>
<h4>Archives</h4>
<div id="sidebar" class="list-group">
<!-- LINE BELOW IS SLOW!!! (to test, uncomment line, and embrace in "<% %>") -->
<!-- #published_posts = Post.published -->
<!-- FAST! Line below replaces line above and is much faster! -->
<% #published_posts = Post.select("id", "title", "date").published %>
<% archives = Hash.new(0) %>
<% #published_posts.each do |post| %>
<% if archives[post.date] == 0 %>
<% archives[post.date] = 1%>
<% else %>
<% archives[post.date] += 1 %>
<% end %>
<% end %>
<% archives.each do |key, value| %>
<button class="accordion"><%= key %> <span class="badge"> <%= value %></span></button>
<div class="panel">
<% #published_posts.each do |post| %>
<% if post.date == key %>
<p><%= link_to post.title, post_path(post.id) %></p>
<% end %>
<% end %>
</div>
<% end %>
</div>
<% end %>
Related
trying to use a news API with rails application
I'm trying to get information from newsapi. I've used the basic structure i found elsewhere for another api to start with but i'm very new to this and not sure what is wrong/how to fix etc. all i'm trying to do is display the titles of the articles on my index page. At the moment, everything displays on the page, like title, author etc as an array but i just want to narrow things down and the syntax for doing so in the view. i've changed my api key to '?' for the time being(i know it should be in the .env file).ive looked at a lot of docs but i cant seem to find an answer. apologies if this is a broad question. class TestsController < ApplicationController require "open-uri" def index url = 'https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=????????????????????????' article_serialized = open(url).read #articles = JSON.parse(article_serialized) end end <% #articles.each do |article| %> <%= article[1] %> <% end %>
This should do it <% #articles["articles"].each do |article| %> <%= article["title"] %> <% end %> Edit : This is what my ruby script looks like that I used for testing require 'json' file = File.read 'output.json' data = JSON.parse(file) data["articles"].each do |item| print item["title"] end
Rails 3.2 One to Many View
I have tried to get this working, looked at multiple tutorials, questions on here tried different things for about a week now and I can't get the view to work correctly. I have teams of users. A team has_many users and a user belongs_to a team (one team at a time). I know the association works because I got it working using the console (with some help there). I'm not sure how to get it working in the view. Below is the code, please let me know if more is needed. What am I missing? _join_team_button <%= form_for(#user) do |f| %> <%= f.submit "Join Team", class: "btn btn-large btn-primary" %> <% end %> Team Show Page <%= render 'shared/join_team_button %> Teams Controller def show #team = Team.find(params[:id]) #team_members = #team.users #user = current_user.users.build if signed_in? end Users Controller def show #user = User.find(params[:id]) #teams = #user.team end
I tried to put a complete demonstration of what you are looking for. Let me know if it fits for you. #FILE: models/team.rb class Team < AR::Base has_many :users end #FILE: models/user.rb class User < AR::Base belongs_to :team end #FILE: config/routes.rb #Here you are defining "users" as a nested resource of "teams" resources :teams do resources :users do member do put :join end end end #if you run "rake routes" it will show you the following line along with others join_team_user PUT /teams/:team_id/users/:id/join(.:format) users#join #FILE: controllers/team_controller.rb def show #team = Team.find(params[:id]) #team_members = #team.users #user = current_user.users.build if signed_in? end #FILE: views/teams/show.html.erb <% if(#user) %> <%= form_for #user, :url => join_team_user_path(#team, #user) do |f| %> <%= f.submit "Join Team", class: "btn btn-large btn-primary" %> <% end %> <% end %> #You dont really need a form for this. You can simply use `link_to` like below <%= link_to 'Join', join_team_user_path(#team, #user), method: :put %> #FILE: controllers/users_controller.rb def join # params[:id] => is the user_id #user = User.find(params[:id]) # params[:team_id] => is the team_id #team = Team.find(params[:team_id]) # Now make the relationship between user and team here. #user.update_attribute(:team, #team) end Update:Based on your comment Q: Do I create a new user's resource and nest that or do I nest the already establishes user's resource? Ans: Based on your requirements any resource can be defined both independently or nestedly. But yes you can control that which method will be available in which way. Like in your case, you can allow only join method when your user is nested under team resource. resources :users, :only=>:join do member do put :join end end resource :users run rake routes with and without :only=>:join option and see differences in available routes. Q: Will that affect other things? Ans: If you strictly define your routes following above example, it should not affect other things. You should confirm all the available routes to your application by rake routes. Q: Should I put my current routes.rb file up there? Ans: Assuming your current routes.rb will be modified in the above way. Could I answer the question? Q: Confused about the comments controller? Ans: Im extreamely sorry. Yes it must be users_controller.rb as the rake routes command is showing. Result of copy and paste from my own example code :P Q: what should I put there? the build method Ans: In your case both the user and team is already exists in your database. All you need to do is just setup a relationship. So you can just use update_attribute option. Ive changed the join method. Please check. But yes if want to create new entries you might need build methods. Sorry for the late reply :)
I figured it out. Still not perfect, but it gets the association working. The team, and the user were already created, I just needed to establish the association, so the build method would not have worked. Here's what I have: View: <%= form_for(#user) do |f| %> <%= f.hidden_field :team_id, :value => #team.id %> <%= f.submit "Join Team", class: "btn btn-large btn-primary" %> <% end %> Teams Controller: def show #team = Team.find(params[:id]) #team_members = #team.users #user = User.find(params[:id]) if signed_in? end
Rails 3.2 HTML5 multiple file upload, Carrierwave and no Javascript
Currently using Rails 3.2 and Carrierwave. I have multiple files setup, but it requires multiple file fields but I only want one file field. I will provide this as the default if the browser does not support the HTML5 multiple property. Controller def new #ad = Ad.new 5.times { #ad.images.build } // provides multiple file fields in the view. end def create ad = Ad.new(params[:ad]) user = User.find(session[:user_id]) if user.ads << ad flash[:notice] = "Ad successfully saved." redirect_to ad_listing_path(ad.id, ad.slug) else render :new, :alert => "Ad was not saved." end end View <%= f.fields_for :images do |a| %> <% if a.object.new_record? %> <%= a.file_field :image, :multiple => true %><br> <% end %> <% end %> If 5.times { #ad.images.build } is providing my multiple fields, what is the proper way to display 1 file field that accepts multiple?
This seems to be a popular issue with no good answers, so I'm going to completely answer it here. Before I start I will mention that the code is available at https://github.com/mdchaney/multi, but follow along to see how to do this in the easiest way possible. Before we go there, in HTML 5 a file input field can have the "multiple" attribute set. If it's set, the result is the same as having multiple file inputs of the same name. In Rails, setting "multiple: true" for the file input field built by the form builder will cause it to upload as an array of files. <%= f.file_field :files, :multiple => true %> becomes <input id="model_files" multiple="multiple" name="model[files][]" type="file" /> where "model" is the name of your model. This input control will (in Chrome, at least) have a label of "Choose Files" instead of "Choose File". CarrierWave cannot deal with this natively. It uses a single text field to store information about a single file, and some internal logic to determine where that file (and possibly its derivatives) are stored. It would be possible to hack it to put information for multiple files in a single text field, choosing an encoding with a set delimiter. This would require a lot of work and hacking on CarrierWave. I don't care to hack CarrierWave, though, so the issue turns into the fact that having multiple files attached to one item is actually a one to many relationship, or in Rails terms a "has_many". So it's possible to add the files from the file input field to multiple attached records using a simple attribute writer. With that, I present the simplest way to do this that uses an HTML 5 file input field with the multiple attribute set. There are ways to do it with jQuery and flash, but I am presenting this to show specifically how to do it with straight HTML 5. In our sample, we will have a simple model for "uploads", each of which will have a name and any number of linked files, which will be stored in another model called linked_files (making it easy, right?). The linked_file will hold the original filename, provided content type, and of course the field for CarrierWave to store its information. Let's create the scaffold for uploads and then just the model for linked_files: rails g scaffold Upload name:string rails g model LinkedFile upload:references filename:string mime_type:string file:string With that done, we can set limits if we wish on the fields and add the "not null" constraint: class CreateUploads < ActiveRecord::Migration def change create_table :uploads do |t| t.string :name, limit: 100, null: false t.timestamps end end end class CreateLinkedFiles < ActiveRecord::Migration def change create_table :linked_files do |t| t.references :upload, null: false t.string :filename, limit: 255, null: false t.string :mime_type, limit: 255, null: false t.string :file, limit: 255, null: false t.timestamps end add_index :linked_files, :upload_id end end Now, let's fix up the Upload model by adding a new attribute writer called "files": class Upload < ActiveRecord::Base has_many :linked_files, inverse_of: :upload, dependent: :destroy accepts_nested_attributes_for :linked_files, reject_if: :all_blank, allow_destroy: true validates_associated :linked_files attr_accessible :name, :files, :linked_files_attributes def files=(raw_files) raw_files.each do |raw_file| self.linked_files.build({filename: raw_file.original_filename, mime_type: raw_file.content_type, file: raw_file}) end end validates :name, presence: true, length: { maximum: 100 } end Most of that is the normal declarations for the Rails model. The only real addition here is the "files=" method, which takes a set of uploaded files in an array and creates a "linked_file" for each one. We need a CarrierWave uploader: class FileUploader < CarrierWave::Uploader::Base storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}" end end This is the simplest uploader possible, you may wish to restrict the type of file uploaded or whatever. Now, the LinkedFile model: class LinkedFile < ActiveRecord::Base mount_uploader :file, FileUploader belongs_to :upload, inverse_of: :linked_files attr_accessible :file, :filename, :mime_type, :file_cache, :remove_file validates :filename, presence: true, length: { maximum: 255 } validates :mime_type, presence: true, length: { maximum: 255 } end And that has nothing special, only added :file_cache and :remove_file as accessible attributes for the file uploader. We are now done except for the views. We really only have to change the form, but we'll also change the "show" to allow access to the uploaded files. Here's the _form.html.erb file: <%= form_for(#upload, { multipart: true }) do |f| %> <% if #upload.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(#upload.errors.count, "error") %> prohibited this upload from being saved:</h2> <ul> <% #upload.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <% if f.object.linked_files.size == 0 -%> <div class="field"> <%= f.label :files %><br /> <%= f.file_field :files, :multiple => true %> </div> <% else -%> <fieldset> <legend>Linked Files</legend> <%= f.fields_for :linked_files do |lff| -%> <div class="field"> <%= lff.label :filename %><br /> <%= lff.text_field :filename %> </div> <div class="field"> <%= lff.label :mime_type %><br /> <%= lff.text_field :mime_type %> </div> <div class="field"> <%= lff.label :file, 'File (replaces current selection)' %><br /> <%= lff.file_field :file %> <%= lff.hidden_field :file_cache %> </div> <div class="field"> <%= lff.check_box :_destroy %> Remove this file </div> <hr /> <% end -%> </fieldset> <% end -%> <div class="actions"> <%= f.submit %> </div> <% end %> I have added two sections of code. If the #upload object has no "linked_files" associated with it, I simply show the multiple file input. Otherwise, I show each linked_file with all its information. It would be possible to add a "files" method to Upload and handle it that way, but doing so would lose the mime type across requests. You can easily test this as the upload "name" is a required field. Start a server and go to http://127.0.0.1:3000/uploads to see the application. Click the "new" link, choose some files and hit "Create Upload" without providing a name. The next page will show all of your files now waiting. When you add a name everything is saved. Let's modify the "show" action to show the linked_files: <p id="notice"><%= notice %></p> <p> <b>Name:</b> <%= #upload.name %> </p> <p> <b>Files:</b><br /> </p> <table> <thead><tr><th>Original Filename</th><th>Content Type</th><th>Link</th></tr></thead> <tbody> <% #upload.linked_files.each do |linked_file| -%> <tr> <td><%= linked_file.filename %></td> <td><%= linked_file.mime_type %></td> <td><%= link_to linked_file.file.url, linked_file.file.url %></td> </tr> <% end -%> </tbody> </table> <%= link_to 'Edit', edit_upload_path(#upload) %> | <%= link_to 'Back', uploads_path %> In this I've simply added a header for "Files" and a table which shows all of them and provides a link for viewing. Nothing fancy, but it works. If I were making this into a real application I would probably also provide a list of files or minimally a count of files on the uploads index page, also. So that's it. Again, the entire test app is available at github if you want to download it, but I have put the entirety of my Rails generating statements and changes in this post.
Multiple uploads for one file field isn't really supported by HTML. You can get around it with some JavaScript plugins. Two that come to mind: Uploadify jQuery File Upload
Unfortunately, CarrierWave does not support the HTML5 multiple attribute (yet). https://github.com/carrierwaveuploader/carrierwave/issues/984
Rails search functionality
I am taking a rails class at my University and I am trying to create a search form which will show the results on the same page rather than show a different page of results. Is this something simple to do? I am creating a museum app with artifacts for each museum but I want the user to search artifacts from either page. On my routes.rb I have resources :artifacts do collection do get 'search' end end On my museum index I have the code below that he gave us but not sure how to tweak the get routes for the same page. <%= form_tag search_artifacts_path, :method => 'get' do %> <p> <%= text_field_tag :search_text, params[:search_text] %> <%= submit_tag 'Search' %> </p> <% end %> <% if #artifacts %> <p> <%= #artifacts.length %> matching artifacts. </p> <h2> Matching Artifacts </h2> <% #artifacts.each do |a| %> <%= link_to "#{a.name} (#{a.year})", a %><br /> <% end %> <% end %>
Yes, this is easy. Just have the index page return the search results if params[:search_text] is present - this way you don't need a new route or a different page. class ArtifactsController < ApplicationController def index #artifacts = Artifact.search(params[:search_text]) end end class Artifact < ActiveRecord::Base def self.search(query) if query where('name ILIKE ?', "%#{query}%") else all end end end So then your form looks like: <%= form_tag artifacts_path, :method => 'get' do %> <p> <%= text_field_tag :search_text, params[:search_text] %> <%= submit_tag 'Search' %> </p> <% end %> Edit: So what you really want to do is any page you want to search, include a form which makes a request to that same page. Then in each of those controller methods just put this line of code: #artifacts = Artifact.search(params[:search_text]) and that will populate the #artifcats array with only artifacts that match the search query.
Try using "Ransack" gem. It can also perform some more powerful searches.
Issues getting a profile to update and show newly submitted form item
Following up on a previous question, I have a few issues to resolve before I have a comment form showing and submitting securely on my profile. I'm a beginner to programming so thinking across multiple controllers seems to have me lost. What I'm doing is posting comments in a form, then listing them. Background: The _comment_form and _comment reside as partials in the Profile about. (My next task is toggling from about to other Profile information, but that's another question altogether.) Using the help provided in my last question I feel like I'm almost there but am getting an error. CreateComments migration: t.integer :profile_id t.integer :author_id t.string :body My Comment model: class Comment < ActiveRecord::Base belongs_to :profile belongs_to :author, :class_name =>"User", :foreign_key => "author_id" end CommentsController: def create #comment = Comment.new(params[:comment].merge(:author_id => current_user.id)) #comment.save! redirect_to profile_path(#comment.profile) end ProfilesController: def create #profile = Profile.new(params[:profile]) if #profile.save redirect_to profile_path(#profile), :notice => 'User successfully added.' else render :action => 'new' end end def show #user = User.find(params[:id]) #profile = #user.profile #comment = #profile.comments.new end Comment partials inside Profile partial: <div id="commentEntry"> <%= render :partial => 'comment', :collection => #profile.comments %> </div> <div id="newitem"> <%= render :partial => 'comment_form' %> </div> Routes.rb: resources :users do resources :profiles end resources :comments _comment_form.html.erb: <%= form_for #comment do |f| %> <%= f.text_field :body %> <%= f.submit 'Add new' %> <% end %> _comment.html.erb: <li class="comment" title="<%= #comment.author.profile.first_name %> <%= #comment.author.profile.last_name %>"> <%= #comment.body %> </li> So, Issue #1: Wrapping the _comment.html.erb in a loop <% for #comment in #user.profile.comments %> shows the profile but when I try and submit a new comment I get "Unknown action The action 'update' could not be found for CommentsController". If I take away the loop, the profile doesn't show and I get "NoMethodError in Profiles#show undefined method `profile' for nil:NilClass". Can anyone help me out and explain what I'm doing wrong? Issue #2: I created a sample comment in rails console and when I get the profile to show, the input field for comment :body repopulates with the comment's body. Any ideas on what could be going on?
Short explanation of your problem: The #comment you're getting in your _comment_form partial is one that's already saved in your database, hence the call to the update action and the body that's already filled. You're creating the new comment just fine with #comment = #profile.comments.new in your show action, but it gets overridden somewhere else. You're mentioning that you wrapped the _comment render in a loop with <% for #comment in #user.profile.comments %>, the problem is most likely there. Fix: The only thing you should have to change is the _comment partial to (without the for loop that you added): <li class="comment" title="<%= comment.author.profile.first_name %> <%= comment.author.profile.last_name %>"> <%= comment.body %> </li> When you do the render :partial => 'comment', :collection => #profile.comments, rails is smart enough to loop over #profile.comments and give the comment (not #comment) variable to the partial. How to avoid this the next time: I'll give you two rules of thumb to avoid getting in this situation: Try to name your variables more precisely. #new_comment would have been a better name for the variable to store the new comment. #comment is a bit ambigous as you've got a boatload of those in your view. Avoid creating and modifying instance variables (# variables) in your views, try to do this only in your controller. I'll admit your particular case was a bit harder to detect because of the <% for #comment in #user.profile.comments %>. The view got its name for a good reason, it's only supposed to let you view the data you've defined in your controller. Hope this helps.