Ruby on Rails: assign relationship on creation - ruby-on-rails

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.

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!

Rails multi-record form only saves parameters for last record

I'm trying to offer teachers a form that will create multiple students at once. It seems that most people tackle this concept with nested attributes, but I'm having a hard time understanding how that would work when I'm only using a single model. This article made it seem possible to achieve this without nested attributes, but my results are not working the way the author suggests. The students array should include one hash for each section of the form. But when I submit the form and check the parameters, only one single hash exists in the array.
Adjusting her approach, I've got this controller:
students_controller.rb
def multi
#student_group = []
5.times do
#student_group << Student.new
end
end
(I'm using an action I've called "multi" because it's a different view than the regular "create" action, which only creates one student at a time. I've tried moving everything into the regular create action, but I get the same results.)
The view:
multi.html.erb
<%= form_tag students_path do %>
<% #student_group.each do |student| %>
<%= fields_for 'students[]', student do |s| %>
<div class="field">
<%= s.label :first_name %><br>
<%= s.text_field :first_name %>
</div>
<div class="field">
<%= s.label :last_name %><br>
<%= s.text_field :last_name %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= submit_tag %>
</div>
<% end %>
The results:
(byebug) params
<ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"3Xpi4XeqXuPs9jQvevy+nvGB1HiProddZzWq6Ed7Oljr3TR2fhx9Js6fN/F9xYcpgfDckCBOC2CoN+MrlFU0Bg==", "students"=>{"first_name"=>"fff", "last_name"=>"ggg"}, "commit"=>"Save changes", "controller"=>"students", "action"=>"create"} permitted: false>
Only one has is included for a student named "fff ggg". There should be four other hashes with different students.
Thank you in advance for any insight.
fields_for is only used in conjunction with form_for. The for is referring to a model, which it expects you to use. Since you're trying to build a form with no model, you have to construct your own input field names.
Don't use fields_for but instead, render each input using the form tag helpers e.g.
<%= label_tag "students__first_name", "First Name" %>
<%= text_field_tag "students[][first_name]" %>
...and so on.
The key is that the field names have that [] in them to indicate that the students parameters will be an array of hashes. You almost got it by telling fields_for to be called students[] but fields_for ignored it because it needs a model to work correctly.

Using form helpers to picking an object by name, but assigning the ID associated with that name

I have a Product model, an Order, and an OrderItem model.
An OrderItem object has a single Product (that is, it belongs to a product)
An Order has multiple order items.
When you want to add an OrderItem to your order, you fill out a form. One of the fields is to enter the product that will be assigned to this order item. The order model stores the product_id as a foreign key, and so Rails complains (as it should) when I write
<%= form_for #orderItem, url: {:action => :create} do |f| %>
<p>
<%= f.label :product_name %>
<%= f.text_field :product_name %>
</p>
<%= f.submit %>
<% end %>
What I have in mind is to to have someone use the form to create order items. When they choose which product to assign to this order item, they will pick a name from a list of product names (which are unique), and then when they submit the form, the controller retrieves the appropriate Product object and assigns its ID to the OrderItem.
How can I set up my form to accomplish this?
Sounds like you want to use nested attributes. They'll let you save information about both an OrderItem and a Product at the same time from a single form.
In your OrderItem, add the line accepts_nested_attributes_for :product.
Then in your form you'll want something like:
<%= form_for #orderItem, url: {:action => :create} do |f| %>
<p>
<%= fields_for :product do |fp| %>
<%= fp.label :product_name %>
<%= fp.text_field :product_name %>
<% end %>
</p>
<%= f.submit %>
<% end %>
(Note the use of fp.label instead of f.label within the fields_for block.
The above is from memory so there may be some mistakes (and you'll probably need to change things in your OrderItemsController too), but it should give you a starting point.
You might find this Railscast helpful too.

passing dynamic params field

Im working with Rails 3.0.3
I want to create bills in my App. Each Bill has many entries (Material, how much of that and the Price)
The Problem i have, is that i want to write the bill and the entries and then save both at the same time. So when you click on save Bill, the Bill + each Entry should be created (saved in the db).
I can write the bill + each entry (with javascript), but i dont know how i could save both of them. Right now i can only save the bill it selft. Is it possible to pass a dynamic field via params so i can handle that in the bills controller to save? How would you implement this?
What you are looking for is called nested form, you have a main form for your bill and multiple forms that are dynamically generated as children of this general form using fields_for like this:
<% form_for #bill do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<% f.fields_for :entry do |builder| %>
<%= render "entry", :f => builder %>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
Of course you will need some js for the dynamic creation of the different entries, here you have a couple of railscasts that will be helpfull.
Nested model form Part 1
Nested model form Part 2

Transferring variables between models using a view

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.

Resources