Wishlist relationships in Rails? - ruby-on-rails-3

Im building an app where Users have some sort of wishlist
a User can have only one wishlist, and can add existing Items to that wishlist
however the Items belong to other Users on the site
I need to be able to access the wishlist items through current_user.wishlist.items (im using Devise so current_user is available)
i though of adding a wishlist_id column to the items table, but that wouldnt work since items can belong to multiple wishlists.
this seems simple but im having a hard time visualizing the relationship or the migration im supposed to generate

class User < ActiveRecord::Base
has_one :wishlist # or belongs_to :wishlist, it depends which you prefer
end
class Wishlist < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :items
end
And of course:
./script/rails generate migration create_item_wishlists wishlist_id:integer item_id:integer
to create join table between items and wishlists.
UPDATE: To answer "frank blizzard" question in comment:
Let's say you have the same structure as in my answer (just change Item to Product or other model name), with HABTM relationship you just need to add new "item" to collection of "items", and then save wishlist:
#user.wishlist.items << item
#user.wishlist.save
You can make it method in user:
class User
def add_to_wishlist(item)
wishlist.items << item
end
end
If you want to remove or modify collection of "items", just use any Ruby method from Array and then save wishlist, which will check differences for you and save only changes.

Related

Can a one to many relationship in rails create a parent object?

If I have an category class that has many products and each product would only have one category so that my models looked like this:
class Product < ActiveRecord::Base
belongs_to :category
end
and this:
class Category < ActiveRecord::Base
has_many :products
end
Then, from the belongs_to side of products, can I create a category_name in my Product Model using: create_category? How can I tell what auto-generated methods are available to me on the product side of things?
How can I tell what auto-generated methods are available to me on the product side of things?
By reading the corresponding documentation on api.rubyonrails.org (i.e. has_many and belongs_to). It tells you what methods are added.
In your case, you get my_product.create_category and my_category.products.create along many other methods.
you can used nested forms to create a category when a category doesn't exist or choose one if it already does exist within the new product create form.
This video from railscast should help
http://railscasts.com/episodes/196-nested-model-form-part-1

Rails: How do I transactionally add a has_many association to an existing model?

Let's imagine I run an imaginary art store with a couple models (and by models I'm referring to the Rails term not the arts term as in nude models) that looks something like this:
class Artwork < ActiveRecord::Base
belongs_to :purchase
belongs_to :artist
end
class Purchase < ActiveRecord::Base
has_many :artworks
belongs_to :customer
end
The Artwork is created and sometime later it is included in a Purchase. In my create or update controller method for Purchase I would like to associate the new Purchase with the existing Artwork.
If the Artwork did not exist I could do #purchase.artworks.build or #purchase.artworks.create, but these both assume that I'm creating a new Artwork which I am not. I could add the existing artwork with something like this:
params[:artwork_ids].each do |artwork|
#purchase.artworks << Artwork.find(artwork)
end
However, this isn't transactional. The database is updated immediately. (Unless of course I'm in the create controller in which case I think it may be done "transactionally" since the #purchase doesn't exist until I call save, but that doesn't help me for update.) There is also the #purchase.artwork_ids= method, but that is immediate as well.
I think something like this will work for the update action, but it is very inelegant.
#purchase = Purchase.find(params[:id])
result = #purchase.transaction do
#purchase.update_attributes(params[:purchase])
params[:artwork_ids].each do |artwork|
artwork.purchase = #purchase
artwork.save!
end
end
This would be followed by the conventional:
if result
redirect_to purchase_url(#purchase), notice: 'Purchase was successfully updated.' }
else
render action: "edit"
end
What I'm looking for is something like the way it would work from the other direction where I could just put accepts_nested_attributes_for in my model and then call result = #artwork.save and everything works like magic.
I have figured out a way to do what I want which fairly elegant. I needed to make updates to each part of my Product MVC.
Model:
attr_accessible: artwork_ids
I had to add artwork_ids to attr_accessible since it wasn't included before.
View:
= check_box_tag "purchase[artwork_ids][]", artwork.id, artwork.purchase == #purchase
In my view I have an array for each artwork with a check_box_tag. I couldn't use check_box because of the gotcha where not checking the box would cause a hidden value of "true" to be sent instead of an artwork id. However, this leaves me with the problem of deleting all the artwork from a purchase. When doing update, if I uncheck each check box, then the params[:purchase] hash won't have an :artwork_ids entry.
Controller:
params[:purchase][:artwork_ids] ||= []
Adding this guarantees that the value is set, and will have the desired effect of removing all existing associations. However, this causes a pesky rspec failure
Purchase.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) fails because :update_attributes actually received {"these"=>"params", "artwork_ids"=>[]}). I tried setting a hidden_value_tag in the view instead, but couldn't get it to work. I think this nit is worthy of a new question.
It is probably best to use make the purchase model a join table and have many to many associations.
Here is an example for your use case.
Customer model
has_many :purchases
has_many :artwork, :through => :purchase
Artwork model
has_many :purchases
has_many :customers, :through => :purchase
Purchase model
belongs_to :customer
belongs_to :artwork
The purchase model should contain customer_id and artwork_id.
you would also need to create a purchase controller that allows you create a new purchase object.
When a customer presses the purchase button it would create a new purchase object which includes the customer_id and the artwork_id. This allows you to create an association between the customer and the artwork they purchase. You can also have a price_paid column to save the price the customer paid at the time of purchase.
if you need more help you can research join many to many associations using :through.
hope it helps

Has_many :through association

I made a relationship with the three models using has_many :through:
class Curriculum class < ActiveRecord::Base
has_many :interests
has_many :vacancies,: through => :interests
end
class Vacancy class < ActiveRecord::Base
has_many :interests
has_many :resumes,: through => :interests
end
class Interest < ActiveRecord:: Base
belongs_to :vacancy
belongs_to :curriculum
end
And to create curriculum and vacancy, I create them by administrative, i need to know how can i create the interest to the id of the vacancy, and how it will be logged on the system I have to get the id of it and make the relationship in creating a new bank interest. I wonder how I can program it to do so, and I wonder how the controller will get the create action, and what better way to do this.
First, try to read the whole "Guide to Rails on Associations", especially the part about has_many :through. Then check your schema if your db is migrated and contains for the table interests the necessary foreign keys to curriculums and vacancies called curriculum_id and vacancy_id.
If that is all in place, the following code will create the relationship between two objects:
#curr = Curriculum.find(1)
#vac = Vacancy.find(1)
#curr.interests << #vac
#curr.save
The last two lines creates an interest between #curr and #vac and store that on the database. So you should not use IDs and handle them directly, but work with objects instead.
The second part now is to provide a UI to allow the definition (and removal) of interests between curricula and vacancies. The base flow here is:
You have one curriculum in focus.
You have a link to add / remove curricula.
The view that opens shows a list of possible vacancies, where every vacancy has a checkbox.
By selecting (or deselecting) the check boxes, the IDs of the vacancies will be held in the params of the request sent to the controller.
See the (older) podcast Railscast #52 how to do that in a similar context. Or see the example for has_many :through with checkboxes.
An alternative way would be to use JQuery autocomplete, and add so interests one-by-one. See the nice podcast Railscast #258 which uses JQuery Tokeninput for that.
I think this is what your looking for:
HABTM Checkboxes
That's the best way to use an Has and Belongs to many association.

rails and namespaced models issue

Using rails 3/3.1 I want to store invoices with their items (and later more associations like payments, etc…).
So in a first approach I set up the models like this:
class Invoice < ActiveRecord::Base
has_many :invoice_items
end
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
end
And the routes likes this:
resources :invoices do
resources :invoice_items
end
I chose InvoiceItem instead of Item because I already have a model named Item and I somehow want to namespace the model to invoices. But this name has the huge disadvantage that one has to use invoice.invoice_items instead of a intuitive invoice.items. Also the generated url helpers look real ugly, for example "new_invoice_invoice_item_path(invoice)" (notice the double invoice_invoice).
So I changed to namespaced models like this:
class Invoice < ActiveRecord::Base
has_many :items, :class_name => "Invoice::Item"
end
class Invoice::Item < ActiveRecord::Base
belongs_to :invoice
end
And the routes likes this:
resources :invoices do
resources :items, :module => "invoice"
end
Now the assocation is named nicely and also the url helpers look pretty. But I can't use dynamic urls (ex. [:new, invoice, :item]) anymore, because the controller is set to "invoice_item" instead of "invoice/item".
I wonder how other people solve this problem and what I'm doing wrong. Or is this simply a bug in rails 3.0.7/ 3.1.rc?
EDIT:
Sorry, I seems I didn't correctly express my concern. My model Item is not related to Invoice::Item. Order::Item is also not related to Item nor Invoice::Item. An Invoice::Item can only belong to one invoice. An Order::Item can only belong to an Order. I need to namespace - but why doesn't rails properly support namespacing out of the box? Or what am I doing wrong with namespacing?
Corin
If an order item and an invoice item are not the same object in the real world then I would name them differently rather than trying to namespace, for example OrderItem and InvoiceItem - this will keep things clearer as your codebase grows and avoid the need to make sure you use the right namespace everywhere you reference an Item.

Rails: Make different references to a DB row refer to the same Ruby object

Suppose I have the following model relationship:
class Player < ActiveRecord::Base
has_many :cards
end
class Card < ActiveRecord::Base
belongs_to :player
end
I know from this question that Rails will return me a copy of the object representing a database row, meaning that:
p = Player.find(:first)
c = p.cards[0]
c.player.object_id == p.object_id # => false
...and therefore if the Player model modifies self, and the Card model modifies self.player in the same request, then the modifications won't take any notice of each other and the last-saved one will overwrite the others.
I'd like to work around this (presumably with some form of caching), so that all requests for a Player with a given id would return the same object (identical object_ids), thereby allowing both models to edit the same object without having to perform a database save-and-reload. I have three questions:
Is there already a plugin or gem to do this?
Are there good reasons why I shouldn't do this?
Can anyone give me some pointers on how to go about doing this?
This is supported in Rails 3.x. You can use the :inverse_of option for the has_many association for example. Documentation here (search for :inverse_of and Bi-directional associations).