Nested forms with additional data from an unrelated table - ruby-on-rails

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

Related

Assemble a complex SQL query using Activerecord::Relation and/or Arel with 'joins', 'as', and 'like'

I am building a rails 3.2 app using datatables (http://datatables.net) with client-side paging and filtering on most html tables and server-side paging and filtering on some other html tables. I want to do per-column filtering, which is super-easy for the client side tables, but I think I need to construct a sql query for the database to do per-column filtering for the server side tables. I closely followed the example from RailsCast #340 on datatables and got that working.
The challenge is doing sorting and filtering on a column that is really a foreign_key relation to another table. I don't want to sort and filter on the actual contents of the foreign_key values. I want to sort and filter on the '.to_s' values displayed for the linked objects (which is the semantics of using the client-side sort and filter feature). Here is an example:
class Address < ActiveRecord::Base
attr_accessible :city, :line1, :line2, :state, :zip
has_many :people
def to_s
[line1, line2, city, state, zip].join(' ')
end
end
class Person < ActiveRecord::Base
attr_accessible :address, :name
belongs_to :address
end
so the view displaying the people list has two columns, for name and address
<td><%= p.name %></td>
<td><%= p.address %></td>
and what appears in the index table is
John Smith | 1234 Main St Anywhere City AA 12345
so with client-side sorting and filtering I can search for 'Anywhere' in the address column and get all the rows with that term in the address field. Doing the same thing on the server-side seems much more difficult. I think I'm trying to assemble a sql query that looks something like:
select * from people
join address on people.address_id = address.id
where concat(address.line1,
address.line2,
address.city,
address.state,
address.zip) as spec_address like query_term
order by spec_address
(This is not necessarily correct SQL code.)
I've looked at both the ActiveRecord Query Rails guide and anything I could find on Arel without success.
You can do this with a scope on Address which is then merged into the Person query.
class Address < ActiveRecord::Base
scope :anywhere, lambda{|search|
attrs = [:line1, :line2, :city, :state, :zip]
where(attrs.map{|attr| "addresses.#{attr} LIKE :search"}.join(' OR '), search: "#{search}%").
order(*attrs)
}
end
Person.joins(:address).merge(Address.anywhere(query_term))

Rails 3. HABTM form select drop down menu

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.

mongoid batch update

I'd like to update a massive set of document on an hourly basis.
Here's the
fairly simple Model:
class Article
include Mongoid::Document
field :article_nr, :type => Integer
field :vendor_nr, :type => Integer
field :description, :type => String
field :ean
field :stock
field :ordered
field :eta
so every hour i get a fresh stock list, where :stock,:ordered and :eta "might" have changed
and i need to update them all.
Edit:
the stocklist contains just
:article_nr, :stock, :ordered, :eta
wich i parse to a hash
In SQL i would have taken the route to foreign keying the article_nr to a "stock" table, dropping the whole stock table, and running a "collection.insert" or something alike
But that approach seems not to work with mongoid.
Any hints? i can't get my head around collection.update
and changing the foreign key on belongs_to and has_one seems not to work
(tried it, but then Article.first.stock was nil)
But there has to be a faster way than iterating over the stocklist array of hashes and doing
something like
Article.where( :article_nr => stocklist['article_nr']).update( stock: stocklist['stock'], eta: stocklist['eta'],orderd: stocklist['ordered'])
UPDATING
You can atomically update multiple documents in the database via a criteria using Criteria#update_all. This will perform an atomic $set on all the attributes passed to the method.
# Update all people with last name Oldman with new first name.
Person.where(last_name: "Oldman").update_all(
first_name: "Pappa Gary"
)
Now I can understood a bit more. You can try to do something like that, assuming that your article nr is uniq.
class Article
include Mongoid::Document
field :article_nr
field :name
key :article_nr
has_many :stocks
end
class Stock
include Mongoid::Document
field :article_id
field :eta
field :ordered
belongs_to :article
end
Then you when you create stock:
Stock.create(:article_id => "123", :eta => "200")
Then it will automaticly get assign to article with article_nr => "123"
So you can always call last stock.
my_article.stocks.last
If you want to more precise you add field :article_nr in Stock, and then :after_save make new_stock.article_id = new_stock.article_nr
This way you don't have to do any updates, just create new stocks and they always will be put to correct Article on insert and you be able to get latest one.
If you can extract just the stock information into a separate collection (perhaps with a has_one relationship in your Article), then you can use mongoimport with the --upsertFields option, using article_nr as your upsertField. See http://www.mongodb.org/display/DOCS/Import+Export+Tools.

Build a table which has a Field of User_ids, and then querying for a particular User_Id

I need some help building a table and then getting data from that table in Rails 3.
Here's the break down:
Models - 3 models involved here they are:
Thread has many participants
Participants belong to thread
Users
Activity table:
id | thread_id | participants
Example records would look something like:
1 | 300 | 3,1,5,67,13
2 | 333 | 3,12
3 | 433 | 1,12
4 | 553 | 1,12, 67
Where participants, is a list of user_ids, if there is a better way to store the user_ids please let me know. I haven't built this yet.
After I populate the activity table. I then want to be able to query along the lines of:
Select all Activity records where the participant_id of 67 is included in the participants field.
I hope the above is clear, if not please let me know. Ideas? Thoughts? Suggestions.
Thanks
While it's tempting to store multiple values in a column, it always ends up with someone getting hurt. You're better off building a join table to relate the models.
For example you could do this:
class DiscussionThread < ActiveRecord::Base
has_many :participations
has_many :participants, :through => :participations
end
class Participation < ActiveRecord::Base
belongs_to :discussion_thread
belongs_to :participant, :class_name => "User", :foreign_key => :user_id
end
class User < ActiveRecord::Base
has_many :participations
has_many :dicussion_threads, :through => :participations
end
That gives you three tables:
table: discussion_threads
columns: id
table: participations
columns: id | discussion_thread_id | user_id
table: users
columns: id
To find the threads in which a user is participating, just do:
#user.discussion_threads
And to find the users participating in a thread:
#discussion_thread.participants
Note: Thread is a reserved word in Ruby, so I've renamed it DiscussionThread
EDIT
mind showing an example of how to serialize an array of ids and then query against them?
You awaken in the middle of the night, and under the power of a strange compulsion you go to your computer and create this migration:
rails g model Abomination horror_ids:text
and model:
class Abomination < ActiveRecord::Base
serialize :horror_ids
end
You test it to make sure it can store an array:
#abomination = Abomination.create(:horror_ids=>[2,33,42])
#abomination.horror_ids # => [2,33,42]
So what? You know that behind the scenes Rails converts it to YAML, which looks like this:
---\n
- 2\n
- 33\n
- 42\n
Again compelled by that wierd urging, you wonder "How could I search for a particular id stored in this column?". Well, it's just a string, right? You know how to find that in a text field:
cthulhu = 33
Abomination.where(["horror_ids LIKE '%- ?\n%'",cthulhu]).first
With increasing dread, you realize someone might stumble across this and think it was actually a good idea. It must be destroyed! But you are unable to type rm -rf *, instead the strange force makes you consider the quirks that a future mindless follower of Cthulhu developer might need to know, such as
#abomination = Abomination.create
#abomination.horror_ids # => nil
#abomination = Abomination.create(:horror_ids=>[])
#abomination.horror_ids # => []
#abomination = Abomination.create(:horror_ids=>"any string value can go here")
#abomination.horror_ids # => "any string value can go here"
And the fact that serialized data can get corrupted when the column size is too small to accommodate it all.
You make one last ditch effort to kick out the power cord, but it is too late, the gibbering, insane consciousness that has taken control of you posts the code on StackOverflow for the whole world to see. Finally you collapse into a troubled sleep. The next day, realizing what you've perpetrated, you give up coding forever and become an accountant.
Moral
Don't do this

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