I'm trying to implement the has_many pattern shown in "Ruby on Rails Nested Attributes". I'm combining it with some of my own methods and know exactly where it's raising an exception and why. I just don't know how to fix it. I'm using accepts_nested_attributes. I have a class called ProfilePhones.
In profile_email.rb:
def self.attrs
column_names.map(&:to_sym) - [:created_at, :updated_at]
end
The use the above for nested attributes so if the model changes it doesn't break the other controller. In the profiles_controller.rb I have:
def profile_params
params.require(:profile).permit(.... profile_phones_attributes: ProfilePhone.attrs)
In the Profile views folder, I have _profile_email_fields.html.erb with the fields for the ProfilePhone records:
<%= f.text_field :kind, placeholder: "Type" %>
<%= f.text_field :email_address, placeholder: "Email" %>
There's a bit more in this partial, but I'm simplifying it because the partial functions fine. In the main _form partial I have the following:
<%= f.fields_for :profile_emails do |f| %>
<%= render 'profile_email_fields', f: f %>
<%= link_to_add_fields('Add Another Email', f, :profile_emails) %>
<% end %>
In application_helper.rb:
def link_to_add_fields(name = nil, f = nil, association = nil, options = nil, html_options = nil, &block)
f, association, options, html_options = name, f, association, options if block_given?
options = {} if options.nil?
html_options = {} if html_options.nil?
if options.include? :locals
locals = options[:locals]
else
locals = { }
end
if options.include? :partial
partial = options[:partial]
else
partial = association.to_s.singularize + '_fields'
end
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, child_index: 'new_record') do |builder|
render(partial, locals.merge!( f: builder))
end
html_options['data-form-prepend'] = raw CGI::escapeHTML( fields )
html_options['href'] = '#'
content_tag(:a, name, html_options, &block)
end
Finally, in the profiles.coffee:
$('[data-form-prepend]').click (e) ->
obj = $($(this).attr('data-form-prepend'))
obj.find('input, select, textarea').each ->
$(this).attr 'name', ->
$(this).attr('name').replace 'new_record', (new Date).getTime()
return
obj.insertBefore this
false
The problem is in the application_helper method above on the following line:
new_object = f.object.class.reflect_on_association(association).klass.new
f.object.class returns:
ProfileEmail(id: integer, kind: string, email_address: string, profile_id: integer, created_at: datetime, updated_at: datetime)
association is set as :profile_emails. The problem is that this produces Nil. Also, I need to reflect on the model it belongs to Profile. When I switch out with:
Profile.reflect_on_association(association).klass.new
It returns:
-> #<ProfileEmail id: nil, kind: nil, email_address: nil, profile_id: nil, created_at: nil, updated_at: nil>
Which is what I want. However, when I go to the view and click on 'Add another email' link nothing happens. This probably an issue either with my coffeescript or a consequence of calling Profile explicitly. I'm not sure.
My two problems are:
In my reflection method I should be reflecting on Profile and not Profile email, but I'm not sure how to fix it. I can get a string of the profile name, but that doesn't help.
When I explicitly call Profile in the helper method, nothing happens.
one problem seems to be that you have shadowing variable names for the form_builder parameter here:
<%= f.fields_for :profile_emails do |f| %>
<%= render 'profile_email_fields', f: f %>
<%= link_to_add_fields('Add Another Email', f, :profile_emails) %>
<% end %>
try changing to
<%= f.fields_for :profile_emails do |ff| %>
<%= render 'profile_email_fields', f: ff %>
<%= link_to_add_fields('Add Another Email', f, :profile_emails) %>
<% end %>
Related
I built a form_for with 4 associated models. I created nested fields_for which can be added dynamically with the code below. My form works if I call the 'edit' method but if I use the method below to add new data the fields_for remains empty and are not shown.
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
This works fine if I only wants to add one form of a model. But if the added content which have fields_for for nested fields_for the text_fields are not created. In my opinion it is because I do not build the associations. How can I fix this method to do this?
I have a very similar method (based on Itay Grudev's blog post), in which I set the child_index as nil and then use jQuery to replace it with a timestamp (so that many records can be added avoiding them having the same id).
So, in my application_helper.rb file I've got:
def link_to_add_fields(name = nil, f = nil, association = nil, options = nil, html_options = nil, &block)
f, association, options, html_options = name, f, association, options if block_given?
options = {} if options.nil?
html_options = {} if html_options.nil?
if options.include? :locals
locals = options[:locals]
else
locals = { }
end
if options.include? :partial
partial = options[:partial]
else
partial = association.to_s.singularize + '_fields'
end
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, child_index: nil) do |builder|
render(partial, locals.merge!( fields: builder))
end
html_options['data-form-prepend'] = raw CGI::escapeHTML( fields )
html_options['href'] = '#'
content_tag(:a, name, html_options, &block)
end
And then I added a Coffeescript file called dynamic_tables.js.coffee in which I find the newly added fields, replace their name with a timestamp (so they're all indexed as a group) and prepend the form to a target (a hidden element I added at the bottom of my table and marked it as prepend_target). So I've got:
$('[data-form-prepend]').click (e) ->
target = $($(this).attr('prepend_target'))
obj = $($(this).attr('data-form-prepend'))
current_time = (new Date).getTime()
obj.find('input, select, textarea').each ->
$(this).attr 'name', ->
$(this).attr('name').replace 0, current_time
return
obj.insertBefore target
false
That should do the trick. Hope this helps.
I'm making an application where the user can search Amazon (with Vacuum) through my application for books, then be able to record the data of the book to their library.
When you search for a book, it goes through every result and puts each in a thumbnail. In every thumbnail there is a button that opens a modal with a form with hidden tags. When the user clicks the submit button, the book's title is saved into a new book. The only problem is that the title is saved like {:value=>"the title of the book that was saved"}
Here is the part of new.html.erb which has the search box:
<%= form_tag({controller: "books", action: "new"}, method: "get", id: "search-form") do %>
<%= text_field_tag :keywords, params[:keywords], placeholder: "Search for a book", class: "form-control" %>
<% end %>
Here is the part of new.html.erb which has the hidden form:
<% #results.each do |result| %>
…
<%= form_for #book do |f|%>
<%= hidden_field_tag :title, class: 'form-control', value: result.name %>
<%= f.submit "Add book", class: "btn btn-default green-hover" %>
<% end %>
…
<% end %>
Here are the new and create actions in my controller:
def new
#book = current_user.books.build if logged_in?
# Search actions
if params[:keywords]
request = Vacuum.new
request.configure(
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
associate_tag: 'my associate tag is here'
)
keywords = params[:keywords]
params = {
'SearchIndex' => 'Books',
'Keywords'=> keywords,
'ResponseGroup' => "ItemAttributes,Images"
}
raw_results = request.item_search(query: params)
hashed_results = raw_results.to_h
#results = []
hashed_results['ItemSearchResponse']['Items']['Item'].each do |item|
result = OpenStruct.new
result.title = item['ItemAttributes']['Title']
result.url = item['DetailPageURL']
result.image_url = item['MediumImage']['URL']
result.author = item['ItemAttributes']['Author']
result.pages = item['ItemAttributes']['NumberOfPages']
#results << result
end
end
end
def create
#book = #list.books.build(book_params)
if #book.save
flash[:success] = #book.title + "was added to your log."
redirect_to list_path(#book.list_id)
else
render 'books/new'
end
end
I tried to use gsub within book.rb to fix it, but that only changed the text within the flash message and it still saved as {:value=>"the title of the book that was saved"}.
after_create :init
private
def init
puts "Init was called!"
self.title.gsub!('{:value=>"', " ")
self.title.gsub!('"}', " ")
end
How can I change it so that it doesn't save the title with the {:value=>} around it?
I don't think the hidden field tag is right.
<%= hidden_field_tag :title, class: 'form-control', value: result.name %>
Try
<%= hidden_field_tag :title, result.name %>
Your title is being saved as a hash not a string. Use hash accessing methods:
t = title[:value]
puts t #=> "the tile of the book that was saved"
I'm trying to permit an array with an arbitrary number of values, but Rails throws Unpermitted parameter: service_rates every time. I tried a lot of things (Rails 4 Unpermitted Parameters for Array, Unpermitted parameters for Dynamic Forms in Rails 4, ...) but nothing works.
The field's name is service_rates and it's column type is jsonb.
I want to create a JSON object from an arbitrary number of input fields:
<%= f.hidden_field :service_ids, value: #services.map(&:id) %>
<% #services.each do |service| %>
<tr>
<td>
<% value = #project.service_rates ? #project.service_rates["#{service.id}"]['value'] : '' %>
<%= text_field_tag "project[service_rates][#{service.id}]", value, class: 'uk-width-1-1', placeholder: 'Stundensatz' %>
</td>
</tr>
<% end %>
So my POST data looks like this:
project[service_rates][1] = 100
project[service_rates][2] = 95
project[service_rates][3] = 75
Currently service_rates is permitted via whitelisting with tap:
def project_params
params.require(:project).permit(:field1, :field2, […], :service_ids).tap do |whitelisted|
whitelisted[:service_rates] = params[:project][:service_rates]
end
end
At least, I'm building a JSON object in a private model function (which throws this error):
class Project < ActiveRecord::Base
before_save :assign_accounting_content
attr_accessor :service_ids
private
def assign_accounting_content
if self.rate_type == 'per_service'
service_rates = {}
self.service_ids.split(' ').each do |id|
service_rates["#{id}"] = {
'value': self.service_rates["#{id}"]
}
end
self.service_rates = service_rates
end
end
end
I've also tried to permit the field like that …
params.require(:project).permit(:field1, :field2, […], :service_rates => [])
… and that …
params.require(:project).permit(:field1, :field2, […], { :service_rates => [] })
… but this doesn't work either.
When I try this …
params.require(:project).permit(:field1, :field2, […], { :service_rates => [:id] })
… I get this: Unpermitted parameters: 1, 3, 2
It's not really clear what service_rates is for you. Is it the name of an association ? Or just an array of strings ?
To allow array of strings : :array => [],
To allow nested params for association : association_attributes: [:id, :_destroy, ...]
params.require(:object).permit(
:something,
:something_else,
....
# For an array (of strings) : like this (AFTER every other "normal" fields)
:service_rates => [],
# For nested params : After standard fields + array fields
service_rates_attributes: [
:id,
...
]
)
As I explained in the comments, the order matters. Your whitelisted array must appear AFTER every classic fields
EDIT
Your form should use f.fields_for for nested attributes
<%= form_for #project do |f| %>
<%= f.fields_for :service_rates do |sr| %>
<tr>
<td>
<%= sr.text_field(:value, class: 'uk-width-1-1', placeholder: 'Stundensatz' %>
</td>
</tr>
<% end %>
<% end %>
Let's say I have on my controller:
#parts = Part.all
and on the view form:
<%= f.collection_select(:part_id, #parts, :id, :title, { :prompt => true } %>
It's working for the new/create actions, edit action works too but I would like to know how to get the selected value for doing a find on another model, since it's a nested form.
Or the only way is get it using some javascript?
Doing:
<%= debug f.object.part %>
I get this:
--- !ruby/object:Part
attributes:
id: 1
title: Pearl 02
part_type_id: 36
created_at: 2011-07-28 07:52:09.000000000Z
updated_at: 2011-07-28 08:34:02.000000000Z
set: !!null
price: 3.53
changed_attributes: {}
previously_changed: {}
attributes_cache: {}
marked_for_destruction: false
destroyed: false
readonly: false
new_record: false
Which the only attribute I want for now is "price".
Have tried to access the value doing:
<%= f.object.part.price %>
but then it returns an error:
undefined method `price' for nil:NilClass
the trace shows me something related to an helper method I have also.
app/views/items/_item_part_fields.html.erb:7:in `_app_views_items__item_part_fields_html_erb___1683960156823546996_2175548540_683257483947746572'
app/helpers/application_helper.rb:46:in `block in link_to_add_fields'
app/helpers/application_helper.rb:45:in `link_to_add_fields'
the helper method that causes this error:
def link_to_add_fields(name, f, association)
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(association.to_s.singularize + "_fields", :f => builder)
end
link_to_function(name, "add_fields(this, '#{association}', '#{escape_javascript(fields)}')" )
end
Removing the link to this helper method and calling f.object.part.price works.
What you think?
<% f.object.part_id = XXX %>
<%= f.collection_select(:part_id, #parts, :id, :title, { :prompt => true } %>
Or better to set this stuff in controller (since it is nested form I can't write solution with controller)
I have the following problem. I have a form which takes input for a "Chart" object. But after processing the form, i wish to display one of the values, and it adds the key of this value.
Class model
class Chart
attr_accessor :title, :series
def initialize(title = nil, series = [])
#title, #series = title, series
end
end
View of form:
<% form_for :chart , :url => { :action => "show" } do |f| %>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>...
<% end %>
Chart controller, show method:
def show
#chart = Chart.new(params[:chart])
end
View of show:
<h2><%=h #chart.title %></h2>
Which displays: "title"input_forms_title""
for example: writing in the input form: Economy, prints in the show view: "titleEconomy"
Any ideas?
I have just figured it out. The problem was in the constructor or initialize method. By changing the initialize method to:
def initialize( options = {} )
#title = options[:title]
#series = []
end
It now accepts all params perfectly!