Nested Models with Comments - ruby-on-rails

I have an application where there are a few nested models... Two parent models and two child models.
I'm trying to create comments on the child models and I had it working great for the first one until I realized I have to create comments on the second child so I realized I had to scrap my work because I was targeting the first parent + child model in the comments controller. So I decided to watch Ryan Bates screencast (http://railscasts.com/episodes/154-polymorphic-association) on creating comments that belong to multiple models...unfortunately, it's not working for me and I'm assuming its because I am trying to create comments on the child models. I will show you what I was using before that worked for the one model and I'll show you what im doing now that doesnt work...
here is what i had for the comments controller
def create
#collection = Collection.find(params[:collection_id])
#design = #collection.designs.find(params[:design_id])
#comment = #design.comments.create(comment_params)
#comment.user = current_user
#comment.save
redirect_to collection_design_path(#collection, #design)
end
and here is what it is now after i tried to implement it to work for multiple models
def create
#commentable = find_commentable
#comment = #commentable.comments.build(comment_params)
#comment.user = current_user
#comment.save
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
here are my crazy routes
resources :collections do
member do
post :like
post :unlike
end
resources :designs do
resources :comments
member do
post :like
post :unlike
end
end
end
anyone have any other different ideas for created comments for multiple nested models? Thanks in advance for the help.
EDIT:
Here was the form I was using for the one model
<%= form_for([#collection, #design, #design.comments.build]) do |f| %>
<%= f.text_area :comment %>
<%= f.submit "Comment", :class => "btn" %>
<% end %>
and here is the one i'm using now
<%= form_for([#collection, #design, #commentable, Comment.new]) do |f| %>
<%= f.text_area :comment %>
<%= f.submit "Comment", :class => "btn" %>
<% end %>
Right now when I try to submit the new comment form I get this error
undefined method `comments' for #<Collection:0x0000010150cf88>
which points back to the create method
EDIT 2
Here is my comment model
belongs_to :commentable, :polymorphic => true
belongs_to :user
Here is my design model (which is a child of the collection model)
has_many :comments, :dependent => :destroy, :as => :commentable
belongs_to :user
belongs_to :collection
and my collection model (which has the child model: design)
belongs_to :user
has_many :designs, :dependent => :destroy
and there is more to the models but its not related to the problem.

Related

Rails wizard form

I have an enrollment form where a user can enroll to some sort of event.
However, I want to give the posibility for teams to enroll also and I was thinking about a wizard like form.
Basically create 5 records at a time.
The problem is, I'll have a new enrollment creation on each step, so I thought the wicked gem would not do it for this scenario.
Can you give me a few guidelines on how should I approach this?
Maybe just render new after creation if a i.e. team attr is sent from the form?
Maybe use self join?
That's off the top of my head but I know there has to be a clever way to do this.
I'm not sure how your models are structured, but if you have something like:
class Attendee
has_many :enrolments
has_many :events, through: :enrolments
end
class Enrolment
has_many :attendees
belongs_to :event
end
class Event
has_many :enrolments
has_many :attendees, through: :enrolments
accepts_nested_attributes_for :enrolments
end
Then you can do something like:
# controllers/enrolments_controller.rb
class EnrolmentController < ApplicationController
def new
#event = Event.find(params[:event_id])
pax = params[:persons].to_i
pax.times do
#event.enrolments.build
end
end
def create
#event = Event.find(params[:event_id])
#event.enrolments.build(enrolment_params)
#event.save
end
protected
def enrolment_params
# require specific parameters here
params.require(:event).permit(:attendee_attributes => [])
end
end
# views/enrolments/new.html.erb
<%= form_for #event, url: event_enrolments_path(#event) do |f| %>
<%= f.hidden_field :event_id %>
<%= f.fields_for :enrolments do |af| %>
<%= af.select :attendee_id, Attendee.all.collect {|p| [ p.name, p.id ] } %>
<% end %>
<%= f.submit %>
<% end %>
# routes.rb
resources :events do
resources :enrolments
end
That's off the top of my head, but the general idea is that you build the nested fields by running event.enrolments.build based on the number of people passed in the params.
This uses fields_for and accepts_nested_attributes_for. This also makes it really convenient to reuse existing forms by passing in the form context in the partial:
<%= f.fields_for :enrolments do |af| %>
<%= render "enrolments/form", f: af %>
<% end %>

Nested attributes wouldn't save in database

I have two models one Topic and Topic_Content.
With the following code
Route
resources :topics do
resources :topic_contents
end
Topic
class Topic < ActiveRecord::Base
has_one :topic_content
accepts_nested_attributes_for :topic_content
end
TopicContent
class TopicContent < ActiveRecord::Base
belongs_to :topics
end
Controller
class TopicsController < ApplicationController
def new
#topic = Topic.new
end
def create
# render text: params[:topic].inspect
#topic = Topic.new(topic_params)
#topic.save
end
private
def topic_params
params.require(:topic).permit(:title, topic_content_attributes: [:text])
end
end
View
<%= form_for #topic do |f| %>
<%= f.label 'Topic:' %>
<%= f.text_field :title %>
<%= f.fields_for :topic_contents do |tf| %>
<%= tf.label :text %>
<%= tf.text_area :text %> 
<% end %>  
<%= f.submit %>
<% end %>
The title will be saved correct in the topic table but the topic_content(text) wouldn't saved in the database, and I couldn't find the problem.
I'm not a Rails expert, but I'm certain you need to build the association in your controller.
In your new and edit actions you need to have:
def new
#topic = Topic.new
#topic_content = #topic.build_topic_content
end
Because this is a has_one/belongs_to you need to have it look that way. If it was a many association you'd build it with something like #topic_content = #topic.topic_contents.build.
I'm pretty sure it's just a matter of building the association in the right controller, which, I believe, for you, is the topic controller.
Your view should be as follow:
f.fields_for :topic_content do |content_fields|
^

nested attributes, passing current_user.id to nested model

I have 3 models: User, Answer and Question.
user.rb
has_many :questions
has_many :answers
question.rb
has_many :answers
belongs_to :user
accept_nested_attributes_for :answers
answer.rb
belongs_to :question
belongs_to :user
In questions/show.html.erb
form_for #question do |f|
f.fields_for :answers, #question.answers.build do |builder|
builder.text_area, :body
end
f.submit
end
Submit calls the questions#update action and, thanks to nested resources, will the new answer be saved in the database. I wonder: how can I save the user_id column for answer, in the database, after the question is submitted? Can I somehow pass current_user.id to answer's user_id column after submitting the form?
You have to pass the user parameter in your controller's create action (also you should build your nested attributes in the controller):
def new
#question = Question.new
#question.answers.build
end
def create
#question = current_user.questions.new(params[:questions])
//The line below is what will save the current user to the answers table
#question.answers.first.user = current_user
....
end
Your view's form should therefore look like:
form_for #question do |f|
f.fields_for :answers do |builder|
builder.text_area, :body
end
f.submit
end
You could do something like this in your controller's update action:
# questions_controller
def update
params[:question][:answers_attributes].each do |answer_attribute|
answer_attribute.merge!(:user_id => current_user.id)
end
if #question.update_attributes(params[:question])
...
end
end
Another more simple solution would be to add the user_id to your form like this:
form_for #question do |f|
f.fields_for :answers, #question.answers.build(:user_id => current_user.id) do |builder|
builder.text_area, :body
builder.hidden_field :user_id # This will hold the the user id of the current_user if the question is new
end
f.submit
end
The problem with this approach is that users would be able to edit the value through a HTML source code inspector (like in chrome), and thereby set the user to someone else. You could of course validate this in some way, but that too would be a bit complex.
'current_user' is a devise helper method. it can be accessed in controllers n views directly.
if i m not wrong, only the new answer should have the current_user.id Old answers should not be updated. u can do this as
f.fields_for :answers, #question.answers.build do |a|
a.hidden_field :user_id, :value => current_user.id
a.submit

Cannot create comments from Post view

I have a model Post that has_many :comments. The form that will post the comment will be shown along with the post in posts/show.html.erb. I have a comments_controller that should handle the creation of comments. Searching on google, I found
<%= form_for([#post, Comment.new], :controller => 'comments', :action => 'create') do |f| %>
But this doesn't work. How do I do this ?
class Post < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
#...
class Comment < ActiveRecord::Base
belongs_to :post
#...
Then in the form
form_for #post do |f|
f.fields_for :comments do |c|
c.text_field :title
#...
f.submit
this would create the associated object through active record's accepts_nested_attributes_for, which doesn't require a separate comments_controller. you are submitting to the posts controller, which is handling creating the associated object during the update of the post.
with a comments_controller, you could do one of two things:
send item_id as a param to comments_controller#new, grab the item, then build the new comment from it
#post = Post.find(params[:item_id); #comment = #post.comments.build
put the post_id in a hidden field on the form and just create the comment as normal
# in the controller
#comment = Comment.create(params[:comment])
# in the view
form_for #comment do |f|
f.text_field :title
#...
f.hidden_field :post_id, value: #post.id

Associating notes with different entities in a database

At my job, we have judges perform a variety of tasks, e.g., rate movies or compare two pieces of text.
We're in the process of designing a new database to hold all our data (we have some data already, but the database it's in is pretty hack), and I'm starting to build a Rails analytics application that will serve as a dashboard on these judgments. Tables will include things like Judges, Movies, Text, MovieRatings, TextComparisons.
As part of the application, we want to be able to add comments or flag items from these tables. For example, someone might want to add a comment to Judge 1 saying "This judge is very inconsistent" and add a comment to Rating 2 saying "This rating is unexpected", or flag different types of movies or texts for review.
What is the best way to handle adding comments or flags to the database? For example, do we want to create a new Comment table for each entity (add a JudgesComments, MoviesComments, TextComments, etc.)? Or do we want to have a single Comments table with (id, comment) columns [which, I guess, would require ids throughout the database to be globally unique within the database, instead of unique only within its table]?
You should use polymorphic associations, so that you will have a single Comment model and controller. According to the excellent #154 "Polymorphic Association" Railscast, after adding a commentable_type:string and commentable_id:integer to your comments table, your code should look something like this:
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
# app/models/judge.rb
class Judge < ActiveRecord::Base
has_many :comments, :as => :commentable
end
# app/models/movie.rb
class Movie < ActiveRecord::Base
has_many :comments, :as => :commentable
end
# app/models/text.rb
class Text < ActiveRecord::Base
has_many :comments, :as => :commentable
end
# app/controllers/comments_controller.rb
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
in the routes:
# config/routes.rb
map.resources :judges, :has_many => :comments
map.resources :movies, :has_many => :comments
map.resources :texts, :has_many => :comments
and in the view:
<!-- app/views/comments/index.html.erb -->
<div id="comments">
<% for comment in #comments %>
<div class="comment">
<%=simple_format comment.content %>
</div>
<% end %>
</div>
<h2>New Comment</h2>
<%= form_for [#commentable, Comment.new] do |f| %>
<p>
<%= f.label :content %><br />
<%= f.text_area :content %>
</p>
<p><%= f.submit "Submit" %></p>
<% end %>

			
				
I have worked in a system where there was a single Comments table, and a globally unique ID for each record in each table you could have a comment for. The system worked fine, wasn't hard to maintain, and it was easy for new people to see how it worked. Generating new records in the comment-able tables was slow by computer standards, but it wasn't really an issue for the users of the system.

Resources