I have a rails application where I have two models called Column and Row:
Column:
has_many :rows
accepts_nested_attributes_for :rows, :reject_if => lambda { |b| b[:data].blank? }
Row:
belongs_to :column
And the form:
<%= form_for [#table, #row] do |f| %>
<% #columns.each do |column| %>
<%= f.label :data %><br>
<%= f.text_area :data %>
<% end %>
<%= f.submit %>
<% end %>
and my create action in the controller:
#row = Row.new(row_params)
if #row.save
redirect_to table_rows_path, notice: 'row was successfully created.'
else
render action: 'new'
end
and my new action:
#columns = Column.where(:table_id => #table.id)
#row = Row.new(id: #table.id)
So I have two problems. The first is if I have say two columns, so there will be two textfields on the new row page, and I enter "Test" in the first text field and "Another Test" in the second. The only thing that is getting saved is the second. The first one saves "Another Test" instead of "Test".
Also, how can I get the row (which belongs to a column) to save a column_id inside each row?
Thanks for all help!
You are setting the new Row's id to the #table.id. Take that out; you never need to create a new .id. Then use column_id: params[:column_id] to link them up.
Related
I followed thos steps http://railscasts.com/episodes/197-nested-model-form-part-2
I created a select box with suppliers so when I select a supplier will show all purchases that has a supplier selected updating the nested form.
Here my tables
suppliers
|id| |name|
1 AAAA
2 BBBB
shopping_documents
|id| |supplier_id|
1 1
2 2
shopping_products
|id| |shopping_document_id| |qty| |purchase_product_id|
1 1 1 1
2 1 1 2
3 2 1 3
purchase_products
|id| |name| |description| |cost| |supplier_id|
1 XP CD-ROM 1000 1
2 VISTA CD-ROM 2000 1
3 W7 CD-ROM 3000 2
Here is the controller /app/controller/purchase_product_controller.rb
class ShoppingDocumentController < ApplicationController
def new
#document = ShoppingDocument.new
#suppliers= Supplier.all
#purchases = PurchaseProduct.all
1.times do
shopping_product = #document.shopping_products.build
end
end
def create
#document = ShoppingDocument.new(params[:shopping_document])
if #document.save
flash[:notice] = "Successfully created document."
redirect_to :action=>"index"
else
render :action => 'new'
end
end
def update_nested_div
#purchases= PurchaseProduct.find(:all,:conditions=>['supplier_id =? ',params[:suppliers] ])
end
end
Here models:
class ShoppingDocument < ActiveRecord::Base
has_many :shopping_products
accepts_nested_attributes_for :shopping_products ,:allow_destroy => true
end
class ShoppingProduct < ActiveRecord::Base
belongs_to :shopping_document
belongs_to :purchase_product
end
class PurchaseProduct < ActiveRecord::Base
has_many :shopping_products
end
Here is the view: /app/view/purchase_product/new.html.erb
<% form_for #document, :url => {:controller=>"shopping_document",:action=>'create'} do |f| %>
Name: <%= select_tag 'suppliers',"<option value=\"\">Select</option>"+options_for_select(#suppliers.collect {|t| [t.name,t.id]} ),:onchange=>remote_function(:url=>{:controller=>"shopping_document",:action=>"update_nested_div"},:with=>"'suppliers=' + $('suppliers').value"),:name=>"shopping_document[supplier_id]" %>
<% f.fields_for :shopping_products do |builder| %>
<%= render "shopping_product_fields", :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add Product", f, :shopping_products %></p>
<p><%= f.submit "Submit" %></p>
<% end %>
Here is the partial view: /app/view/shopping_document/_shopping_product_fields.html.erb
<div class="nested_form">
<%= f.select :purchase_product_id,#purchases.map { |c| [c.cost, c.id] },{},:id=>"helping" %> %>
Quantity: <%= f.text_field :qty %> <%= link_to_remove_fields "remove", f %>
</div>
Here is the view rjs : /app/view/shopping_document/_update_nested_div.rjs
page.replace_html 'helping', options_for_select(#purchases.map{|c| [c.cost, c.id]})
Is working perfect but is just working with the first nested select box , If I add click on "Add Product" the second or next or more won't be according the supplier selected and will show all suppliers.
Please somebody can help me with this issue?
Is just working only on the first nested select box if I add another won't show values according the supplier selected just show all .
EDIT 8/11 9pm
You can set a class instead of an id on the select tag you want to change the options of based on the supplier selection.
<%= f.select :purchase_product_id,#purchases.map { |c| [c.cost, c.id] }, {}, :class => "helping" %>
And then change your /app/view/shopping_document/_update_nested_div.rjs with the following:
page.select('.helping').each do |select_tag|
page.replace_html select_tag, options_for_select(#purchases.map{|c| [c.cost, c.id]})
end
The problem you had was that replace_html works on the first element it finds with the id "helping". With a class and page.select, we get a collection of select tags from which we have to the options by iterating on them.
The pattern I use is described in the Rails 2.3.8 api documentation. Check it out for the other methods available.
As the error mentions, f is not defined. Use select_tag (documentation at http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag) like this:
<%= select_tag :purchase_product_id,#purchases.map { |c| [c.cost, c.id] } %> %>
I created a combobox that show suppliers so when supplier is selected will update a div showing purchases , after showing purchases will be in combobox but in a nested form so when I select a purchase will show me the amount of the purchase selected.
The problem is that when I select a purchase only works in the first line and when I select another combobox line show the amount of the first combobox selected.
Here my tables
suppliers
|id| |name|
1 Supplier A
2 Supplier B
shopping_documents
|id| |supplier_id|
1 1
2 2
shopping_products
|id| |shopping_document_id| |qty| |purchase_product_id|
1 1 1 1
2 1 1 2
purchase_products
|id| |name| |price_sale|
1 XP 1000
2 VISTA 2000
supplier_products
|id| |supplier_id| |amount| |purchase_product_id
1 1 1000 1
2 1 2000 1
3 2 3000 2
Here is the controller /app/controller/purchase_product_controller.rb
class ShoppingDocumentController < ApplicationController
def new
#document = ShoppingDocument.new
#suppliers= Supplier.all
#products = SupplierProduct.find(:all,:conditions=>['supplier_id =? ',params[:suppliers] ],:group=>['purchase_product_id'])
#purchases= SupplierProduct.find(:all,:conditions=>['supplier_id =? ',params[:suppliers] ],:group=>['purchase_product_id'])
4.times do
shopping_product = #document.shopping_products.build
end
end
def create
#document = ShoppingDocument.new(params[:shopping_document])
if #document.save
flash[:notice] = "Successfully created document."
redirect_to :action=>"index"
else
render :action => 'new'
end
end
def update_supplier_div
#products = SupplierProduct.find(:all,:conditions=>['supplier_id =? ',params[:suppliers] ],:group=>['purchase_product_id'])
end
def update_product_div
#purchases= SupplierProduct.find(:all,:conditions=>['purchase_product_id =? ',params[:product] ],:group=>['purchase_product_id'])
end
end
Here models:
class ShoppingDocument < ActiveRecord::Base
has_many :shopping_products
accepts_nested_attributes_for :shopping_products ,:allow_destroy => true
end
class ShoppingProduct < ActiveRecord::Base
belongs_to :shopping_document
belongs_to :purchase_product
end
class PurchaseProduct < ActiveRecord::Base
has_many :shopping_products
has_many :supplier_products
end
class SupplierProduct < ActiveRecord::Base
belongs_to :purchase_product
end
Here is the view: /app/view/purchase_product/new.html.erb
<% form_for #document, :url => {:controller=>"shopping_document",:action=>'create'} do |f| %>
Name: <%= select_tag 'suppliers',"<option value=\"\">Select</option>"+options_for_select(#suppliers.collect {|t| [t.name,t.id]} ),:onchange=>remote_function(:url=>{:controller=>"shopping_document",:action=>"update_supplier_div"},:with=>"'suppliers=' + $('suppliers').value"),:name=>"shopping_document[supplier_id]" %>
<% f.fields_for :shopping_products do |builder| %>
<%= render "shopping_product_fields", :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add Product", f, :shopping_products %></p>
<p><%= f.submit "Submit" %></p>
<% end %>
Here is the partial view: /app/view/shopping_document/_shopping_product_fields.html.erb
<div class="fields">
<table>
<tr><td><%= f.text_field :qty , :size=>"9" %></td>
<td><%= select_tag "product","<option value=\"\">Select</option>"+options_for_select(#products.map { |c| [c.amount, c.id] }),:onchange=>remote_function(:url=>{:controller=>"shopping_document",:action=>"update_product_div"},:before=>"load_close('loading_condition')",:success=>"load_off('loading_condition')",:with=>"'product=' + $('product').value"),:class=>"product" %></td>
<td><%= f.select :purchase_product_id,"<option value=\"\">Select</option>"+options_for_select(#purchases.map { |c| [c.amount, c.id] }), {}, :class => "helping" %></td>
<td><%= link_to_remove_fields image_tag("standard/delete.png",:border=>0,:size=>"14x14"), f %></td>
</tr>
</table>
</div>
Here is the view rjs : /app/view/shopping_document/_update_supplier_div.rjs
page.select('.product').each do |select_tag|
page.replace_html select_tag, "<option value=\"\">Select</option>"+options_for_select(#products.map{|c| [c.purchase_product.name, c.purchase_product_id]})
end
Here is the view rjs : /app/view/shopping_document/_update_product_div.rjs
page.select('.helping').each do |select_tag|
page.replace_html select_tag,"<option value=\"\">Select</option>"+options_for_select(#purchases.map { |c| [c.amount, c.id] })
end
Is not updating in other lines just updating in the first line.
Please somebody can help me with this issue?
The problem is that you always send the same value to your controller action when updating products.
Look at this line from your /app/view/shopping_document/_shopping_product_fields.html.erb partial:
<%= select_tag "product",
"<option value=\"\">Select</option>"+
options_for_select(#products.map { |c| [c.amount, c.id] }),
:onchange=> remote_function(
:url= {:controller => "shopping_document", :action=>"update_product_div"},
:before => "load_close('loading_condition')",
:success => "load_off('loading_condition')",
:with => "'product=' + $('product').value"),
:class=>"product" %>
From what I remember, in prototype.js, when you use $("something"), it will find the first element in your DOM tree with id = 'someting'
The fact you're using $('product') in the with clause makes it always send the select tag value of the first row to the server, which is wrong.
You should be using $(this) instead to make sure it's the value of the current select that is being sent. So you need to replace:
:with => "'product=' + $('product').value"
With this one:
:with => "'product=' + $(this).value"
Also another problem is that when you change the product, it changes all the prices at once, and you only want that product's price to change. So, you need to be more specific about what line to change in your rjs.
The way we are going to do that is by changing the rjs you return. This rjs will now just assign a javascript variable:
page.assign "prices_for_product", "<option value=\"\">Select</option>" + options_for_select(#purchases.map { |c| [c.amount, c.id] })
And now we have to create a new javascript method that will use the values in that variable and set it to the current element. For that you need to add the following method in the javascript file where you defined load_close and load_off:
function update_prices_for_product(prices_select_tag){
prices_select_tag.innerHTML = prices_for_product;
}
But we still need to call this method when the AJAX call succeeds so we're going to add it to the :success parameter.
:success => "load_off('loading_condition'); update_prices_for_product($(this));"
There is also something that is really confusing me. You are sending the supplier_product_id that comes from your nested form to your controller, and injecting it in your query as a purchase_product_id, all that called on the SupplierProduct table. From what I can see on your data model that's not correct and I must admit I'm really confused. See this in your update_product_div action:
SupplierProduct.find(:all,:conditions=>['purchase_product_id =? ',params[:product] ],:group=>['purchase_product_id'])
What you're looking for are all the purchases done for a product. That would be something more like:
PurchaseProduct.find(:all,:conditions=>['supplier_product_id =? ', params[:product]])
You're clearly missing the supplier_product_id column on your purchase_products table.
That's as much as I can do for you, let's hope someone else jumps in...
I must say that this is really not good practice and you should use unobstrusive javascript all the time and upgrade to jquery. Moreover, use a more up-to-date version of Rails because not many people can still help you with Rails 2.3...
The only thing good with this answer is that it might make it work...
I have a controller that looks like this:
def new
#columns = Column.where(:table_id => #table.id)
#row = Row.new(id: #table.id)
end
def create
row_params.each do |row_param|
#row = Row.new(row_param)
#row.column_id = params["column_id"]
if #row.save
redirect_to collection_rows_path, notice: 'item was successfully created.'
else
render action: 'new'
end
end
end
I have a form that looks like:
<%= form_for [#table, #row] do |f| %>
<% #columns.each do |column| %>
<%= column.id %>
<%= hidden_field_tag :column_id, column.id %>
<%= f.label :data %><br>
<%= f.text_field :data %>
<% end %>
<%= f.submit %>
<% end %>
Basically, I'm trying to send multiple params and have them inserted with the column. But I keep getting this error:
undefined methodstringify_keys' for ["data", "No"]:Array` when there is two columns which means there is two text fields and I insert "Hello" in the first one, and "No" in the second.
Two things: Why is it only reading the "No" on the second one instead of both the "Hello" and "No"? And also why am I getting this error?
Thanks for all help!
Answers to your questions:
It is only reading "No" which is your input in the last "Data" text_field since the two text_fields generated in your form_for save their input value in the same params key which is params[:row][:data]. What happens then is the latest value saved to the params[:row][:data] key overrides any previous value it had.
The error undefined method stringify_keys' for ["data", "No"]:Array happens because you create 2 text_fields with the same name which is :data. When you submit the form, an Array is being submitted instead of a String that Rails expects when using text_field.
Solution to your problem:
This seems like an ideal use case for using a nested model form. Basing on your code, it looks like Row belongs_to Table. So in your Table model you'll need to add this code:
#app/models/table.rb
accepts_nested_attributes_for :row
Then add the following code in your RowsController:
#app/controllers/rows_controller.rb
def new
#columns = Column.where(:table_id => #table.id)
#columns.each do |column|
#table.rows.build(column_id: column.id)
end
end
def create
#table = Table.new(table_params)
if #table.save
redirect_to collection_rows_path, notice: 'item was successfully created.'
else
render :new
end
end
private
def table_params
params.require(:table).permit(rows_attributes: [:data, :column_id])
end
Then in your 'rows#new' view:
#app/views/rows/new.html.erb
<%= form_for #table, url: rows_path ,method: :post do |f| %>
<%= f.fields_for :rows do |r| %>
<%= r.object.column.name %>
<%= r.hidden_field :column_id, value: r.object.column_id %>
<%= r.label :data %><br>
<%= r.text_field :data %>
<% end %>
<%= f.submit %>
<% end %>
What the above code will do is allow you to create multiple rows for a column according to the number of columns the table has. This won't work though if a #table has no #columns yet. This assumes that you've created #columns for the #table already. Basing on your code though, it seems like that's already what you're doing.
you want to store 'data' as array in Row
In Rails model Row add
serialize :data, Array
in view
text_field_tag 'row[data][]'
You are getting only 'No' because form for does not know its an array so , it picks the last one And you are getting this error because rails does not know you want to store it as array , it excepts a string but got an array instead.
I am using Rails 3 and Mongoid.
I am able to store text fields and select fields but can't seen to figuer out how to store a radio option in the DB whe the form is submitted. Here is my code...
Model
class Somerandomname
include Mongoid::Document
field :name
field :option
end
Controller
def create
#somerandomname = current_user.somerandomnames.new(params[:somerandomname])
respond_to do |format|
if #somerandomname.save
format.html { redirect_to(#somerandomname, :notice => 'Somerandomname was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
View
<%= f.label :name , "Name:" %>
<%= f.text_field :name %>
<%= radio_button_tag(:option, "option1") %>
<%= label_tag(:option_option1, "option 1") %>
<br />
<%= radio_button_tag(:option, "option2") %>
<%= label_tag(:option_option2, "option 2") %>
What do I need in my model and controller file in order to capture the selection into my DB?
Try changing your view to:
<%= f.label :name , "Name:" %>
<%= f.text_field :name %>
<%= f.radio_button(:option, "option1") %>
<%= f.label(:option_option1, "option 1") %>
<br />
<%= f.radio_button(:option, "option2") %>
<%= f.label(:option_option2, "option 2") %>
That will cause the generated input fields to have the proper #name namespaced within :somerandomname.
you can have a field called option in your model. And in your controller you could do
model.option = params[:option]
model.save
depending on what radio button is selected the value of params[:option] will be set (to the value of the selected radio button).
As far as parameters go, radio buttons are just like checkboxes: they all have a parameter name and a value that they send through when checked. The only difference is that a particular group of radio buttons uncheck each other when checked, so that only one of them can be selected.
So, in your example you would have a field called 'option' in your db table, which would be set from params[:option] which would be equal to either "option1", "option2" or "option3".
I'm trying to create a scoring App. Since I'm new to StackOverflow I can't upload the screen shots but here are screen shot links
Now it looks like this.
This is what I want it to be.
Here are the models involve in the view. Just click the next picture = the last picture in the Photostream (again StackOverflow won't allow me to post more than 3 hyperlinks).
This view above is from: localhost/matchdays/4/matches/new
The relationship would look like this:
A Matchday has_many matches.
A Match has_many games (maximum of 3, but for now we'll stick with 1). We will update the score attribute.
A Game has_many pairs (maximum 2).
My question is:
How do you code in the MatchesController (when the user hit Start) to create a game with 2 pairs and each pair has its own score (which is an attribute in the Game model)?
How do you loop in the view to add another Game(Score attribute) and Pair form that belongs to the Match (in this case Match 10)? Just like in the screen shot 2 above.
Matches Controller:
def new
#matchday = Matchday.last
#match = Match.new()
#match.number = match_numbering
#pairs = Pair.all
#matchday.best_of.times { #match.games.build }
end
def create
#match = Match.new(params[:match])
#matchday = Matchday.last
#match.number = match_numbering
if #match.save
#matchday.matches << #match
flash[:success] = "Match recorded"
redirect_to matchdays_path
else
#title = "Record Match"
render 'new'
end
end
/views/matches/new:
<h1>Match <%= #match.number %> of Matchday <%= #matchday.number %> details</h1>
<%= form_for #match, :url => {:action => 'create', :id => #match.id } do |p| %>
<%= render 'shared/error_messages', :object => p.object %>
<%= render :partial => 'match_form', :locals => {:p => p} %>
<% for game in #match.games %>
<%= fields_for :games, game do |game_form| %>
<p>
Score: <%= game_form.text_field :score, :size => 2 %>
</p>
<p>
Pair: <%= select(#pairs, :pair_id,Pair.all.collect{|p| [p.name]}) %>
</p>
<% end %>
<% end %>
<div class = "action_links">
<p> <%= link_to "Cancel", matchdays_path, :class => "cancel" %> |
<%= p.submit "Start" %></p>
</div>
<% end %>
I think the looping must be put some where in the for loop, but not sure how to implement it. Plus my fields_for and select form might might not be correct... much to learn :)
All I had to do was add this line to the Match.rb
has_many :games
accepts_nested_attributes_for :games, :allow_destroy => true