Rails - Add multiple records in one form - ruby-on-rails

I have a User model and Todo model. I want to be able to add multiple todos using a single form.
Routes:
# Users
resources(:users) do
resources(:todos)
end
User model:
class User < ApplicationRecord
has_many :todos
....
Todos controller:
class TodosController < ApplicationController
def new
#user = User.find(params[:id])
#todos = Array.new(10) {#user.todos.build}
end
....
View for todos/new:
<%= form_for([#user, :todos]) do |f| %>
<% #todos.each do |todo| %>
<%= f.text_field(:name, class: "form-control") %>
<% end %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
What I get is:
undefined method `model_name' for :todos:Symbol
What am I doing wrong? I searched SO for doing multiple saves using one form, and I found this.

I believe you are looking for nested forms.
In your model you will add
accepts_nested_attributes_for :todos
and in your view
<%= form_for #user do |f| %>
TODO:
<ul>
<%= f.fields_for :todo do |todo| %>
<li>
<%= todo.label :todo %>
<%= todo.text_field :todo %>
</li>
<% end %>
</ul>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
in controller
def new
#user = User.find(params[:id])
10.times {#user.todos.build}
end
for refference http://guides.rubyonrails.org/form_helpers.html#nested-forms
NOTE: this code is untested

In your view, try
<%= form_for([#user, #user.todos]) do |f| %>
I'm also new to ruby but hopefully it works.

Related

How to update the nested form attributes of only the current_user in rails 6?

I have a 'User', 'Task' and 'Comment' model in my rails 6 app. The User model is defined using devise for authentication. User has_many comments and has_many tasks and Task has_many comments.
I have a nested form for 'Task' which accepts_nested_attributes_for 'Comment'. Something like this:
<%= form_with model: #task do |f| %>
<%= f.text_field(:name) %>
<br><br>
<%= form.fields_for :comments do |c| %>
<%= render('comment_fields', f: c) %>
<% end %>
<%= link_to_add_association('Add comment', f, :comments) %>
<%= f.submit %>
<% end %>
The '_comment_fields.html.erb' file looks like this:
<div class="nested-fields">
<%= f.text_area(:body) %>
<%= link_to_remove_association('Remove comment', f) %>
</div>
The above two forms are just a minimum version of the original code as I am using them only for reference here.
Now, suppose a user named 'user1' added a task named 'task1' using the task form in the database and also added some comments using the nested form for comments.
What I want is if some other user named 'user2' tries to edit the task 'task1' then he/she should be able to add and edit only his comments with this form and can only edit the task name and 'user2' should not be able to edit or remove someone else's comments but only his. How would we go about doing this for a nested form.
We can have this functionality in some normal form like:
<% if #task.user.id == current_user.id %>
<%= f.text_field(:name) %>
<% end %>
The above code will only show the text_field when the current model's user_id matches the current signed in user's id but I don't know how to do this in f.fields_for because we don't have a variable like #task in a nested form.
You can get the object wrapped by a form builder instance through the object attribute:
<div class="nested-fields">
<%= f.text_area(:body) %>
<% if f.object.user == current_user %>
<%= link_to_remove_association('Remove comment', f) %>
<% end %>
</div>
However I wouldn't really encourage you to reinvent the authorization wheel. The Pundit or CanCanCan gems are the most popular choices to avoid spreading and duplicating the authentication logic all over your views and controllers.
With Pundit you would do:
class CommentPolicy < ApplicationPolicy
def destroy?
record.user == user
end
end
<div class="nested-fields">
<%= f.text_area(:body) %>
<% if policy(f.object).destroy? %>
<%= link_to_remove_association('Remove comment', f) %>
<% end %>
</div>
The equivilent with CanCanCan is:
class Ability
include CanCan::Ability
def initialize(user)
can :destroy, Comment, user: user
end
end
<div class="nested-fields">
<%= f.text_area(:body) %>
<% if can?(:destroy, f.object) %>
<%= link_to_remove_association('Remove comment', f) %>
<% end %>
</div>

Rails - Conditional statement in form recognizing wrong url paramater

I have a polymorphic relationship between Attachments and Users & Teams so that both users and teams can upload attachments. The :team_id or :user_id is passed along as a param when the "Add file" button is clicked. Then, a hidden field in the attachments#new form tells the controller if the request is coming from a Team or User so that attribute_update can be applied to the proper model. In practice, however, the team request is being delivered as user subaction and vice versa. Any ideas as to what may be going wrong?
class Attachment < ActiveRecord::Base
belongs_to :upload, polymorphic: true
end
class User < ActiveRecord::Base
has_many :attachments, as: :upload
end
class Team < ActiveRecord::Base
has_many :attachments, as: :upload
end
team#show
<%= link_to "Add Files", new_attachment_path(:team_id => #team.id), class: "btn btn-md" %>
user#show
<%= link_to "Add Files", new_attachment_path(:user_id => current_user), class: "btn btn-md" %>
attachments#new
<% provide(:title, 'Add file') %>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#attachment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.file_field :file %>
<% if request.path == new_attachment_path(params[:team_id]) %>
<%= hidden_field_tag(:subaction, 'Team') %>
<% elsif request.path == new_attachment_path(params[:user_id]) %>
<%= hidden_field_tag(:subaction, 'User') %>
<% end %>
<%= f.submit "Add Files", class: "btn btn-primary" %>
<% end %>
</div>
</div>
attachment controller
def create
#attachment = Attachment.create(attachment_params)
#team = Team.find_by(params[:team_id])
#user = User.find_by(params[:user_id])
if #attachment.save
if params[:subaction] == 'Team'
#attachment.update_attribute(:upload, #team)
flash[:success] = "Team file uploaded!"
redirect_to #team
elsif params[:subaction] == 'User'
#attachment.update_attribute(:upload, #user)
flash[:success] = "User file uploaded!"
redirect_to current_user
end
else
render 'new'
end
end
I think the problem is with your if statement in attachments#new. Both params[:team_id] and params[:user_id] would return an integer so your if statement can't really tell if the request is coming from a team or a user. As a suggestion you could store your params[:team_id] in a variable such as #team_id and then change your if statement to something like this:
<% unless #team_id.nil? %>
<%= hidden_field_tag(:subaction, 'Team') %>
<% else%>
<%= hidden_field_tag(:subaction, 'User') %>
<% end %>
However you may be able to improve this further by using polymorphic associations in your models as outlined in the following link: https://launchschool.com/blog/understanding-polymorphic-associations-in-rails
Changed the url param to pass the string 'User' or 'Team' instead of the team_id or user_id allowing the controller if statement to function properly.
Team#show
<%= link_to "Add Files", new_attachment_path(:upload_type => 'Team'), class: "btn btn-md" %>
User#show
<%= link_to "Add Files", new_attachment_path(:upload_type => 'User'), class: "btn btn-md" %>
Attachments#new
<% provide(:title, 'Add file') %>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#attachment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.file_field :file %>
<%= hidden_field_tag(:subaction, params[:upload_type]) %>
<%= f.submit "Add Files", class: "btn btn-primary" %>
<% end %>
</div>
</div>

undefined method `comments_path' for #<#<Class:... > (RAILS)

I'm trying to add comments to my photos model however when I submit my comment I am getting the following error:
undefined method `comments_path' for #<#<Class:0x007fdef11c3dd8>:0x007fdef4163f98>
My routes look like this:
resources :photos do
resources :comments
end
Models:
class Comment < ActiveRecord::Base
belongs_to :photo
end
class Photo < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
end
views new comment:
<%= form_for [#picture, #comment] do |f| %>
<%= f.label :comments %>
<%= f.text_area :comments %>
<%= f.submit 'Leave Comment' %>
<% end %>
views index:
<% if #photos.any? %>
<% #photos.each do |photo| %>
<%= link_to photo.title, photo_path(photo) %>
<%= photo.description %>
<% if photo.comments.any? %>
<ul>
<% photo.comments.each do |comment| %>
<li>
<%= comment.comments %>
</li>
<% end %>
<% else %>
<p> No comments yet </p>
<% end %>
<% if current_user %>
<%= link_to "Comment on #{photo.title}", new_photo_comment_path(photo) %>
<% if current_user.id == photo.user_id %>
<%= link_to "Delete #{photo.title}", photo_path(photo), method: :delete %>
<% end %>
<% end %>
<br>
<% end %>
<% else %>
No photos yet
<% end %>
<br>
<%= link_to "Add a photo", new_photo_path %>
Comments Controller:
class CommentsController < ApplicationController
def new
#photo = Photo.find(params[:photo_id])
#comment = Comment.new
end
def create
#photo = Photo.find(params[:photo_id])
#photo.comments.create(comment_params)
redirect_to photos_path
end
def review_params
params.require(:comment).permit(:comments)
end
end
Please let me know if you need any further info, haven't posted much so still getting used to the format. Thanks all.
EDIT: Here is the error I am getting in full:
Error message
Problem was in the comments view:
<%= form_for [#picture, #comment] do |f| %>
<%= f.label :comments %>
<%= f.text_area :comments %>
<%= f.submit 'Leave Comment' %>
<% end %>
#picture was meant to be #photo. doh.

How can I leave a comment and associate with the post in post' index page?

How can I leave a comment and associate with the post in post' index page?
It is my PostsController:
def index
#posts = Post.all
"what should I add here?"
end
# GET /posts/1
# GET /posts/1.json
def show
#comments = #post.comments.all
#comment = #post.comments.build
end
and its my posts show view:
<p id="notice"><%= notice %></p>
<p>
<h3><%= #post.name %></h3>
</p>
<p>
<%= (#post.descriptopm).html_safe %>
</p>
<%= link_to 'Edit', edit_post_path(#post), :class => "btn btn-info btn-xs" %>
<%= link_to 'Back', posts_path, :class => "btn btn-info btn-xs" %>
<h3>Comments</h3>
<% #comments.each do |comment| %>
<div>
<strong><%= comment.user_name %></strong>
<br />
<p><%= (comment.body).html_safe %></p>
</div>
<% end %>
<%= render 'comments/form' %>
and its my posts index view:
<h1>Listing posts</h1>
<%= link_to 'Create a New Post', new_post_path, :class => "btn btn-success btn-sm" %>
<% #posts.each do |post| %>
<div class="post thumbnail">
<h3><%= post.name %></h3>
<div><%= (post.descriptopm).html_safe %></div>
<div class="bottom-bottoms">
<%= link_to 'Display', post, :class => "btn btn-info btn-xs" %>
<%= link_to 'Edit', edit_post_path(post), :class => "btn btn-info btn-xs" %>
<%= link_to 'Delete', post, method: :delete, data: { confirm: 'Are you sure?' }, :class => "btn btn-info btn-xs" %>
</div>
<h3>Comments</h3>
<% post.comments.each do |comment| %>
<div>
<strong><%= comment.user_name %></strong>
<br />
<p><%= (comment.body).html_safe %></p>
</div>
<% end %>
<%= render 'comments/form' %>
</div>
<% end %>
the post.rb :
class Post < ActiveRecord::Base
has_many :comments
end
the comment.rb :
class Comment < ActiveRecord::Base
belongs_to :post
end
the show page's comment function looks right
but when I leave a comment in my post's index page
It might not save the comment with the right post's id
how do I fix it?
and onother one:
how can I redirect the page to the index page after I save the comment not to comment's index page?
How can I leave a comment and associate with the post in post' index
page?
There are several things to consider:
Comment Objects
Firstly, you need to appreciate that since Rails is an object orientated framework (by virtue of being built on Ruby), you will need to ensure your new comment corresponds with the relevant Post object
I think this the core of what you're getting confused about:
def index
#posts = Post.all
# Here needs to go your "comment" build methodology. Except, it only works *per* post ;)
end
The trouble you have is that you can't "build" a comment for Posts - you have to build them per post, like this:
#post = Post.find 2
#post.comments.new #-> allows you to create a new comment for that particular post
--
Implementation
The best solution I can give you will be somewhat constricted, but will serve your purposes correctly. Here it is:
#app/controllers/posts_controller.rb
Class PostsController < ApplicationController
def index
#posts = Post.all
#comment = Comment.new
end
end
#app/controllers/comments_controller.rb
Class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
#comment.save
end
private
def comment_params
params.require(:comment).permit(:comment, :params, :post_id)
end
end
This will take into consideration that for each comment on your index, you'll have a form with the post_id attached; and will be a "linear" flow (IE you can only post a single comment at a time):
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
<%= post.title %>
<%= form_for #comment do |f| %>
<%= f.hidden_field :post_id, value: post.id %>
<%= f.text_field :title %>
<%= f.submit %>
<% end %>
<% end %>
Yes, this will give each Post a "new comment" form on your index page (hence my denoting its constriction). However, what it will do is give you the ability to add a comment for any of the posts on the page, handling them with the CommentsController
To answer your second second question, you can use
redirect_to post_index_path
As for the first question, you can set the post id of the post by using:
#post.comments.new
Instead of just Comment.new, which won't set the ID of the post.
In this scenario...this is the best example that you can get help
Railscasts show post with comments using polymorphism

Rails 3: Why am I not able to see the input fields that should be generated by form_for?

What I see
The following is my Track template:
app/views/tracks/index.html.erb
<h1>Playlist</h1>
<%= form_for #track do |f| %>
<%= f.error_messages %>
<p>
<%= f.text_field :youtube_url %>
<%= f.submit 'Add' %>
</p>
<% end %>
<hr />
<% if Track.first == nil %>
<p>Database is empty!</p>
<%else%>
<% #tracks.each do |track| %>
<ul>
<li><%= track.youtube_url %></li>
</ul>
<% end %>
<% end %>
And clearly, the input fields are not being shown for some reason.
The other relevant files:
app/controllers/tracks_controllers.rb
class TracksController < ApplicationController
def index
#track = Track.new
#tracks = Track.all
end
def create
#track = Track.new(params[:track])
render :action=>"index"
end
def delete
#track = Track.find(params[:id])
#track.destroy
redirect_to(tracks_url)
end
end
routes.rb
Music::Application.routes.draw do
resources :tracks
end
track.rb
class Track < ActiveRecord::Base
attr_accessible :title, :youtube_url
end
What am I doing wrong?
You have to put <%= opening tag in your form_for instead of <%.
<%= form_for #track do |f| %>
<%= f.error_messages %>
<p>
<%= f.text_field :youtube_url %>
<%= f.submit 'Add' %>
</p>
<% end %>

Resources