Understanding of Rails 4's strong parameters - ruby-on-rails

I'm totally new to Rails and I'm playing with it. Now I'm trying to understand the strong parameter feature introduced in Rails 4. Below is my code:
class PostsController < ApplicationController
def index
end
def show
end
def create
p = Post.new(create_post_params)
p.save
redirect_to p
end
def new
end
private
def create_post_params
params.require(:post).permit :title, :body
end
end
Beside the controller, I also have a Post model with a title and a body. My question is what is this :post thing in params.require(:post).permit :title, :body? I write it as :post, is it because I'm currently inside the PostsController? Or I'm reading the properties of a Post?
Edit
Based on gdpelican's answer, if my new.html.erb is like this:
<h1>Create a post</h1>
<%= form_for :post, url: posts_path do |f| %>
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: "form-control" %>
<p class="help-block">Please type the title of the post</>
</div>
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, class: "form-control", rows: 5 %>
<p class="help-block">Please type the body of the post</>
</div>
<%= f.submit class: "btn btn-primary" %>
<% end %>
It's the :post part in <%= form_for :post, url: posts_path do |f| %> determines that I should use :post in params.require(:post).permit :title, :body, right?

Your parameters (typically) look like this
{"utf8"=>"✓", "authenticity_token"=>"...", "post"=>{"title"=>"My title", "body" =>"Body of my Post"}}
When you require a specific key from the parameters (for example post) Rails will throw an error if the hash it was passed doesn't have "post"=>{....}, then once it passes that check it permits the allowed keys and returns only the parameters nested under "post" hash allowed. To copy the api docs examples
params = ActionController::Parameters.new({
person: {
name: 'Francesco',
age: 22,
role: 'admin'
}
})
params.require(:person).permit(:name, :age)
=>{"name"=>"Francesco", "age"=>22}
So after your strong params check, the return is a hash of :post parameters that you have allowed.
EDIT: To answer your second question.
That is one way of thinking about it. Your form syntax (form_for :post) is creating the post hash with the attributes nested inside, and sending it as part of the overall parameters hash. And your params.require(:post) is taking the entire params, and finding only the hash key it wants (post) and then permitting the keys that are inside the post hash.

It is the name of the JSON wrapper of your form values.
The form will typically wrap the form parameters like so:
{
post: {
title: "Title",
body: "Body",
}
}
When using something like form_for #post
In essence, params.require(:post).permit(:title, :body) is saying two things:
my parameters must have a :post attribute
the :post attribute may only have a title and a body parameters, and nothing else.
UPDATE
The parameters in form_for are what affect how your parameters are wrapped.
Generally, the name of the controller matches the name of the form parameters, so in most instances it's a safe assumption that a 'BooksController' will accept form parameters in a 'book' field.

Related

Correct way to permit strong parameters for nested attributes in Rails

I have model named note.rb as follows:
class Note < ApplicationRecord
belongs_to :user
has_many :tags
accepts_nested_attributes_for :tags
end
And also a model named tag.rb:
class Tag < ApplicationRecord
belongs_to :note
end
The form for new note creation is as follows:
<%= form_with scope: :note, url: notes_path, local: true do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<%= f.fields_for :tags_attributes do |t| %>
<p>
<%= label_tag(:name, "Add a tag") %><br>
<%= t.text_field :name %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
<div id= "tag-displayer">
<span id= "tags"></span>
</div>
I am trying create a record for tag with a record of note.
In my notes_controller.rb I have
def create
#note = Note.new(note_params)
#note.user = current_user
if #note.save
redirect_to '/notes'
else
render 'new'
end
end
and :
private
def note_params
params.require(:note).permit(:title, :description, tags_attributes: [:id, :name])
end
Now on form submit I get the following:
TypeError (no implicit conversion of Symbol into Integer):
I get the same error if I use:
params.require(:note).permit(:title, :description, :tags_attributes => [:id, :name])
I get the error:
Unpermitted parameter: :tags_attributes
If I use:
params.require(:note).permit(:title, :description, :tags_attributes => [])
Params for form submit:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"iSsLyOb0ZxZP0rfB4I5yfyrw965zJSLrtkroTUzseY2k4o5DwKpKXlyxN6p99pt4Fwju1RhMZPkbNdv+YVSESQ==", "note"=>{"title"=>"test note with Tag", "description"=>"Test note with tag", "tags_attributes"=>{"name"=>"Rails"}}, "commit"=>"Save Note"}
I am not sure what I'm doing wrong, I've tried all possible solutions available to me.
Using Rails 5 with ruby 2.4.1.
Well, if I look at your params, you actually do have strings. There are several ways to fix that, but the most straightforward way in my opinion is just to permit strings instead of symbols. For tags_attributes, that should be 'tags_attributes' => ..., for the rest I'm not sure if I remember correctly: it's either 'title' or :'title', probably the latter. I think the same goes for 'note' or :'note'. I hope you can just play around a bit and see which one it is. I had the error before, so I'm quite certain that should fix it. I hope I can help! Please let me know which one fixes it, I'm curious to know :)
Well i think you don't need to add id in strong parameters
try this: -
params.require(:note).permit(:title, :description, tags_attributes: [:name])
The immediate issue is that your form is passing:
{..., "tags_attributes"=>{"name"=>"Rails"}}, ...}
When it needs to pass:
{..., "tags_attributes"=>[{"name"=>"Rails"}], ...} # The hash is inside an array.
Or possibly:
{..., "tags_attributes"=>{"any_key_except_id" => {"name"=>"Rails"}}, ...}
I'm pretty sure the TypeError is coming from that issue, probably that at some point it's trying to treat the string "Rails" as a hash and tries to call "Rails"[:id] on it.
My hunch (without reproducing your entire setup) is that this can be solved by changing your line that says:
<%= f.fields_for :tags_attributes do |t| %>
to
<%= f.fields_for :tags do |t| %>
If you look at the documentation for fields_for, it uses the name of the association, without the _attributes at the end. With :tags_attributes, I think the form doesn't know what to do with it, assumes it's a single item instead of a collection, and so doesn't nest the attributes in an array.
Note that if you want to have it display the field for a new tag (instead of just existing tags), I believe you'll have to call #note.tags.build somewhere before the fields_for call so that there's an unsaved Tag entity in the tags collection.
I think it has something to do with your model declaring has_many :tags and your form data coming in as an object :tags_attributes => {id: "", name: ""}.
The TypeError (no implicit conversion of Symbol into Integer): error could be bubbling up when you are trying trying to save the note #note.save or when you are initialising it using #note = Note.new(note_params) but not because of Strong parameters. Strong parameters should permit those parameters because the definition matches the form data you are sending.
Try modifying the frontend to send tags in an array format like :tags_attributes => [{id: "", name: ""}, {id: "", name: ""}]
There is blog that is almost if not exact similar to the problem you are facing, check it out http://billpatrianakos.me/blog/2013/09/29/rails-tricky-error-no-implicit-conversion-from-symbol-to-integer/

Rails form_for assistance: param is missing or the value is empty

I am having an annoying problem with my form_for method while creating forums. Everytime I try to submit a forum for creation, I receive this error from Rails.param is missing or the value is empty: forum
The problem is in my forums_param method:
def forum_params
params.require(:forum).permit(:id, :name, :position)
end
The forum part does not exist. The code below is my form for the view:
well.span11
.span7
= form_for #forum, url: forums_path, html: { method: :post } do |f|
= render partial: "form", locals: { f: f }
.actions
= submit_tag 'Create', { class: 'btn btn-primary btn-small' }
.clear
And the partial that it renders:
%fieldset
%div{class: 'control-group'}
= label_tag :title, "Title (required)", class: 'control-label required'
%div{class: 'controls'}
= text_field_tag :name, nil, class: 'span8'
- if #forum.errors[:name]
%p{class: 'error'}#{#forum.errors[:name]}
%div{class: 'control-group'}
= label_tag :position, "Position", class: 'control-label'
%div{class: 'controls'}
= text_field_tag :position, nil, size: 5
%div{class: 'control-group'}
= label_tag :description, "Description", class: 'control-label'
%div{class: 'controls'}
= text_area_tag :description, nil, rows: 10, class: 'span10'
Below is the controller code:
def new
#forum = Forum.new
end
def create
#forum = Forum.new(forum_params)
if #forum.save
redirect_to forums_path, flash: { success: t('.success') }
else
redirect_to forums_path, flash: { error: t('.error') }
end
end
I'm not sure what is going on here. I have already implemented the recommendations described under these posts.
Solution 1
Solution 2
Solution 3
What is the problem here? Help would be greatly appreciated.
From what I can see, you're missing a bunch of stuff as it goes from view to controller. You have the description field, title etc.. and these aren't being factored into the forum_params
If the user can add these and change them, they must be included in strong params. I don't think id should be in there though... user shouldn't be allowed to change the id. That should be created by AR when the record is created.
The issue here appears to be your usage of <foo>_tag instead of f.<foo>_field.
When you use <foo>_tag, a literal tag with the attributes you gave it appears in the DOM.
text_field_tag example:
text_field_tag 'title'
# => <input id="title" name="title" type="text" />
Based on an example from the docs. Source: the Ruby on Rails API docs for text_field_tag
Whereas, when you use f.<foo>_field, the name attribute is namespaced under the model name.
f.text_field example:
text_field(:post, :title, size: 20)
# => <input type="text" id="post_title" name="post[title]" size="20" value="#{#post.title}" />
Source: the Ruby on Rails API docs for text_field
Slightly more in-depth explanation
With the examples above, when the first is submitted, the params look like:
{ ..., "title" => "user's input", ... }
You can see from this that if your controller tries to get :post out of this parameters hash, it is nil, and it throws the error you encountered.
The params for the second example in the first section look like:
{ ..., "post" => {"title" => "user's input", ... }, ... }
When the controller tries to get :post out of this hash, it gets the sub-hash containing title (and any other form fields).
I hope this solves your problem!

How to submit fields other than model fields

I need to write a view that returns the model plus additional fields that is not part of the model.
In the controller I should access these like:
def create
#post = Post.new(params[:post])
#stuff = params[:stuff]
How can I write the view?
you can use fields_for helper
form_for #post do |f|
f.text_field :title
fields_for :stuff do |stuff_form|
stuff_form.text_field :name
end
end
remember, its simply fields_for not f.fields_for. if you use f.fields_for it will give the stuff_form fields as part of your post object params
Use form helpers, thus:
<%= form_for #post do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body, size: "60x12" %>
<%= text_field_tag :stuff %>
<%= f.submit "Create" %>
<% end %>
For most types of input field, there's both the f.field_type and field_type_tag helpers. The former are for fields that are part of the model, whereas the latter are simply additional fields that will be present in the params. So the above would give you a params hash like this:
{:post => {:title => 'the title', :body => 'the contents of the post'}, :stuff => 'contents of stuff field'}
You can add field tags that are not part of your form which will add to the params hash.
in haml...
= text_field_tag :stuff, "what you want in stuff"
= hidden_field_tag :stuff, "or if you wanted a hidden field to show stuff"
will result in params hash...
stuff=>"what you want in stuff"

No post parameters sent when a form is submitted

I have a model called AlphaUser. When I submit a form to create a new alpha user, I'm told there wasn't any data in the params[:post]. How can this happen?
My code for the form:
<%= form_for :alpha_user,
url: alpha_users_path,
class: "form",
id: "alphaTest" do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.text_field :email,
class: "form-control", placeholder: "grandma#aol.com" %>
<%= f.text_field :producer,
class: "hidden", value: "0" %>
</div>
<%= f.submit "Request Access", class: "btn btn-primary" %>
<% end %>
My controller:
class AlphaUsersController < ApplicationController
def create
render text: params[:post].inspect
end
end
Data is still recieved, just in this format:
{"utf8"=>"✓", "authenticity_token"=>"blahblahblah", "alpha_user"=>{"email"=>"test#test.com", "producer"=>"0"}, "commit"=>"Request Access", "action"=>"create", "controller"=>"alpha_users"}
You need to call params for the :alpha_user... e.g. params[:alpha_user].
If you're using strong_parameters in Rails 4, this should do the trick:
class AlphaUsersController < ApplicationController
def create
#alpha_user = AlphaUser.new(alpha_user_params)
#alpha_user.save
end
private
def alpha_user_params
params.require(:alpha_user).permit(:email, :producer)
end
end
Notice that in your create action you are no longer referencing the params directly, but the alpha_user_params method required by strong_parameters.
Check this answer: Custom name for params hash from Rails form_for
Also there's a reason for this naming convention. Look here through section 7 "Understanding Parameter Naming Conventions".

RoR : Mongoid and form create hash

Simple question for Rails gurus. Why I do have to use the following statement to insert a new Mongoid document : params[:video][:description] in the following create method of my VideosController? Why I can't use the params[:description] from the POST form? If I use it, the value becomes nil.
def create
#video = Video.new(
:title => params[:video][:title],
:description => params[:video][:description]
)
if #video.save
render 'success'
else
render 'error'
end
end
Here is the Video.rb class :
class Video
include Mongoid::Document
field :title, type: String
field :description, type: String
validates_presence_of :title
validates_presence_of :description
acts_as_url :title
end
And finaly the form view :
<%= form_for #video do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<p/>
<%= f.label :description %>
<%= f.text_field :description %>
<%= submit_tag("Enqueue video") %>
<% end %>
I don't quite get why the form input are video[description] and not just description as expected :
<label for="video_title">Title</label>
<input id="video_title" name="video[title]" type="text" />
<p/>
<label for="video_description">Description</label>
<input id="video_description" name="video[description]" type="text" />
When you are using form_for:
Creates a form that allows the user to create or update the attributes
of a specific model object.
In your case, Video model. To understand Rails convention:
<%= form_for #video do |f| %>
...
<%= f.text_field :description %>
...
Which results in an html tag whose name attribute is video[description]. This means that when the form is submitted, the value entered by the user will be available in the controller as params[:video][:description].
The params variable is an instace of ActiveSupport::HashWithIndifferentAccess, like a Hash with a small difference, according to documentation:
This class has dubious semantics and we only have it so that people
can write params[:key] instead of params[‘key’] and they get the same
value for both keys.
Your params is something like:
{"utf8"=>"✓",
"_method"=>"post",
"authenticity_token"=>"xxx",
"video"=>
{"description"=>"Video desc"}
}
Where "video" or :video is one of the keys of the Hash. So, params[:video] is equivalent to params.fetch("video") which value is {"description"=>"Video desc"}. As you can see the value is another Hash. Finally to get the value of the description you have to params[:video][:description] (The Rails way) or params.fetch("video").fetch("description"), which value is "Video desc".
fetch is a Ruby method of Hash: "Returns a value from the hash for the given key."
Taking this into account:
Video.new(params[:video]) = Video.new(:description => "Video desc") = Video.new(:description => params[:video][:description])
It's easier to use conventions, but for sure you can have params[:description] (just in case):
<%= form_for #video do |f| %>
...
<%= text_field_tag :description %>
...
Note that I'm using text_field_tag instead of f.text_field. In this case the html tag name will be description in the params Hash you will receive { "description" => 'Video desc" }...
Take a look to Rails API documentation to understand different helpers, etc. And also review your server's log.
If you want to use video[:description]. Create your form like this
<%= form_for #video do |f| %>
....
<p/>
<%= f.label :description %>
<%= f.text_field :description, :name => "description" %>
....
<% end %>
Rails form_for helper name the input elements such that it becomes easy to push them into model attributes in one go like this
#video = Video.new(params[:video])
so that you don't have to do it like the way you have done
#video = Video.new(
:title => params[:video][:title],
:description => params[:video][:description]
)

Resources