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...
Related
I'm still learning Rails and in my project app admin should be able to filter all holiday leave requests by status (boolean field in db) using dropdown list (approved for true, pending for false). I think I have declared everything right in my controller and model however I have no idea how to implement this to the view. I was trying to follow up Austin's story blog and Using select_tag multiple => true to get a combined scope topic but still I don't figured how to do it.
leaves controller:
def index
#leave = Leave.new
#leaves = Leave.all.order(created_at: :desc).includes(:user)
#leaves = Leave.find_by('select = ?', params[:status])
end
model leave.rb
scope :approved, -> { where(status: true) }
scope :pending, -> { where(status: false) }
belongs_to :user, optional: true
view index.html.erb
<%= form_tag(action: :index) do %>
<div class="input-group">
<%= select_tag :status, options_for_select([['Approved', Leave.approved], ['Pending', Leave.pending]]) %>
<%= submit_tag 'Filter' %>
</div>
<% end %>
Change your code to this
def index
#leave = Leave.new # as you have added it, won't affect what we want to achieve.
#if params have status then only you've to show
#the selected ones otherwise you'll be showing all
#of them. right?
if params[:status].present?
#leaves = Leave.where(status: params[:status])
#dont use find_by it will return only one record instead of array.
else
#leaves = Leave.includes(:user).order(created_at: :desc)
end
end
<%= form_tag(url: admin_leaves_path, method: :get) do %>
<div class="input-group">
#Leave.approved will hit database from view. which is not recommended
#instead of that try using values which will be accessed in controller
#via params and then you can perform actions according to it.
<%= select_tag :status, options_for_select([['Approved', true], ['Pending', false]]) %>
<%= submit_tag 'Filter' %>
</div>
<% end %>
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 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.
I am just trying to make my form display a blank field for each item that has not yet been created. I've done this successfully using the build method for more simple forms but I'm not sure how to do it for this case. Is the strategy I'm using to do this wrong or am I making a simple syntax mistake?
Here's the basic model setup:
A Comp has many Rounds and many Teams. A Round has many Items. Each Team has only 1 Item per Round
So when the form is loaded, if a team has not already created an item, I want there to be a blank item created for that team so that it shows up in the form and can be edited for that team.
I tried 2 different methods in my controller and neither has worked:
Method 1:
def edit_admin
#comp = Comp.find(params[:comp_id])
#round = #comp.rounds.find(params[:round_id])
team_ids = #round.items.all(:select => :team_id).collect(&:team_id)
#comp.teams.each do |team|
if team_ids.include? team.id == false
new_item = #round.items.new(:team_id => team.id, :round_id => #round.id)
new_item.save
end
end
end
def update_admin
#comp = Comp.find(params[:comp_id])
#round = #comp.rounds.find(params[:round_id])
if #round.update_attributes(params[:round])
redirect_to(edit_comp_path(#comp))
else
render 'edit_admin'
end
end
Method 2:
Essentially the same thing but I defined a method to run before the page loads:
before_filter :build_new_items, :only => :edit_admin
private
def build_new_items
#comp = Comp.find(params[:comp_id])
#round = #comp.rounds.find(params[:round_id])
team_ids = #round.items.all(:select => :team_id).collect(&:team_id)
#comp.teams.each do |team|
if team_ids.include? team.id == false
new_item = #round.items.new(:team_id => team.id, :round_id => #round.id)
new_item.save
end
end
end
The form looks like this (the view is called edit_admin.html.erb):
<%= form_tag update_admin_comp_round_items_path(#comp,#round), :method => :put do %>
<% for item in #round.items.all %>
<%= "Team: " + #comp.teams.find(item.team_id).team_name %> <br />
<%= fields_for 'round[items_attributes][]', item do |f| %>
<%= f.label :item_name %>
<%= f.text_field :item_name %> <br />
<%= f.hidden_field :id, :value => item.id %> <br />
<% end %>
<% end %>
<p><%= submit_tag "Submit" %></p>
<% end %>
Thanks.
Answering my own question since no one else did: I got method B to work with a very minor tweak. All it needed was parentheses around the value after "include?", like so:
if team_ids.include?(team.id) == false.
I figured this out by playing with each line of code in the rails console.
Method A may work as well but since I got method B to work, I haven't yet tried it.
UPDATE: method A also works
Background: Users and communities share a
has_many :through
relationship. Each community has a "community_type" string that identifies it (ie "Gender", "City", etc.).
Objective: In my edit form, I'd like to allow the user to edit his :community_ids based on community type. Something like:
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_ids, Community.filtered_by("Gender"), :id, :name) %>
<%= f.collection_select(:community_ids, Community.filtered_by("City"), :id, :name) %>
<% end %>
The issue is that the form only accepts the last form field value for :community_ids - in this case being the "City" - rather than merging all of them as one big :community_ids array.
Solution:
For those interested, I ended up refactoring my model code from the answers below to this:
%W[ community1 community2 community3 community4 ].each do |name|
define_method "#{name}" do
self.communities.filtered_by("#{name}").map(&:name)
end
define_method "#{name}_ids" do
self.communities.filtered_by("#{name}").map(&:id)
end
define_method "#{name}_ids=" do |val|
self.community_ids += val
end
end
Is there a reason you're using select boxes for a has_many relationship? It seems checkboxes would be more appropriate. If you want to go with select boxes, I don't think you can use FormHelper#select, because as far as I know, it's expecting a single value, and your community_ids is an array. This is why it's only picking one of the values you give it.
For a select box (or any field), you can combine the values across fields by adding [] to the parameter name which tells Rails that the parameter is an array of values. You can do this by using regular select_tag to create the fields, and setting the parameter name as follows:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
You could also go with Ryan's approach of sending separate parameters, though one downside is your User model will have to be very aware of the types of communities that exist, and you'll have to write separate logic in the User model for each type of community. This will make your resources less modular. But if you do go that way, I'd suggest using pseudo-attributes instead of a before_save so that your community_ids get updated automatically from the params:
class User < ActiveRecord::Base
...
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
...
end
And then your select_tag calls would look something like this:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_gender_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_city_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
Updated to complete tsherif's (better than my original) answer.
view.rb
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_gender_ids, Community.filtered_by("Gender"), :id, :name, {}, id: 'community-gender-options') %>
<%= f.collection_select(:community_city_ids, Community.filtered_by("City"), :id, :name, {}, id: 'community-city-options') %>
<% end %>
model.rb
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
def community_gender_ids
self.communities.select(:id).where(:community_type => 'gender').map(&:id)
end
def community_city_ids
self.communities.select(:id).where(:community_type => 'city').map(&:id)
end
Alternatively, you could write some CoffeeScript/Javascript to bind to the select tags and add the IDs to a hidden value which is then submitted to the server with the form.