Transferring variables between models using a view - ruby-on-rails

I am creating a very simple book review site and it needs the ability to allow the user to add little comments about a book. Now I have my two tables, one for the book and one for the comments and now need a way to transfer data between the two because i find the way rails handles things quite puzzling.
So my book model contains "has_many :comments"
and me comment model has: "belongs_to :book"
the view i am using to both view and add comments is "/views/book/viewbook.html.erb"
this shows the book and all its details, at the bottom is a section where the user can add their own comments, it looks like this:
<%= form.text_field :title %>
<%= form.text_area :body %>
<%= submit_tag "Add Comment", :class => "submit" %>
now i know this cannot work because that above ":title" and ":body" would be in the book model but i need them to be send to the comment model because these are in the comment DB. How do i pass this data to the comment database. I've tried ":comment.title" and other various things but still cannot workout how to pass this data.
Any thoughts would be greatly appreciated.
(I apologize if this question is very stupid or has not been explained to well, my lecturer set this assignment and rails is not a language i have ever used.)

You define what the form is for in the opening form tag:
<% form_for :comment do |form| %>
<%= form.text_field :title %>
<%= form.text_area :body %>
<%= submit_tag "Add Comment", :class => "submit" %>
<%= end %>
The idea is that the form is an empty comment object. Controllers communicate between models and views, so your controller should have an action to process the form that knows to save the comment object into the comment model. You'll also want to specify which book the comment is for. There are a lot of ways to handle this (hidden fields, nested RESTful resources, etc.).
Maybe start here: http://guides.rubyonrails.org/action_controller_overview.html

It sounds like you need to use nested object forms. This is a new feature in Rails 2.3:
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes

So i have modified it using your great suggestions and now only one problem has come-up. Whilst i can now successfully store the comments title, body, time and the person who left the comment to the DB i still cant work out how to use store the book.id. Here is the view:
<% form_for :comment, :url => { :action => "addcomment" } do |form| %>
<%= form.hidden_field :user_id, :value => session[:user_id] %>
<%= form.hidden_field :book_id, :value => #book.id %> <!-- WONT WORK -->
<%= form.label "Title" %><%= form.text_field :title %><br />
<%= form.label "Comment" %><%= form.text_area :comment %>
<%= submit_tag "addComment", :class => "submit" %>
<% end %>
Here is my controller that can now successfully store the details, apart from the book.id
def addcomment
#newcomment = Comment.new(params[:comment])
if #newcomment.save
flash[:notice] = "Comment Was Added To The System."
redirect_to :action => "index"
end
end
i though that "#book.id" would work because in that view i am also showing the books details using things like "#book.title" and "#book.authour" and that seems to work, but not for the ID field though.

So can now successfully post comments and store them with the correct details, now im on to displaying a list of comments of that particular book using the "book_id" value. Here is what i thought would work, i also have code like this in the search part of my app so i thought it would well:
def view
#book = Book.find(params[:id])
#reviews = Comment.find_by_book_id(#book.id)
end
With the corresponding view:
<% if #reviews %>
<% for review in #reviews %>
<%= form.label "Title: " %><%h review.title %> <br />
<%= form.label "Review:" %><%h review.comment %>
<% end %>
<% end %>
Now that should get the comments that have the "book_id" of the book i am viewing and the display each one using the for loop. Doesnt quite work though, it spits out an error message saying the following:
#undefined method `each' for #<Comment:0xb682c2f4>
#Extracted source (around line #27)
And line 27 is
<% for review in #reviews %>

To find the comments for a book it's just:
#book = Book.find(params[:id])
like you've already done, then:
#book.comments
for that books comments. So:
<% for review in #book.comments %
<%= form.label "Title: " %><%h review.title %> <br />
<%= form.label "Review:" %><%h review.comment %>
<% end %>
You don't have to find the comments in the controller.

Related

Many-to-many is Rails

I'm doing backend for trello-clone app using Rails. I have board entity which has many column entities which are having many cards entities. I've made board-column part (using blog app example) and it works fine, but I can't understand how to make column-card part of that.
Method create in card controller is like:
def create
#board = Board.find(params[:board_id])
#column = #board.columns.find(params[:column_id])
#card = #column.cards.create(card_params)
end
I've made the form like this for adding cards for each column :
Form code:
<p>
<strong>Name:</strong>
<%= #column.name %>
</p>
<p>
<strong>Color:</strong>
<%= #column.background_color %>
</p>
<h2>Add a card:</h2>
<%= form_with(model: [#board, #column, #column.cards.build], local: true) do |form| %>
<p>
<%= form.label :name %><br>
<%= form.text_field :name %>
</p>
<p>
<%= form.label :description %><br>
<%= form.text_area :description %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
So my question is how to make normal entity adding for the second level of the many-to-many relationship?
Your question is quite ambiguous but I guess you probably are confused about this thing:
Add hidden_field for board_id like:
form.hidden_field :board_id, value: #board.id
Then access it in the controller update/create with something like params[:column][:board_id] (check params to be exact) and relate the column with board id.
You surely are missing this piece but you surely are missing more.
I would suggest using byebug gem and inspecting params hash. It will help you a lot because I think you need to inspect params which are confusing you.
Another way (not good but simpler) to
puts "*"*100
puts params
puts "*"*100
as first line of create action in controller to see how exactly your params are. (Go into console and find whatever is written between 2 lines of asterisks in your server console)
Good Luck!

How to come up with a view for one to many relation for two models in Rails 5

I'm trying to set up a one to many relationship between one model to another in rails, and it has worked in the console, but I can't seem to implement it properly in views so that I am able to create more pictures for a single event (more explained below)
The background of the web application is, a single event has many event pictures, and many event pictures belong to a single event. I'm trying to set this up properly within the console and schematics, it seems to have worked. I was unable to get it working when I tried implementing it with views.
<%= form_with(model: [ #event, #event.event_pictures.build ], local: true) do |form| %>
<p>
<%= form.label :answer %><br>
<%= form.text_field :answer %>
</p>
<p>
<%= form.label :hint %><br>
<%= form.text_field :hint %>
</p>
<p>
<%= form.label :event_pics %>
<%= form.file_field :event_pics, multiple: true %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
The code above is my form which is the main cause of the issue. The error is shown below.
undefined method `event_pictures' for nil:NilClass
Console stuff works.. in Pastebin (https://pastebin.com/ESYAfMzE)
Controller
def new
render 'new'
end
def create
#event = Event.find(params[:event_id])
#event_picture = #event.event_pictures.create(event_picture_params)
redirect_to event_path(#event_picture)
end
**Button to create new picture, in Event Index, so you can see the event, before clicking on a button for a new picture. **
<%= button_to "new picture", {:controller => :event_picture, :action => 'new', :event_id => event.id},
:method => :get,
class: "btn btn-warning" %>
This is fairly frustrating as I've also looked into the possibility of using fields_for instead of form_with, and checked my schema.rb and migrations a number of times to make sure it is properly linked. At this point, I'd really appreciate any help at all as I've gone hours looking at this problem.
Thank you very much.
I see, in your new action, you need to setup the #event instance variable:
def new
#event = Event.find(params[:event_id])
end

Ruby on Rails: assign relationship on creation

I'm new to Ruby on Rails. There are two models in my project: room and guest. The association is "room has_many guests" and "guest belongs to room".
I have separated views for manage rooms and guests. Rooms don't require "guests" value on creation. However, I want to create new guests and assign it to certain room at the same time. What will be the proper way to do it? How do I transfer the input from web and match the entities in database.
The code is pretty much the same as "Getting Started with Rails". In the tutorial, they add "comments" in the "article" view and use "comment" as a sub-resource of "article". In my case, I treat the two models equally and want to manage them in separated views.
Update:
I used the collection_select and try to work with my guest_controller.
<%= form_for :guest, url: guests_path do |f| %>
<% if #guest.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#guest.errors.count, "error") %> prohibited this guest from being added:
</h2>
<ul>
<% #guest.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :phone %><br>
<%= f.text_field :phone %>
</p>
<p>
<%= f.label :room%><br>
<%= f.text_field :room %>
</p>
<p>
<%= f.label :room %><br>
<%= f.collection_select(:room_id, Room.all, :id, :title) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', guests_path %>
In my guest_controller, the create method called by the form above is :
def create
#guest = Guest.new(guest_params)
#guest.room = Room.find(params[:room_id])
if #guest.save
redirect_to #guest
else
render 'new'
end
end
However, when I create a new guest, it shows that:
ActiveRecord::RecordNotFound in GuestsController#create
Couldn't find Room with 'id'=
I checked that room_id=4 and Room.find(4) return the proper room.
What's wrong?
If you want to select one room from those that exist, use collection_select form helper, here is a relevant snippet from the docs:
f.collection_select(:city_id, City.all, :id, :name)
This outputs a dropdown list that:
fills in city_id parameter in this context
uses City.all for filling in the options in the list (I will be referring to "each" city as city)
uses city.id as data (that gets sent in the form)
shows city.name for each city in the dropdown list (hopefully, human-readable)
Bear in mind though, that in terms of security it's like "look, you can select this, and this and this!", that does not prevent users from selecting an unlisted option: either by modifying form markup by hand or sending handcrafted queries.
So should you ever be limiting access to specific rooms, and list only Room.unlocked (unlocked assumed a scope), make sure the received room_id refers to a room from that scope as well. Most of these problems are dealt with using either validations or careful association management (Room.unlocked.find_by_id(:room_id) that outputs nil if the room is not in that scope).
UPD: as for the latest problem you're having -- your understanding on how the form contents look in params seems to be wrong. It's quite a common misconception actually.
form_for :guest will construct a separate object/hash in params[:guest], with all the form's fields inside it. So it actually is inside params[:guest][:room_id], but no, don't rush with adding the missing part.
You've already built a #guest object from entire params[:guest], so if the room actually exists, it's inside #guest.room already and can be validated inside the model during save. Have a look at Rails validators.
Take a look at the fields_for tag:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for
It allows just that, to create a guest while creating a room and associating each other.

Ruby on Rails 3: How to pass a parameter to a select box in a form?

I have a Client model that can have many Projects.
These are my view files:
edit.html.erb
<%= form_for(#project) do |f| %>
<%= render 'fields', :f => f %>
<%= f.submit Create %>
<% end %>
_fields.html.erb
<div>
<%= f.label :name %><br/>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :client_id %><br/>
<%= f.select(:client_id, current_user.client_names) %>
</div>
This is easy and works great.
But is there a way to create a Project from within a Client view as well?
For example on the client edit page it would be nice to have a link New Project for this Client that leads to the above New Project form, but with the respective client preselected in the select box.
Can this be done somehow?
I can't seem to find a way to pass the Client ID to the New Project form.
Thanks for any help!
This is certainly possible, but requires passing the client id to the new_projects_path. This can be done by nesting the routes to projects inside of clients, or just appending the client id. The difference would be URLs that look like the following:
/clients/1/projects/new
- or -
/projects/new?client_id=1
In your projects controller, you should be able to instantiate the #project variable with a preselected client:
#project = Project.new :client_id => params[:client_id]
Yes, you can do this. You're going to use accepts_nested_attributes_for :project in your Client model. Nesting this will allow you to do something like
<%= f.fields_for :project do |p| %>
<fieldset>
<%= p.text_area :content %>
</fieldset>
<% end %>
Check out the railscasts episode #196 on Nested Model Form.

Multiple forms for the same model in a single page

On the front page of my rap lyrics explanation site, there's a place where users can try explaining a challenging line:
alt text http://dl.dropbox.com/u/2792776/screenshots/2010-02-06_1620.png
Here's the partial I use to generate this:
<div class="stand_alone annotation" data-id="<%= annotation.id %>">
<%= song_link(annotation.song, :class => :title) %>
<span class="needs_exegesis"><%= annotation.referent.strip.gsub(/\n/, "\n <br />") %></span>
<% form_for Feedback.new(:annotation_id => annotation.id, :created_by_id => current_user.try(:id), :email_address => current_user.try(:email)), :url => feedback_index_path, :live_validations => true do |f| %>
<%= f.hidden_field :annotation_id %>
<%= f.hidden_field :created_by_id %>
<p style="margin-top: 1em">
<%= f.text_area :body, :rows => 4, :style => 'width:96%', :example_text => "Enter your explanation" %>
</p>
<p>
<% if current_user %>
<%= f.hidden_field :email_address %>
<% else %>
<%= f.text_field :email_address, :example_text => "Your email address" %>
<% end %>
<%= f.submit "Submit", :class => :button, :style => 'margin-left: .1em;' %>
</p>
<% end %>
</div>
However, putting more than one of these on a single page is problematic because Rails automatically gives each form an ID of new_feedback, and each field an ID like feedback_body (leading to name collisions)
Obviously I could add something like :id => '' to the form and all its fields, but this seems a tad repetitive. What's the best way to do this?
If you don't want to change your input names or your model structure, you can use the id option to make your form ID unique and the namespace option to make your input IDs unique:
<%= form_for Feedback.new(...),
id: "annotation_#{annotation.id}_feedback"
namespace: "annotation_#{annotation.id}" do |f| %>
That way our form ID is unique, i.e. annotation_2_feedback and this will also add a prefix, e.g. annotation_2_, to every input created through f.
Did you consider nested_attributes for rails models? Instead of having multiple new feedback forms where each is tied to an annotation, you could have multiple edit annotation forms where each annotation includes fields for a new feedback. The id's of the generated forms would include the annotations id such as edit_annotation_16.
The annotation model would have a relationship to its feedbacks and will also accept nested attributes for them.
class Annotation < ActiveRecord::Base
has_many :feedbacks
accepts_nested_attributes_for :feedbacks
end
class Feedback < ActiveRecord::Base
belongs_to :annotation
end
You could then add as many forms as you want, one for each annotation. For example, this is what I tried:
<% form_for #a do |form| %>
Lyrics: <br />
<%= form.text_field :lyrics %><br />
<% form.fields_for :feedbacks do |feedback| %>
Feedback: <br/>
<%= feedback.text_field :response %><br />
<% end %>
<%= form.submit "Submit" %>
<% end %>
<% form_for #b do |form| %>
Lyrics: <br />
<%= form.text_field :lyrics %><br />
<% form.fields_for :feedbacks do |feedback| %>
Feedback: <br/>
<%= feedback.text_field :response %><br />
<% end %>
<%= form.submit "Submit" %>
<% end %>
And the quick and dirty controller for the above edit view:
class AnnotationsController < ApplicationController
def edit
#a = Annotation.find(1)
#a.feedbacks.build
#b = Annotation.find(2)
#b.feedbacks.build
end
def update
#annotation = Annotation.find(params[:id])
#annotation.update_attributes(params[:annotation])
#annotation.save!
render :index
end
end
I had this same issue on a site I'm currently working on and went with the solution you mention at the bottom. It's not repetitive if you generate the ID programmatically and put the whole form in a partial. For example, on my site, I have multiple "entries" per page, each of which has two voting forms, one to vote up and one to vote down. The record ID for each entry is appended to the DOM ID of its vote forms to make it unique, like so (just shows the vote up button, the vote down button is similar):
<% form_for [entry, Vote.new], :html => { :id => 'new_up_vote_' + entry.id.to_s } do |f| -%>
<%= f.hidden_field :up_vote, :value => 1, :id => 'vote_up_vote_' + entry.id.to_s %>
<%= image_submit_tag('/images/icon_vote_up.png', :id => 'vote_up_vote_submit' + entry.id.to_s, :class => 'vote-button vote-up-button') %>
<% end -%>
I also had the same issue but wanted a more extensible solution than adding the ID to each field. Since we're already using the form_for ... |f| notation the trick is to change the name of the model and you get a new HTML ID prefix.
Using a variant of this method: http://www.dzone.com/snippets/create-classes-runtime (I removed the &block stuff)
I create a new model that is an exact copy of the model I want a second form for on the same page. Then use that new model to render the new form.
If the first form is using
#address = Address.new
then
create_class('AddressNew', Address)
#address_new = AddressNew.new
Your ID prefix will be 'address_new_' instead of 'address_' for the second form of the same model. When you read the form params you can create an Address model to put the values into.
For those stumbling here, looking for the solution for Rails 3.2 app, look at this question instead:
Rails: Using form_for multiple times (DOM ids)

Resources