Rails: Many-to-many relationships with 3 Models - ruby-on-rails

I have a situation where I have Products, Suppliers, ShoppingLists and Valuations.
A shopping_list consist of many valuations each with a product, an specific supplier and a price.
My models are as follows:
class Product < ActiveRecord::Base
has_many :valuations
has_many :shopping_lists, :through => :valuations
end
class Supplier < ActiveRecord::Base
has_many :valuations
has_many :shopping_lists, :through => :valuations
end
class ShoppingList < ActiveRecord::Base
has_many :valuations
has_many :products, :through => :valuations
has_many :suppliers, :through => :valuations
end
class Valuation < ActiveRecord::Base
belongs_to :product
belongs_to :supplier
belongs_to :shopping_list
end
My routes.rb is:
map.resources :shopping_lists do |shopping_list|
shopping_list.resources :valuations
end
map.resources :product
map.resources :supplier
I wonder if this could be the best solution, anyway what I want is that the user can create as many lists as he wish, each with several valuations.
The first time a shopping list is created its also filled with one valuation at least. Then, the user can add/remove valuations to/from the shopping_list.
I would like a simple and elegant solution, without Ajax callbacks.
What is the best way to do this, from the controllers/views/routes perspectives?
Or should I completley change my schema ?
Thanks!

Just found two excelent resources from Ryan Bates:
http://asciicasts.com/episodes/196-nested-model-form-part-1
http://asciicasts.com/episodes/197-nested-model-form-part-2
Let's see if that do the job!
// UPDATE: Worked great!

Related

Rails habtm joins

I have this relationship between categories, products & brands:
class Brand < ActiveRecord::Base
has_many :products
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
belongs_to :brand
end
How can I select all categories by specified brand with this relations?
I try this but get an error
b = Brand.find(1)
Category.joins(:products).where(:products => b.products)
You did the right thing with the join, just add a more complex where definition:
Category.joins(:products).where(:products => {:brand_id => 1})
Controversially HABTM's are rarely, if ever, a good design and IMO just about the only thing Rails got wrong.
Introduce an xref table to join products and categories and use has_many :through on both sides of the relationship so you end up with
class Brand < ActiveRecord::Base
has_many :products
has_many categories :through => products # This is now allowed in Rails 3.x and above
end
class Category < ActiveRecord::Base
belongs_to :product_category
has_many :products :through => product_category
end
class Product < ActiveRecord::Base
belongs_to :brand
belongs_to :product_category
has_many :categories :through => product_category
end
class ProductCategory < ActiveRecord::Base
has_many :products
has_many :categories
end
This gives you the best flexibility with the least amount of code re-factoring for you plus a much more intuitive path to get whatever data you need on either side of the relationship and will enable you to achieve the following
b = Brand.find(1)
b.categories.all
Update
The above is totally untested code and I have just corrected a glaringly stupid mistake I made. If you have any issues implementing this then come back

has_many :through relationships explained

I'm new to Rails and have some doubts about the kind of relationship do I need to use. Here is the case.
I have two models Offer and User, a user could belong to to many offers and offers can have many user. Also the users create the offers.
I think I have to use a has_many :through ralationship. For example I've created another model "Applicant". Applicant belongs_to user and belongs_to offer. But how is the relationship from the user and offer model? For example:
User Model
has_many :offer, :through => :applicant
Offer Model
has_many :user, :through => :applicant
My doubt is because I already have this two relationship
User Model
has_many :offers, :dependent => :destroy
Offer Model
belongs_to :user
After solve this, I guest I have to save the record in the applicant model from the applicanst_controller, right?
Thanks in advance
What you have described is a many-to-many relationship using a join table. You're actually pretty close but you just need to remove the has_many :offers, :dependent => :destroy from your user model and the blongs_to :user in your offer model. It should look something like this:
class User < ActiveRecord::Base
has_many :offers, :through => :applicants
end
class Applicant < ActiveRecord::Base
belongs_to :users
belongs_to :offers
end
class Offer < ActiveRecord::Base
has_many :users, :through => :applicants
end
You don't have to worry about the dependent destroy part as associations are automatically removed as the corresponding objects are removed. With a many to many association it doesn't really matter how you go about building the relationship. Either of the following will work:
#user.offers << #offer
#offers.users << #user
If you don't need to store any information specific to your applicant join table (e.g., time stamps, descriptions) you might instead want to look at a has_and_belongs_to_many relationship. Check out choosing between has_many_through and has_and_belongs_to_many for reference.
Edit
Heres the code for a HABTM relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :offers
end
class Offer < ActiveRecord::Base
has_and_belongs_to_many :users
end

Fetching records through multiple tables

The image shows part of my data model. I would like to fetch all items that are associated with a user (through organizations and items_group). How should I change the models and write this query in the controller? Using :through => organizations I can get all items_groups but I don't how to include one more relation to query related items.
class User < ActiveRecord::Base
has_and_belongs_to_many :organizations
has_many :items_groups, :through => :organizations
end
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :items_groups
has_many :items, :through => :items_groups
end
class ItemsGroup < ActiveRecord::Base
has_many :items, :inverse_of => :items_group
has_and_belongs_to_many :organizations
has_many :users, :through => :organizations
end
I think you might have to do it back-to-front and find items joined back to your user.
You could define method like this in your User class:
def items
Item.joins(:items_group => {:organizations => :users}).where("users.id" => self.id).select("distinct items.*")
end
The items it returns will be read-only because of the explicit select but I think you'll want that to avoid returning individual items more than once.
If you set in your models the relationships this should work:
users.organizations.item_groups.items
Though for it to work your models should contain this:
class User < ActiveRecord::Base
has_many :organizations, :through => :organization_users
end
class Organization < ActiveRecord::Base
has_many :item_groups, :through => :items_groups_organizations
end
class ItemsGroup < ActiveRecord::Base
belongs_to :item
end
Hope it works for you!

Rails/ActiveRecord Sub collection

I have three models: Store, Author, Books
Store has many Authors which has many Books.
What is the cleanest way to get a collection of all the books at the store?
This works:
#store.authors.collect{|a| a.books}.flatten
Is there something in Active Record that I'm missing that makes this cleaner?
Jake
This may work...
class Store < ActiveRecord::Base
has_many :authors
# I used :uniq because a book can have more than one author, and without
# the :uniq you'd have duplicated books when using #store.books
has_many :books, :through => :authors, :uniq => true
end
class Author < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :author
end
With this code you can use #store.books...
What you want is has_many through. It works like this:
# in store.rb
has_many :authors
has_many :books, :through => :authors
# in author.rb
belongs_to :store
has_many :books
# in book.rb
belongs_to :author
Now you can say #store.books and it should just work.

how to specify multiple relationships between models in rails using ActiveRecord associations

Say there're two models : User and Post, i want to keep track of who has read which post, and who writes which post, which user favorites which posts etc. Then i come up with the following solution:
class User < ActiveRecord::Base
has_many :writings
has_many :posts, :through => :writings
has_many :readings
has_many :posts, :through => :readings
#..
end
class Post < ActiveRecord::Base
has_many :writings
has_many :posts, :through => :writings
#...
end
and setting up the intermediate models - Writing, Reading. this works, but finally i find out that when i writing this
#user.posts #...
the returned array contains housekeeping information both for writings and readings. How can i solve this problem.
You want something like this:
class User < ActiveRecord::Base
has_many :writings
has_many :posts, :through => :writings
has_many :readings
has_many :read_posts, :through => :readings, :class_name => "Post"
#..
end
By giving the association name something other than just :posts you can refer to each one individually. Now...
#user.posts # => posts that a user has written via writings
#user.read_posts # => posts that a user has read via readings

Resources