I have two models
Project which has has_and_belongs_to_many :users
and
Users which has has_and_belongs_to_many :projects
in project view I have a form that has a selector where I want to try and assign multiple users to a project.
<%= select_tag("project[users]", options_for_select(#users, #project_users), {:multiple=>true, :size=>6} )%>
in my project controller the variables that get used in the select are
#project_users = #project.users.collect { |user| user.id}
#users = User.all.collect { |user| ["#{user.first_name} #{user.last_name}", user.id] }
which all out puts
<select id="project_users" multiple="multiple" name="project[users][]" size="6">
<option value="#<User:0x007f567cb7f078>">User1</option>
<option value="#<User:0x007f567cb7e9c0>">User2</option>
</select>
The problem is that this is not the equivalent to
#some_project << [User(#), User(#)]
("note User(#) represent class instance")
instead its the equivalent to
#some_project << ["1", "2"]
The problem is the user instance gets converted to string but not back into the instance again.
Which does not work and will throw an error as
ActiveRecord::AssociationTypeMismatch in ProjectsController#update
User(#70004716784160) expected, got String(#4266680)
How can I make this work correctly?
In your User model:
def full_name
[first_name, last_name].join(" ")
end
In your controller
#users = User.all
In your view:
<%= select_tag('project[user_ids]', options_from_collection_for_select(#users, 'id', 'full_name'), { :multiple => true, :size => 6 }) %>
Then you can just use
#project.user_ids = params[:project][:user_ids]
or
#project.update_attributes(params[:project])
for assignment.
You can't send the instance. It can change between the render of the form and the submit. The conversion to string is a one way operation, as it refers to the object's unique id (like memory address), not it's properties or database identifier! As it is not part of the ActiveRecord but the ruby's object base. When the data of the form is returned, the instance is not in the memory, so you can't convert it back, even if it would be possible otherwise.
Stick to the plain old way:
#project_users = #project.users.collect { |user| user.id}
#users = User.all.collect { |user| ["#{user.first_name} #{user.last_name}", user.id] }
And when the data is submitted:
#users = params[:project][:users].map{|a| User.find(a) }
Related
I have a form that allows user to register for a tournament. In the process of building the registration form I hae dynamic nested fields with so far field_type of basic. When I load the participants new form I am trying to load all the fields from the Fields table with field_type of basic. It will find them and if I just try <%= #basic.name %> from the new form it will give the name of the last field in the database with that field_type, but if I try:
<% #basic.each do |b| %>
<%= b.name %>
<% end
I get the error undefined method `each' for #<Field.
Here is the new action from the participants_controller:
def new
#participant = #event.participants.new
#user = User.find(current_user.id)
#children = #user.children
#basic = Field.find_by(event_id: #event.id, field_type: 'basic')
end
Fields belong to events but do I have to connect them to participants to make this work?
Thanks
find_by only returns a single record (or nil if the criteria aren't matched) use where to return a collection.
#basic = Field.where(event_id: #event.id, field_type: 'basic')
However assuming you have the association has_many :fields defined in Event you could also use:
#basic = #event.fields.where(field_type: 'basic')
And if you have the scope :basic, -> { where(field_type: 'basic') } defined in Field you can further simplify to:
#basic = #event.fields.basic
I have a form_for for creating a new record. I have set getter and setter methods to access form field in my view. Below are my getter ans setter methods with my form view,
Getter & Setter Methods respectively :
def manufacturer_model_name
self.manufacturer_models.pluck(:name).join(', ') unless self.manufacturer_models.blank?
end
def manufacturer_model_name=(names)
names = names.split(',').map{|n| n.strip}.delete_if(&:empty?) if names.present?
names.uniq.each do |name|
id = ManufacturerModel.where(:name => name, :manufacturer_id => manufacturer.id).first_or_create.id
if self.new_record?
self.user_skill_manufacturer_models.build(:user_skill_id => self.id, :manufacturer_model_id => id)
else
self.user_skill_manufacturer_models.where(:user_skill_id => self.id, :manufacturer_model_id => id).first_or_create.id
end
end if names.present?
end
Form View:
= f.text_field :manufacturer_model_name, :class => 'form-control
My problem is that, my input field is autocomplete with multiple set to true, to get multiple values. when user enters multiple comma separated values and submits form and if there are any errors on the form, my new action is rendered and user losts all the entered values forcing him to reenter all again. How can I solve this problem?
It would be better to make a manufacturer_model_name_form field or some such via attr_accessor, and then parse that in validate. It would look something like this:
class ManufacturerModel < ActiveRecord::Base
attr_accessor :manufacturer_model_name_form
validate :validate_manufacturer_model_name
def validate_manufacturer_model_name
errors.add(:manufacturer_model_name, 'not a valid manufacturer model name') if !!true # perform your validation here in place of `!!true`
end
end
Then, in your form, you would use manufacturer_model_name_form instead of manufacturer_model_name:
= f.text_field :manufacturer_model_name_form, :class => 'form-control'
I've a Rails model called Biography, and biography has one lifestyle.
class Lifestyle < ActiveRecord::Base
belongs_to :biography
end
class Biography < ActiveRecord::Base
has_one :lifestyle, dependent: :destroy
accepts_nested_attributes_for :lifestyle
end
And In my BiographyController I've this:
def update_biography
biography = current_user.biography
logger.debug("params are: #{params}")
logger.debug("biography_params are: #{biography_params}")
if biography.update(biography_params)
render :json => biography
else
render :json => { :errors => biography.errors.full_messages }, :status => 400
end
end
def biography_params
params.require(:biography).permit(
:disability, :hiv_positive, :blood_type,
lifestyle_attributes: [:id, :diet, :smoke, :drink])
end
And this is what I get from my two logger.debug statements above:
params are: {"lifestyle_attributes"=>{"diet"=>"2", "smoke"=>"false", "drink"=>"2"}, "disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3", "controller"=>"biographies", "action"=>"update_biography", "id"=>"4", "biography"=>{"disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3"}}
biography_params are: {"disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3"}
Why is that my biography_params do not contain lifestyle_attributes even though I've accepts_nested_attributes_for statment in the Biography model, and also defining association between Biography and Lifestyle in the models? I've also added lifestyle_attributes in the strong parameters permit list.
However, if I run this in rails console the assignment does work:
b = Biography.first
b.update("lifestyle_attributes"=>{"diet"=>"2", "smoke"=>"false", "drink"=>"2"})
require and permit are actually the method of ActionController::Parameters. The require which in this case is the :biography needs to be present in the hash you are sending from your backbone views.
The require method ensures that a specific parameter is present, and if it's not provided, the require method throws an error. It returns an instance of ActionController::Parameters for the key passed into require i.e :biography.
You can try
params = {biography: {first_name: "new", last_name: "user", disability: false, hiv_positive: false, blood_type: 3, "lifestyle_attributes: {diet: "2", smoke: "false", drink: "2"}}
If you do not want biography: on your params you can ignore require(:biography) on params.require(:biography) to just params.permit(...)
Hope now it will work
You get more info on Nested Attributes
The problem is that lifestyle_attributes are not a part of the biography params hash. You should have:
params: {
biography: {
lifestyle_attributes: {
...
}
}
}
This will allow the params method to access the data properly.
To explain how it works, you need to look at how the ActionController::Parameters class works:
Returns a new ActionController::Parameters instance that includes only the given filters and sets the permitted attribute for the object to true.
Each time you use params.require(:x).permit(:y), it will return a new hash with only the permitted params. These permitted params have to be nested within the required param.
As you've demonstrated, this works well...
biography_params are: {"disability"=>"false", "hiv_positive"=>"false", "blood_type"=>"3"}
The problem is that because lifestyle_attributes is not nested under biography, its parameters are not returned after you call the params method.
The fix for this will be in your form:
#app/views/biographies/new.html.erb
<%= form_for #biography do |f| %>
<%= ... biography attributes %>
<%= f.fields_for :lifestyle do |l| %>
<%= lifestyle fields %>
<% end %>
<%= f.submit %>
<% end %>
I don't know how you've done it currently, but somehow, you've attached lifestyle attributes outside of the biography hash.
Currently, I have an action in my customers controller generating an array of names, #username_array, of all objects of class User with which to populate a drop down menu in a form that creates a new object of class Customer. The form element looks like this right now:
<%= f.select :user_id, #username_array %>
What I'd really like is for the id of the user to be sent into params[:customer][:user_id] instead of the name of that user that is chosen in the drop down. So in my create action in my customers controller I have the following code:
#customer = Customer.new(params[:customer])
#user = User.find_by_name(params[:customer][:user_id]) # where :user_id is actually currently the name selected from the drop down
#customer.user_id = #user.id
Is there an easier way of doing this?
Change your #username_array to include both the name and the id of the user:
Instead of:
["Bob","Sally","Dave"]
Make it:
[["Bob", 1],["Sally",2],["Dave",3]]
This could be accomplished by something like this:
#username_array = User.all.map {|user| [user.name, user.id]}
Then, f.select will display the name in the dropdown, but the actual value passed in through params[:customer][:user_id] will be the id of the user, which is what you want. With this in place, the following is all you need in the controller action code:
#customer = Customer.new(params[:customer])
You won't have to look up the user by name, the params hash will already have the correct id value.
Note that instead of making #username_array an instance variable you could just create a utility method in the helper for this controller, or the application helper:
def user_select
User.all.map {|user| [user.name, user.id]}
end
Then, in your view:
<%= f.select :user_id, user_select %>
If you put this in your application helper, you can use it everywhere and only have the code in one place (DRY).
you can do
#user = User.find_by_name(params[:customer][:user_id])
#user.customers.new(params[:customer])
or
#user = User.find_by_name(params[:customer][:user_id])
#customer = #user.customers.create(params[:customer])
but to do that you must have the relation (has_many, belongs_to,...)
or
Customer.new(params[:customer], :user_id => params[:customer][:user_id])
or
f.collection_select :user_id, #username_array, :id, :name
I have a model that has an attribute that is an Array. What's the proper way for me to populate that attribute from a form submission?
I know having a form input with a field whose name includes brackets creates a hash from the input. Should I just be taking that and stepping through it in the controller to massage it into an array?
Example to make it less abstract:
class Article
serialize :links, Array
end
The links variable takes the form of a an array of URLs, i.e. [["http://www.google.com"], ["http://stackoverflow.com"]]
When I use something like the following in my form, it creates a hash:
<%= hidden_field_tag "article[links][#{url}]", :track, :value => nil %>
The resultant hash looks like this:
"links" => {"http://www.google.com" => "", "http://stackoverflow.com" => ""}
If I don't include the url in the name of the link, additional values clobber each other:
<%= hidden_field_tag "article[links]", :track, :value => url %>
The result looks like this: "links" => "http://stackoverflow.com"
If your html form has input fields with empty square brackets, then they will be turned into an array inside params in the controller.
# Eg multiple input fields all with the same name:
<input type="textbox" name="course[track_codes][]" ...>
# will become the Array
params["course"]["track_codes"]
# with an element for each of the input fields with the same name
Added:
Note that the rails helpers are not setup to do the array trick auto-magically. So you may have to create the name attributes manually. Also, checkboxes have their own issues if using the rails helpers since the checkbox helpers create additional hidden fields to handle the unchecked case.
= simple_form_for #article do |f|
= f.input_field :name, multiple: true
= f.input_field :name, multiple: true
= f.submit
TL;DR version of HTML [] convention:
Array:
<input type="textbox" name="course[track_codes][]", value="a">
<input type="textbox" name="course[track_codes][]", value="b">
<input type="textbox" name="course[track_codes][]", value="c">
Params received:
{ course: { track_codes: ['a', 'b', 'c'] } }
Hash
<input type="textbox" name="course[track_codes][x]", value="a">
<input type="textbox" name="course[track_codes][y]", value="b">
<input type="textbox" name="course[track_codes][z]", value="c">
Params received:
{ course: { track_codes: { x: 'a', y: 'b', z: 'c' } }
I've also found out that if pass your input helper like this you will get an array of courses each one with its own attributes.
# Eg multiple input fields all with the same name:
<input type="textbox" name="course[][track_codes]" ...>
# will become the Array
params["course"]
# where you can get the values of all your attributes like this:
params["course"].each do |course|
course["track_codes"]
end
I just set up a solution using jquery taginput:
http://xoxco.com/projects/code/tagsinput/
I wrote a custom simple_form extension
# for use with: http://xoxco.com/projects/code/tagsinput/
class TagInput < SimpleForm::Inputs::Base
def input
#builder.text_field(attribute_name, input_html_options.merge(value: object.value.join(',')))
end
end
A coffeescrpt snippet:
$('input.tag').tagsInput()
And a tweak to my controller, which sadly has to be slightly specific:
#user = User.find(params[:id])
attrs = params[:user]
if #user.some_field.is_a? Array
attrs[:some_field] = attrs[:some_field].split(',')
end
I had a similar issue, but wanted to let the user input a series of comma separated elements as the value for the array.
My migration uses rails new ability (or is it postrges' new ability?) to have an array as the column type
add_column :articles, :links, :string, array: true, default: []
the form can then take this input
<%= text_field_tag "article[links][]", #article.links %>
and it means the controller can operate pretty smoothly as follows
def create
split_links
Article.create(article_params)
end
private
def split_links
params[:article][:links] = params[:article][:links].first.split(",").map(&:strip)
end
params.require(:article).permit(links: [])
Now the user can input as many links as they like, and the form behaves properly on both create and update. And I can still use the strong params.
For those who use simple form, you may consider this solution. Basically need to set up your own input and use it as :array. Then you would need to handle input in your controller level.
#inside lib/utitilies
class ArrayInput < SimpleForm::Inputs::Base
def input
#builder.text_field(attribute_name, input_html_options.merge!({value: object.premium_keyword.join(',')}))
end
end
#inside view/_form
...
= f.input :premium_keyword, as: :array, label: 'Premium Keyword (case insensitive, comma seperated)'
#inside controller
def update
pkw = params[:restaurant][:premium_keyword]
if pkw.present?
pkw = pkw.split(", ")
params[:restaurant][:premium_keyword] = pkw
end
if #restaurant.update_attributes(params[:restaurant])
redirect_to admin_city_restaurants_path, flash: { success: "You have successfully edited a restaurant"}
else
render :edit
end
end
In your case just change :premium_keyword to the your array field
I had some trouble editing the array after implementing this for my new.html.erb, so I'll drop my solution to that problem here:
Edit a model property of type array with Rails form?