Rails 3 Create method using nested resources? - ruby-on-rails

How can I clean this up using rails 3 features? I have a post that belongs to a group and also a user. The group and user has_many posts. I am using a nested resource
resources :groups do
resources :posts
end
<%= form_for #post, :url => group_posts_path(params[:group_id]) do |f| %>
....
<% end %>
def create
#group = Group.find(1)
#post = #group.posts.build(params[:post])
#post.user_id = current_user.id
respond_to do |format|
if #post.save
.....
end
end
end
Thank you.

Use the accepts_nested_attributes_for method in your model.
If you're not familiar with nested forms check out this railscast and the second part for further information.

In view:
<%= form_for [#group, #group.posts.build] do |f| %>
...
<% end %>
In controller:
class PostsController < ApplicationController
before_filter :find_group
...
def create
#post = #group.posts.build(params[:post])
current_user.posts << #post
end
protected
def find_group
#group = Group.find(params[:group_id])
end
end

Related

Ruby rails. Undefined method error, creating nested resource object

I am trying to render a form to upload a song.
I have a playlist model and a song model, song being a nested resource of playlist.
With simple form, when I try and render the new song partial, I get undefined method songs_path for #<#<Class:0x007fdc51980870>
In routes, the new song path is new_playlist_song but it doesn't seem that simple form knows this.
Songs controller:
class SongsController < ApplicationController
def new
#song = Song.new
end
def create
#song = Song.new(song_params)
if #song.save
flash[:info] = "Song uploaded sensually"
redirect_to user_path(current_user)
else
render 'new'
end
end
def index
#song = Song.all
end
private
def song_params
params.require(:song).permit(:audio)
end
end
Playlists controller:
class PlaylistsController < ApplicationController
before_action :find_playlist, only: [:edit, :update, :destroy]
def index
#playlists = Playlist.all
end
def new
if user_signed_in?
#playlist = current_user.playlists.new
else
redirect_to(root_url)
flash[:danger] = "You have to register to purchase gigs"
end
end
def create
#playlist = current_user.playlists.new
#playlist.user_id = current_user.id
if #playlist.save
redirect_to new_playlist_path
else
render 'new'
end
end
def destroy
#playlist = Playlist.find(params[:id])
if #playlist.present?
#playlist.destroy
redirect_to playlists_path
end
end
private
def find_playlist
#playlist = Playlist.find(params[:id])
end
end
Song model:
class Song < ActiveRecord::Base
belongs_to :playlist
has_attached_file :audio
validates_attachment_presence :audio
validates_attachment_content_type :audio, :content_type => [ 'audio/mp3','audio/mpeg']
end
Playlist model :
class Playlist < ActiveRecord::Base
belongs_to :user
has_many :songs
end
Routes:
resources :playlists do
resources :songs
end
User profile link to create new song:
<p> <%= #user.playlist.user_id %> </p>
<p> <%= #user.playlist.created_at %> </p>
<% if #user == current_user %>
<p> <%= link_to "Uploaad track", new_playlist_song_path(#user) %>
<% end %>
I have tried a few variants for the simple_form form.
New song partial 1:
<%= simple_form_for ([#playlist, #song]) do |f| %>
<%= f.file_field :audio %>
<%= f.button :submit %>
<% end %>
another try :
<%= simple_form_for ([#playlist, current_user.playlist.songs.build]) do |f| %>
I just can't get the form to show and I can't figure out why not. Any ideas would be appreciated, thanks.
As pointed out in the comments songs_path is not referenced in your code, and shouldn't even exist as it's a sub-resource of playlist.
If you want to list all the available routes just run a rake routes and make sure the code references an existing path.
I didn't define the playlist that the song belongs to.
Adding to the controller
def create
#song = current_user.playlist.songs.new song_params
#song.playlist_id = #playlist.id
if #song.save
and
def find_playlist
#playlist = Playlist.find(params[:id])
end
then to simple form
<%= simple_form_for ([#playlist, #playlist.songs.build]) do |f| %>
It works.

Rails is looking for Update when it should be Create

I'm trying to make a comment form on my Collection show page. I'm a bit rusty with Rails and I'm not sure why this form isn't attempting to Create a comment and is instead returning the error
The action 'update' could not be found for CommentsController
The comments system works in console.
Here is my form
<%= form_for [#commentable, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :content %>
<%= f.submit "Comment", class: "btn btn-large btn" %>
<% end %>
My comment model
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
belongs_to :user
end
My collection model
class Collection < ActiveRecord::Base
has_many :comments, as: :commentable
end
My comments_controller
class CommentsController < ApplicationController
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
flash[:success] = 'Comment posted!'
redirect_to #commentable
else
flash[:notice] = "Error creating comment: #{#comment.errors}"
redirect_to #commentable
end
end
private
def comment_params
params.require(:comment).permit(:content, :commentable_type, :commentable_id, :user_id)
end
end
My collections_controller show action
def show
#collection = Collection.find(params[:id])
#commentable = #collection
#comments = #commentable.comments
#comment = Comment.new if user_signed_in?
end
You're not using #commentable in your form or controller, which may be causing your problem:
<%= form_for [#commentable, #comment] do |f| %>
And your controller action should look like this:
def create
#comment = #commentable.comments.new(comment_params)
Update: Then to load commentable based on resource:
before_filter: load_commentable
def load_commentable
resource, id = request.path.split('/')[1, 2]
#commentable = resource.singularize.classify.constantize.find(id)
end
Method courtesy Ryan Bates: http://railscasts.com/episodes/154-polymorphic-association-revised
Can you show the controller action that renders that forms? I think the problem is that #comment is already persisted/created so it tries to update it.
UPDATE:
I just noticed your show action. Yes the problem is that #comment is already persisted. Just change it to:
#comment = Comment.new if user_signed_in?

ActiveRecord::RecordNotFound - Couldn't find without an ID

I'm working on an app that allows users to comment on a single "work" (think blog post). The associations in the models are as follows:
class User < ActiveRecord::Base
has_many :works
has_many :comments
class Work < ActiveRecord::Base
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
belongs_to :work
There's a form on the Works show page that allows users to post a comment:
<%= form_for(#comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post a comment!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
The Works controller is as follows. Note that I'm adding the build comment functionality here so that the form on the Works page functions:
class WorksController < ApplicationController
#before_filter :current_user, only: [:edit, :update]
def index
#works = Work.all
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #works }
end
end
def create
#work = current_user.works.create(params[:work])
redirect_to current_user
end
def edit
#work = current_user.works.find(params[:id])
end
def new
#work = current_user.works.new
end
def destroy
#work = current_user.works.find(params[:id]).destroy
flash[:success] = "Work deleted"
redirect_to current_user
end
def update
#work = current_user.works.find(params[:id])
if #work.update_attributes(params[:work])
flash[:success] = "Profile updated"
redirect_to #work
else
render 'edit'
end
end
def show
#work = Work.find(params[:id])
#comment = #work.comments.build
#comment.user = current_user
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Work", trackable_id: #work).all
#comments = #work.comments.order("created_at DESC").where(work_id: #work ).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #work }
end
end
end
And lastly, here is the Comments controller:
class CommentsController < ApplicationController
before_filter :authenticate_user!
def index
#comments = Comment.all
end
def show
#comment = Comment.find(params[:id])
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Comment", trackable_id: #comment).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #comment }
end
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
flash[:success] = "Comment updated"
redirect_to #comment
end
end
def create
#work = Work.find(params[:id])
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
if #comment.save
#flash[:success] = "Post created!"
redirect_to #work
else
render 'home#index'
end
end
end
end
When I attempt to submit a comment using the comment form on the works show view page, I get the following error:
Activerecord::RecordNotFound in CommentsController#create
Couldn't find Work without an ID
Why can't the application find the Work so that it can associate the comment to it?
EDIT 1:
Thanks to the answers below I edited the comment form:
<%= form_for(#work, #comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post feedback or contribute content
to this work!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
I'm still getting the same error after making the change to the form and adding the nested route.
I edited the routes file to include a nest for work comments:
authenticated :user do
root :to => 'activities#index'
end
root :to => "home#index"
devise_for :users
resources :users do
member do
get :following, :followers, :posts, :comments
end
end
resources :works do
resources :comments
end
resources :relationships, only: [:create, :destroy]
resources :posts
resources :activities
resources :comments
Rake routes shows the following for Comments#create:
POST /comments(.:format)
The POST URL (where the error shows up) is appURL/works/1/comments
Doesn't seem right. What do I need to change? Thank you so much for the help so far!!
Your form needs to be form_for([#work, #comment]) so that Rails knows to build a URL like /works/123/comments. Right now it would just be posting to /comments.
Check your rake routes to see the route for your CommentsController#create action. You might also need to tweak the controller to read params[:work_id] instead of params[:id].
The view helper form_for(#comment) will post to '/comments' by default. You can specify a url (see the guides) that includes the :id of the work record. The typical approach is to use form_for([#work, #comment]) and Rails will do this for you so long as you've set up your routes with comments as a nested resource of work.

Creating form to update relationship table and model table

I have 3 models:
event
vendor
vendor_relationship
Every event has multiple vendors through that relationship.
Now I want to create a form at /events/1/add_vendors which creates the relationship AND creates the vendor model.
How would I go about doing this?
Thanks for the help!
ensure that you're Event model looks something like this:
attr_accessible :vendor_relationships, :vendor_relationships_attributes
has_many :vendor_relationships
has_many :vendors, :through => :vendor_relationships
accepts_nested_attributes_for :vendor_relationships
your VendorRelationship model looks something like this:
attr_accessible :vendors, :vendors_attributes
has_many :vendors
accepts_nested_attributes_for :vendors
in your EventController:
#event = Event.new(params[:event])
and in your create view, something like:
<% form_for Event.new do |f| %>
<%= f.text_field, :field_for_the_event %>
<% f.fields_for :vendor_relationships do |rf| %>
<%= rf.text_field, :price_maybe? %>
<% rf.fields_for :vendor do |vf| %>
<%= vf.text_field, :name_and_so_on %>
<% end %>
<% end %>
<% end %>
That's one way. Another, probably better user experience would be to allow for selection of vendor from existing vendors or create new. Create new would ajax creation to the VendorController, and on creation, return the vendor's information to the form. Saving the relationship would ajax a call to create the vendor_relationship and display the result.
Hope that sends you down the right direction.
# routes.rb
resources :events do
resources :vendors, :path_names => { :new => 'add_vendors' }
end
# vendors_controller.rb
before_filter :load_event
before_filter :load_vendor, :only => [:edit, :update, :destroy]
def load_vendor
#vendor = (#event ? #event.vendors : Vendor).find(params[:id])
end
def load_event
#event = params[:event_id].present? ? Event.find(params[:event_id]) : nil
end
def new
#vendor = #event ? #event.vendors.build : Vendor.new
...
end
def create
#vendor = #event ? #event.vendors.build(params[:vendor]) : Vendor.new(params[:vendor])
...
end
def edit
...
end
def update
...
end
def destroy
...
end
# View form
<%= form_for([#event, #vendor]) do |f| %>
...
<% end %>

How to get the post id in rails?

This is is how I have my models define for a simple blog
def User
has_many :comments, :dependent => :destroy
has_many :posts
end
def Post
belongs_to :user
has_many :comments
end
def Comment
belongs_to :user
belongs_to :post
end
In my Post Controller I have this code so that I can create a comment in the view
def show
#post = Post.find(params[:id])
#comment = Comment.new
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
Then in my Comment#create I have this
def create
#comment = current_user.comments.create(params[:comment])
if #comment.save
redirect_to home_show_path
else
render 'new'
end
end
How should I make it so that my comment model can receive the post_id? I have done this in my Post show view as a fix but is there another way that is better?
<%= f.hidden_field :post_id, :value => #post.id %>
There's nothing necessarily wrong with setting the post_id via a hidden field in your form - however it does mean that people could potentially associate their comment with any random post.
A better way might be to use nested resources for the comments of posts. To do this, set the following in your routes.rb file:
resources :posts, :shallow => true do
resources :comments
end
Then your form should look like this:
<%= form_for #comment, :url => post_comments_path(#post) do %>
...
<% end %>
Which will mean that the form POSTs to the path /post/[:post_id]/comments - which means in turn that the post_id is available to the controller as a param:
def create
#comment = current_user.comments.new(params[:comment])
#comment.post = Post.find(params[:post_id])
if #comment.save
...
end
end
This has the advantage of doing a select for the Post using the post id, and if the Post isn't found, an error will be raised.
It might also be worth rewriting that controller method slightly, so that the Post.find comes first:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:comment])
#comment.user = current_user
if #comment.save
...
end
end
Hope that helps.
Yes, there is a better way. Use nested resources as described in the official Routing guide or in the Getting Started guide. The Getting Started guide even covers this exact example of posts and comments!
<%= form_for :comment, :url => post_comments_path(#post) do |f| %>
<%= f.text_field :content %>
<%= f.submit%>
<% end %>
In your comment create action you have
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
#comment.user = current_user #if you are using devise or any authentication plugin or you define a current_user helper method
if #comment.save
......
end
if you are using rails 3, in you config/routes.rb do
resources :posts do
resources :comments
end
the first section of the code should be in your post/show.html.erb

Resources