RoR : Mongoid and form create hash - ruby-on-rails

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]
)

Related

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"

Rails Access field values that are not from model on controller

I have a form for some model, inside this form I have some text_fields and hidden_fields
that i need to use in the controller but that are not from the model.
this is a simplified version of it
<%= from_for #user do |f| %>
<%= f.text_field :name %>
<%= hidden_field :photo, value: 'blabla' %>
<%= text_field :type %>
<% f.submit %>
Lets say that the :photo and :type parameters are not in the model user but i need them to decide how to create the user.
they are going in the params hash, but all messed up. How do I access their value?
Thank you
hidden_field_tag "photo", "photo_value"
=> <input id="photo" name="photo" type="hidden" value="photo_value" />
Then in your controller:
#hidden_photo = params[:photo]
Whenever you are working with a form and want a value not associated with a model or object, then use the helpers ending in "*_tag"

How do I create default values in a Ruby Form?

This might be a really basic question but how do I create default values in forms?
I'm trying to put the <%= params[:id] %> of the page in as a default hidden value in this form.
`<% form_for(#revision) do |f| %>
<%= f.label :background_title %><br />
<%= f.text_field :background_title %><%= params[:id] %>
<%= f.label :title %><br />
<%= f.text_field :title %>
<%= f.submit 'Create' %>
<% end %>`
Thanks.
The form is linked to the object you pass to form_for, so set the value on the object before you start the form. For example, in the controller:
#revision.id = params[:id]
then in the form:
<%= f.hidden_field :id %>
However, I hope this is an example and you're not actually setting the ID (primary key) of an object based on a URL parameter...
If you are trying to create a new object, you can set the default values when you first instantiate a new object
def new
#revision = Revision.new(
:background_title => "Some Background Title",
:title => "Some Title"
)
end
then automatically the value of the fields will be set accordingly =) it's just that simple ;-)

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)

How to edit a Rails serialized field in a form?

I have a data model in my Rails project that has a serialized field:
class Widget < ActiveRecord::Base
serialize :options
end
The options field can have variable data info. For example, here is the options field for one record from the fixtures file:
options:
query_id: 2
axis_y: 'percent'
axis_x: 'text'
units: '%'
css_class: 'occupancy'
dom_hook: '#average-occupancy-by-day'
table_scale: 1
My question is what is the proper way to let a user edit this info in a standard form view?
If you just use a simple text area field for the options field, you would just get a yaml dump representation and that data would just be sent back as a string.
What is the best/proper way to edit a serialized hash field like this in Rails?
If you know what the option keys are going to be in advance, you can declare special getters and setters for them like so:
class Widget < ActiveRecord::Base
serialize :options
def self.serialized_attr_accessor(*args)
args.each do |method_name|
eval "
def #{method_name}
(self.options || {})[:#{method_name}]
end
def #{method_name}=(value)
self.options ||= {}
self.options[:#{method_name}] = value
end
attr_accessible :#{method_name}
"
end
end
serialized_attr_accessor :query_id, :axis_y, :axis_x, :units
end
The nice thing about this is that it exposes the components of the options array as attributes, which allows you to use the Rails form helpers like so:
#haml
- form_for #widget do |f|
= f.text_field :axis_y
= f.text_field :axis_x
= f.text_field :unit
Well, I had the same problem, and tried not to over-engineer it. The problem is, that although you can pass the serialized hash to fields_for, the fields for function will think, it is an option hash (and not your object), and set the form object to nil. This means, that although you can edit the values, they will not appear after editing. It might be a bug or unexpected behavior of rails and maybe fixed in the future.
However, for now, it is quite easy to get it working (though it took me the whole morning to figure out).
You can leave you model as is and in the view you need to give fields for the object as an open struct. That will properly set the record object (so f2.object will return your options) and secondly it lets the text_field builder access the value from your object/params.
Since I included " || {}", it will work with new/create forms, too.
= form_for #widget do |f|
= f.fields_for :options, OpenStruct.new(f.object.options || {}) do |f2|
= f2.text_field :axis_y
= f2.text_field :axis_x
= f2.text_field :unit
Have a great day
emh is almost there. I would think that Rails would return the values to the form fields but it does not. So you can just put it in there manually in the ":value =>" parameter for each field. It doesn't look slick, but it works.
Here it is from top to bottom:
class Widget < ActiveRecord::Base
serialize :options, Hash
end
<%= form_for :widget, #widget, :url => {:action => "update"}, :html => {:method => :put} do |f| %>
<%= f.error_messages %>
<%= f.fields_for :options do |o| %>
<%= o.text_field :axis_x, :size => 10, :value => #widget.options["axis_x"] %>
<%= o.text_field :axis_y, :size => 10, :value => #widget.options["axis_y"] %>
<% end %>
<% end %>
Any field you add in the "fields_for" will show up in the serialized hash. You can add or remove fields at will. They will be passed as attributes to the "options" hash and stored as YAML.
I've been struggling with a very similar problem. The solutions I found here were very helpful to me. Thank you #austinfromboston, #Christian-Butske, #sbzoom, and everyone else. However, I think these answers might be slightly out-of-date. Here's what worked for me with Rails 5 and ruby 2.3:
In the form:
<%= f.label :options %>
<%= f.fields_for :options do |o| %>
<%= o.label :axis_y %>
<%= o.text_field :axis_y %>
<%= o.label :axis_x %>
<%= o.text_field :axis_x %>
...
<% end %>
and then in the controller I had to update the strong parameters like so:
def widget_params
params.require(:widget).permit(:any, :regular, :parameters, :options => [:axis_y, :axis_x, ...])
end
It seems to be important that the serialized hash parameter comes at the end of the list of parameters. Otherwise, Rails will expect the next parameter to also be a serialized hash.
In the view I used some simple if/then logic to only display the hash if it is not empty and then to only display key/value pairs where the value was not nil.
I was facing the same issue, after some research i found a solution using Rails' store_accessor to make keys of a serialized column accessible as attributes.
With this we can access "nested" attributes of a serialized column …
# post.rb
class Post < ApplicationRecord
serialize :options
store_accessor :options, :value1, :value2, :value3
end
# set / get values
post = Post.new
post.value1 = "foo"
post.value1
#=> "foo"
post.options['value1']
#=> "foo"
# strong parameters in posts_controller.rb
params.require(:post).permit(:value1, :value2, :value3)
# form.html.erb
<%= form_with model: #post, local: true do |f| %>
<%= f.label :value1 %>
<%= f.text_field :value1 %>
# …
<% end %>
No need setter/getters, I just defined in the model:
serialize :content_hash, Hash
Then in the view, I do (with simple_form, but similar with vanilla Rails):
= f.simple_fields_for :content_hash do |chf|
- #model_instance.content_hash.each_pair do |k,v|
=chf.input k.to_sym, :as => :string, :input_html => {:value => v}
My last issue is how to let the user add a new key/value pair.
I will suggest something simple, because all the time, when user will save form You will get string. So You can use for example before filter and parse those data like that:
before_save do
widget.options = YAML.parse(widget.options).to_ruby
end
of course You should add validation if this is correct YAML.
But it should works.
I'm trying to do something similar and I found this sort of works:
<%= form_for #search do |f| %>
<%= f.fields_for :params, #search.params do |p| %>
<%= p.select "property_id", [[ "All", 0 ]] + PropertyType.all.collect { |pt| [ pt.value, pt.id ] } %>
<%= p.text_field :min_square_footage, :size => 10, :placeholder => "Min" %>
<%= p.text_field :max_square_footage, :size => 10, :placeholder => "Max" %>
<% end %>
<% end %>
except that the form fields aren't populated when the form is rendered. when the form is submitted the values come through just fine and i can do:
#search = Search.new(params[:search])
so its "half" working...

Resources