Rails 3. HABTM form select drop down menu - ruby-on-rails

I have an invoice form. This is a simplified version: so it has line items where you select a drop down menu of product names.
This is working well: So the invoice-line_item relations is this: invoice has_many line_items and line_item belongs to invoice. line_item belongs to item and item has_many line_items. I have the items, line_items and invoice setup correctly.
But now I want to add taxes to the line items.
So I created a line_items_taxes table to create a HABTM relationship between line_items and taxes. But I can't set it up correctly in the form. My form looks like this...
|name|price|tax|
| v| | v|
| v| | v|
| v| | v|
[save invoice]
So I need a TAXES drop down select menu, and when the invoice is saved, it saves the tax for each line item.
I have tried the solutions offered at http://snippets.dzone.com/posts/show/4369 and Rails HABTM Question but I get errors.
undefined method merge for :name:Symbol
<%= f.collection_select "line_item", "tax_ids", #taxes, :id, :name, {:name => 'line_item[tax_ids][]'} %>

Your call to collection_select contains an extra parameter that is throwing things off. (Since I assume you are using *form_for*, the 'line_item' argument is automatically included, and yours is redundant.)
It should instead look something like this:
f.collection_select 'tax_ids', #taxes, :id, :name, {:name => 'line_item[tax_ids][]'}
That's a start in the right direction, anyway.

Related

Rails: Possible to send a disabled option as a valid params value?

I have a User, and a Country models, and in User model, it has has_many :countries relationship. The country has 3 attributes -> :id, :name, :is_main
Let's say the countries table is currently populated as follow:
id name is_main
----------------------------------
1 USA true
2 Germany nil
3 France nil
4 England nil
Let's say a user is created that has countries USA, and Germany. So in this case the user.countries.pluck(:id) would return [1,2].
What I would like to achieve is that when editing the user's countries, a dropdown will appear and I am only allowed to add (or remove) countries where 'is_main' attribute is nil. In other words, in the dropdown, the USA country should either be disabled or be hidden completely to be selected. At the same time, the USA should remain in the user.countries after form submission plus any new countries that have been added from update action.
In short:
Original => user.countries.pluck(:id) => [1,2]
In edit form, if I add France to the user, the end result should be user.countries.pluck(:id) => [1,2,3]
I have tried the following:
f.collection_select(:countries, Country.where(is_main: nil), :id, :name, {}, {:multiple => true})
In doing this, the dropdown will display all the countries for me to add except for USA, which is what I desired. But the problem is when I click submit, the params[:country_ids] will be [2, 3]. As a result, after update action, the user.countries.pluck(:id) would become [2,3] instead of desired [1,2,3], effectively removing USA's id.
Is there a way to work around this? I have tried also adding :disabled option in collection_select that disables USA option, but the params[:country_ids] would still be [nil, 2, 3]. Appreciate if anyone could advise me on this.
I've come across this issue on my application. In rails 4+, set include_hidden attribute to false in multi-select selet_tags. Here's how i did it.
= f.input :countries, as: :select, collection: Country.where(is_main: nil).map{|country| [country.id, country.name]}, input_html: { multiple: true, data: { placeholder: "Countries"} }, include_hidden: false
Hope this helps you solve your issue
Actually using your example for multiple selection, your params after submit should have params[:country_ids] = [2,3]. If you are not giving the option to select USA, then it just won't be included.
You can disable options doing this:
// Your select id should be 'user_country_ids', if not change it
// This assumes that your first item will always be USA
$('#user_country_ids option:eq(0)').prop('disabled', true);
Anyway, this JS can be bypassed, so you will need to handle this on your backend too adding a validation for this in your User model:
class User
has_many :countries
validate :main_countries_always_associateds
private
def main_countries_always_associateds
errors.add(:countries, "must include main ones") if Country.where(is_main: true).any?{ |c| !self.countries.include? c }
end
end
EDIT:
If you want to always have main countries inside each user countries, then you can use before_validation callback to override this selection. I recommend to also include the disabled options on the multiple select, so the user is aware of this, the validation may become not necessary.
class User
has_many :countries
before_validation :associate_main_countries
# You should use something like this if you want your dropdown to always show marked main countries
def self.new_custom_user
User.new(countries: Country.where(is_main: true))
end
private
def associate_main_countries
Country.where(is_main: true).each do |c|
self.countries << c if !self.countries.include?(c)
end
end
end

Custom ordering for query results from ActiveRecord association

I recently switched my project to postgres and it is messing up my select forms in small but annoying manner.
I have 2 select fields,
<%= f.collection_select :state, State.all, :value, :name, {include_blank: '- Select State -'} %>
<%= f.grouped_collection_select :area, State.all, :areas, :name, :value, :name, {include_blank: '- Select Area -'} %>
The first select field contains a bunch of States. The second select field contains a bunch of Areas/Districts grouped(optgroup) by States.
Using "seeds.rb", I inserted all the relevant data. Everything was inserted in alphabetical order.
In SQLite3, the select results are all presented alphabetically.
However, when i swapped over to Postgrsql, my :area select is now presenting its options in reverse alphabetical order which is terrible for user experience.
I have also tested the following scenario with the following query in the rails console:
State.find("Texas").areas
SQLite3 results:
[Area A, Area B, Area C]
Postgres results:
[Area C, Area B, Area A]
Is there any way i can get the same results as SQLite3 with Postgres while still using it with my "f.group_collection_select"?
You can specify the default order in the has_many relationship.
class State < ActiveRecord::Base
has_many :areas, -> { order("areas.name ASC") }
end
use order clause on name or id whichever appropriate(I'd prefer name) with default scope for Area.
default_scope { order(:name :asc) }
You can do this by defining a scope in your Area model
See : Grouped Collection Select Alphabetical Order Rails

Nested forms with additional data from an unrelated table

Perhaps I'm overthinking this, but I want to be able to use a nested form that depends on the data that's coming from another table. Let me elaborate.
I have 4 tables with something like the following pseudo-code:
Product
has_many: providers
Provider
belongs_to: product
Sale
has_many: products
has_many: orders
accepts_nested_attributes_for :orders
Order
belongs_to: sale
So far, so good.
My problem begins when I'm trying to make a Sale. Each Product will have an X amount of Providers (usually 4), and each Sale will always have an Order for each of those Providers (even if it's 0), so I need the data of each Provider so I can specify how much I'm ordering from each.
I have something like this:
=form_for #sale do |s|
=s.label :date, 'Date'
=s.text_field :date
=s.label :other, 'Other Information'
=s.text_area :other
=s.object.product.providers.each do |p|
=s.fields_for :orders do |o|
=p.name
=o.label :amount, 'Amount'
=o.text_field :amount
This doesn't work. What this is achieving is obtaining the amount of Providers of each Product, and listing the name of each Provider - so far, so good - but I need to specify the amount of that each one is going to receive in the Order. If there's data, the text_field will get populated with the very first record that gets matched in Orders, but since I'm looping through it, the same data is also populated for the remaining 3.
I know my logic is flawed (I've been battling through it for the last 4 hours). I think it is a matter of going back to the drawing board (which I'm doing right now), but I wanted to see if anybody could maybe see the obvious.
What I'd like to see is something like this:
|--Provider Name--|---Amount----|
|-----------------|-------------|
| Provider One | 10 |
| Provider Two | 2 |
| Provider Three | 0 |
| Provider Four | 4 |
|-----------------|-------------|
Where the "amount" on each line is a text_field for the Amount field of the form. Initially, the text_fields should come out empty, so I can fill them in with data. Once they have data, they should pop back with the previous data used. Pretty standard.
I thought about creating those x amount of records on the Orders table as soon as I visited the new Sale page, but that'd make me fill the Order table with lots of unused data. That approach would also hinder it later, if for some reason a new Provider is added.
I don't know how to proceed, any pointers would be greatly appreciated.
Try this code for list of text_fields for orders:
=form_for #sale do |s|
=s.label :date, 'Date'
=s.text_field :date
=s.label :other, 'Other Information'
=s.text_area :other
=s.products.each do |product|
=product.providers.each do |provide|
=p.name
=s.fields_for :orders do |o|
=o.label :amount, 'Amount'
=o.text_field :amount

Rails and Postgres: Getting the latest entry for each customer

I have a model (which has been simplified here) that looks like this:
class Customer < ActiveRecord::Base
attr_accessor :name
has_many :orders
end
class Order < ActiveRecord::Base
attr_accessor :description, :cost, :date
end
I'm using Postgres – what's the best way to run a query that will return me a single result that contains the latest order for each customer?
If I do something like this:
Order.order('date DESC').group('customer')
Then I get a Postgres error:
PGError: ERROR: column must appear in the GROUP BY clause or be used in an aggregate function
What's the cleanest way to do this? I'm a Rails newbie, so let me know if I've left out any vital information here.
as question says Getting the latest entry for each customer
Customer.all.map{|C| [c.name, c.orders.last]}
will return an array with customer name and latest order. In view it will look like this:
<% #customers.each do |c| %>
<%= c.name %> - <%= c.orders.last.cost %>
<% end %>
result:
John Travolta - $5454
Johnny Depp - $6849
Order.order('date DESC').group('customer_id')
I guess your Order model does not have customer field by convention it should be customer_id
For a more SQL oriented method, which ought to be better performing:
Order.where("id in (select max(id) from orders group by customer_id)")
It relies on the most recent order having the highest id.

How to create links between two tables

Ok so I'm starting on normalising my database. Currently I have one model "Products" which is populated with about 60,000 products via a data feed (XML), which contains a product with a category name and a merchant name. I want to split these into 3 models; products, categories and merchants.
Each product has one category and one merchant so the natural idea is to create these models:
category_id | category_name
merchant_id | merchant_name
I can work out the code to associate between the models i.e. has_one, belongs_to etc but I'm struggling to work out to automatically associate a new Product with a category and a merchant programatically.
I've seen examples in books where your start with an empty database and that seems pretty straightforward. However, I'm starting off with a full database and a list of Category names.
Here is my product creation statement which is working great:
Product.create(:name => node.xpath("./text/name/text()").inner_text.downcase,
:description => node.xpath("./text/desc/text()").inner_text,
:brand => node.xpath("./brand/text()").inner_text,
:merchant => node.xpath("../#name").inner_text,
:category => node.xpath("./cat/text()").inner_text.downcase,
:price => "£" + node.xpath("./price/btext()").inner_text)
Would I need to do something like this, see the :category line, (i know the following is wrong btw!)...
Product.create(:name => node.xpath("./text/name/text()").inner_text.downcase,
:description => node.xpath("./text/desc/text()").inner_text,
:brand => node.xpath("./brand/text()").inner_text,
:merchant => node.xpath("../#name").inner_text,
:category => << Category.find_by_name(node.xpath("./cat/text()").inner_text.downcase),
:price => "£" + node.xpath("./price/btext()").inner_text)
Any ideas? Does this even make sense!?
Assuming the columns are called category_name and merchant_name, and you've set up the associations on Category and Merchant, you could do something like this:
Product.all do |product|
product.category = Category.find_or_create_by_category_name(product.category_name)
product.merchant = Merchant.find_or_create_by_merchant_name(product.merchant_name)
product.save!
end
It will take a while, so for large datasets you might need a better solution.
So would this actually set the :category value in the products table to a category_id or set the value to the category_name?
.find_or_create_by does a find on the attribute and returns the matching row, or creates one if it does not exist. When creating the association via `.category=, Rails will set the foreign key to match the id of the row in the categories table.
So to answer your question more directly:
Product.create(:category=>Category.find_or_create_by_name("Magic Beans"))
is like doing this:
category = Category.find_by_name("Magic Beans")
if category.nil?
category = Category.create(:name=>"Magic Beans")
end
product = Product.new
product.category = category
product.save
where the penultimate step sets the foreign key category_id to the value category.id. By convention associations are set up such that the foreign key is the model name suffixed with _id, so your products table should have both category_id and merchant_id.

Resources