What does the following form do in the code? - ruby-on-rails

<%= form_for [#blog,#blog.comments.build] do |f| %>
<p><%= f.text_area :text, :size => '40x10' %> </p>
<p><%= f.submit "Post Comment" %> </p>
<% end %>
This is handler by comments_controller, but I would like to know the reason, especially for form_for

The form_for creates a form for creation or update of passed object. If the object is not persisted, the associated url will target the creation action. Otherwise the targeted action will the update. form_for can receive many different kinds of parameter to generate the form.
If you check out the Rails url_helpers documentation, you will see that you can do something like:
<%= link_to 'First comment', blog_comment_path([#blog, #blog.comments.first]) %>
This will generate a link to the first comment of the blog with a path like /posts/#post.id/comments/#post.comments.first.id. This also assumes that you have the correct setup on your routes.rb:
resources :blogs do
resources :comments
end
With this, you generate a bunch of paths that you can use to build, for instance, links and forms. Thus, the form_for in your code works similarly. Think of it as a url_helper. You have a #blog and a comment associated to the post(#blog.comments.build). As the comment is not persisted yet, the will generate a form for the creation of comments targetting CommentsController#create. The associated path will be something like /blogs/#blog.id/comments and the HTTP method will be POST.
Also, check these links to get more info:
Rails Routing
Rails Form Helpers

It adds a form with a text box, submit button and some hidden authentication related hidden fields for entering comment.
The comment is added to #blog object with relationship:
has_many :comments
Comment is build by the code if not present by:
#blog.comments.build
So overall you get a form for entering comments in a # blog object. The blog object is necessary in this case and the comment will be automatically combined to the blog entry in proper column in comment record column "blog_id" by default.
This is called Nested Form Relationship, where instead of editing only one record of comment you can combine the parent object also and edit it.
build is basically used to create a structure for object, something like new ( e.g. Model.new). Form action is decided on the basis of given objects. In your case the objects are #blog and #blog.comments.build so the action called will be of either update of Blog controller or Create of Comments Controller..
Hope this helps.

Related

Why is my Rails form helper written improperly?

I know I've written it wrong, but I'm looking at the documentation and can't figure out how.
My model is Quote and has three fields, body, attribution, and work, all strings. The form is intended to add a new quote to a page of quotations.
on main/index.html.erb
<%= form_for(:quote, url: {action: 'create'}) do |f| %>
<%= f.text_field :body %>
<%= f.text_field :attribution %>
<%= f.text_field :work %>
<%= submit_tag "Submit" %>
<% end %>
in main_controller.rb
def create
Quote.create(body: params[:body], attribution: params[:attribution], work: params[:work])
end
The form submits, and an entry is saved to the database -- but it's a totally blank entry. I'm not sure why. Help would be appreciated!
Three things:
The way rails forms are supposed to work, you're not meant to get body, attribution, etc independently, they should be wrapped up into a quote object. But...
In your form, your not properly binding an object to the form the way rails expects. You can read more in the documentation here: http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object. You could also generate a fake scaffold rails generate scaffold HighScore game:string score:integer to generate a fake model and see an example of how it's supposed to work. The default scaffolding even has simple examples of how to deal with save errors.
Finally, as #Paven suggested, when you get confused, be sure to look at what's going on in your log - i.e. what params are being posted to your create action. That is always helpful and a good way to diagnose problems quickly.
Your form does't need the action argument. The form_for helper uses ActiveRecord objects to determine the path, meaning as long as you build your object correctly, you won't need to determine your path individually:
<%= form_for #quote do |f| %>
Secondly, you'll want to look at your create method:
#app/controllers/quotes_controller.rb
def new
#quote = Quote.new
end
def create
#quote = Quote.new(quote_params)
end
private
def quote_params
params.require(:quote).permit(:body, :attribution, :work)
end
The problem is you're not sending an ActiveRecord object to your form_for helper. You can read the explanation here:
In Rails, this is usually achieved by creating the form using form_for
and a number of related helper methods. form_for generates an
appropriate form tag and yields a form builder object that knows the
model the form is about. Input fields are created by calling methods
defined on the form builder, which means they are able to generate the
appropriate names and default values corresponding to the model
attributes, as well as convenient IDs, etc. Conventions in the
generated field names allow controllers to receive form data nicely
structured in params with no effort on your side.
In order to get the form working correctly, you need to be able to provide a valid ActiveRecord object (#variable), which the helper can use to determine the url etc
My code above helps you provide a new ActiveRecord variable, and allows you to use it in the form. This should allow the form_for method to send your data to the create method, which will then create & save an object in the db for you

activeadmin rails 4 understanding how to create custom forms

I am new to activeadmin / formtastic and I have having a bit of trouble understanding how things work. I read through the documentation on how to create a form using formtastic but I seem to be still running into issues and I am sure its me not understanding how things work.
I am creating a discussions application very similar to a blog application and the end result is that I would like to create an interface for the administrators to add comments to discussions without having to go into the users interface.
My starting point is the discussions view in the admin section presented by activeadmin. I am attempting to work on the add comment form. According to the instructions, I should be able to add a form using
form partial: 'new_admin_comment_form', locals {discussion_comment: DiscussionComment.new}
which then I should create this partial in app/views/admin/discussions folder. I have done that and have entered some arbitrary text to make sure the partial renders and it does. But once I start adding code I am not able to get the form to display.
The current code I am working with is:
<%= semantic_form_for [:admin, discussion_comment] do |f| %>
<%= f.inputs, :body %>
<%= f.actions %>
<% end %>
So a few questions I have that I wasn't able to find in the documentation:
Where do I create instance variables to be used in my form? I have been setting these in the activeadmin files and that is bothering me.
How do I pass params around? I assumed I could do this as normal yet when I try to view them using <%= debug params.inspect %>, it is empty even when I should have at least the id that was in the parent form. Even when using locals: {id: params[:id]}, id is empty in the partial.
What are the best ways to debug why my form is not appearing? Am I able to use regular ERB if worse comes to worse?
You can do this without a custom form. If you stick to the active admin DSL you can use its has_many method. Example here:
http://www.activeadmin.info/docs/5-forms.html
Your Discussion model should look like this
class Discussion < ActiveRecord::Base
has_many :discussion_comments
accepts_nested_attributes_for :discussion_comments, allow_destroy: true
end

how form_for works in Ruby on Rails

I am an newbie. I have read the API documentation. But still don't understand how form_for works.
Firstly, from Ruby on Rails Tutorial, the form for follow button:
<%= form_for(current_user.relationships.build(followed_id: #user.id)) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
I understand current_user.relationships.build(followed_id: #user.id) means a new record. But why can we not just submit and trigger controller to save the record without hidden_field? Why do we still need to post followed_id to controller?
Secondly, in hidden_field, what does :followed_id means? I believe that is a symbol, i.e. it equals only "followed_id" not a variable of id. If that is only the name of the input field, then what is its value?
Thirdly, how does form_for know where the submission should be sent to? Which controller and action the form_for will post to?
Fourth, how does params work with form_for? In this follow button case, params[:relationship][:followed_id] will return #user.id in controller. How does it know the first hash attribute is :relationship? We have neither mentioned form_for :relationship nor form_for #relationship.
I know these questions can be very dumb, but I am really stuck. Any help will be appreciated.
I didnt do that tutorial so mind me if i dont answer directly to your question.
Take a look at the rails guide about form helpers and it explains in details your questions, probably in a more articulate way than i can.
form_for(path/to/your/controller/action) is a helper method to create HTML form elements with the url path to the POST or GET request. The helper knows if it should be a new record or an update record based on what you are asking to do in your controller action.
For example
In your controller
def new
#my_instance_variable = Myobject.new
end
In your view new.html.erb
<%= form_for #my_instance_variable do |f| %>
...
<% end %>
In your case the logic was directly written in the helper and you could also directly write
<%= form_for Myobject.new %>
Both will result with the following html
<form action="/myobjects/new" method="post">
# in this case rails knows its a `POST` request because the route new action
# is by default a POST request. You can check these routes and their request
# by using `rake routes` in terminal.
Then the hidden_field is another helper to contain a value, in your case the #user.id that will be passed as parameter then saved as a Create or update action for the given object. The reason it doesnt add the value in the hidden field tag is because you already have a model association that knows the id of user since the link of form uses the build method with user id.
Last part you need to understand the form_for link logic
current_user.relationships
# implies the association of the current_user has many relationships
current_user.relationships.build
# .build is a method to populate a new object that can be save as a new record
# means you will create a new relationship record by populating the user_id
# column with the current_user.id and the followed_id with the target #user.id
After reading the book The Rails 4 Way, I understand form_for better now.
11.9.1.5 Displaying Existing Values.
If you were editing an existing instance of Person, that object’s attribute values would have been filled into
the form.
in this way, when we build the relationship by usingcurrent_user.relationships.build(followed_id: #user.id), the relationship instance will be created and gain attribute followed_id. So that, instead of "creating" a relationship, we are actually editing the relationship by the form.
Then Rails will know you are editing and load the existing attribute "followed_id" to the field. Therefore, we don't need to assign value to the field like using f.hidden_field :followed_id, value: #user.id.
And the reason why we have to use a field to pass followed_id to params is because HTTP server is stateless, it doesn't remember you are creating a relationship with which user.
One of the advantages of writing form_for current_user.relationships.build(followed_id: #user.id) instead of standard form_for #relationship is we don't need to write "if-condition" in controller like this:
unless current_user.nil?
if current_user.following?(#user)
#relationship=current_user.relationships.find_by(followed_id: #user.id)
else
#relationship=current_user.relationships.new
end
end
params will be sent to the controller which belongs to the instance's model. "post" method will go to action create, "delete" will go to destroy, "patch" will go to update, etc.
params will be a hash with another hash inside like { instace_name: { field_1: value1, field_2:value2 } } or full params as below
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"afl+6u3J/2meoHtve69q+tD9gPc3/QUsHCqPh85Z4WU=",
"person"=>{"first_name"=>"William", "last_name"=>"Smith"},
"commit"=>"Create"}

More than a simple nested form

campaign.rb
class Campaign < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :item
end
item.rb
class Item < ActiveRecord::Base
belongs_to :campaign
end
Campaign has 2 attributes: title and description
Item has 1 attirubte: name
I'll try explain myself by words, I want to create a nested form where they user insert the campaign's name and description but he can insert more than just 1 item, he can insert a list of items (in particular there will be a "+" button that when clicked a new item row will appear and the user can insert items).
At the end all is send all together clicking just one submit button.
How can I reach my goal with rails?
I answered a question just yesterday. Here's the link: Rails accepts_nested_attributes_for with f.fields_for and AJAX
I'll write out how it works & provide some resources to help give you some more ideas:
How It Works
Loading associative fields into a form is done by using f.fields_for
You'll do it like this:
#app/views/campaigns/new.html.erb
<%= form_for #campaign do |f| %>
<%= f.fields_for :items do |a| %>
<%= a.text_field :information %>
<% end %>
<% end %>
In order to get this to work, you have to build the associated ActiveRecord objects in the backend before you render the view, like this:
#app/controllers/campaigns_controller.rb
def new
#campaign = Campaign.new
#campaign.items.build
end
Adding Extra Fields Via Ajax
Adding extra fields with Ajax requires engineering a new solution to the issue
The way you do this is to take the f.fields_for text & put it into a partial. This partial can be called from the original view, as well as another view (which we can render through Ajax)
The Ajax part works by basically taking a request from your form (the ajax request), and then using another action in your controller to build a new ActiveRecord object & render another partial that will contain another form. This partial will then call the original f.fields_for partial, allowing you to render another field
Your Ajax can then extract the new field & append it to your page. The way you get around the id issue (keeping the IDs sequential & unique) is to employ the child_index method, and use Time.now.to_i to generate a timestamp
If you read my answer referenced at the top of this answer, all of this will make sense :)
Some great resources for this:
RailsCasts Nested Forms
Adding Fields With Ajax
A nice gem along with tutorial is available from ryanbates who is the author of railscasts.com site.You can use this and have a look at tutorial here
And also if you want to try manually use the fields_for while writing in the form like here and manage some jquery code for add or remove.

What's the difference between using nested routes vs. accepts_nested_attributes_for?

I may be confusing the two entirely, but I'm seeing that forms can facilitate associations using an array argument based on nested routes, ex:
<%= form_for [#project, #task]...
or using the fields_for helper if the parent class accepts_nested_nested_attributes_for the child.
What's the difference / trade-offs between these approaches?
I didn't find the answers given to be as clear as I had hoped, so after doing a bit more research I discovered an answer that satisfied me and so I figured I'd share it with others.
Nested routes approach
Basically, the nested routes approach is useful when you're presenting a form for a child model as a single form in it's own right. In other words, if you have a blog with a Post model with a Comment model as it's child, you can use nested routes to present the form for the child such that submitting that form will let rails do its magic in terms of associating the child with the parent.
Nested attributes approach
The accepts_nested_attributes_for method on the other hand, is more aptly used to present a form that while taking on the appearance of a single form, is really multiple forms merged together with a single submit button.
So to summarize, the nested routes approach deals with one model in single form (albeit associated with a parent model), while the nested attribute approach deals with multiple models in a single form.
The difference might be subtle to newcomers, but meaningful enough to understand.
Hope this helps others who had questions about this. Cheers.
This is child model form:
<%= form_for [#project, #task] ... %>
where you submiting Task for the existent Project.
Here #project = Project.find(params[:project_id]) and #task = Task.new according to docs or #task = Task.find(params[:id]) if you're updating existing task.
This is used in the parent model form:
<%= form_for #project do |f| %>
<%= f.fields_for :task do |builder| %>
<% # ... %>
<% end %>
<% end %>
where you can create or update both objects at once. Task attributes will be pass in the params[:project][:task_attributes] with task id if you're updating objects.

Resources