HABTM and accepts_nested_attributes_for - ruby-on-rails

Say I have two models, Book and Author with a has_and_belongs_to_many relationship between them.
What I want to do is to be able to add author names in the book form, and on submit to either link the authors with the book if they already exist, or create them if they don't.
I also want to do the same with the author form: add book names and on submit either link them if they exist, or create them if they don't.
On edit, however, I want to neither be able to edit nor delete the nested objects, only remove the associations.
Is accepts_nested_attributes_for suitable for this, or is there another way?
I managed to accomplish this by following the Complex Forms railscasts on Rails 2, but I'm looking for a more elegant solution for Rails 3.

I'm not sure why so many people use has_and_belongs_to_many, which is a relic from Rails 1, instead of using has_many ..., :through except that it's probably in a lot of old reference books and tutorials. The big difference between the two approaches is the first uses a compound key to identify them, the second a first-class model.
If you redefine your relationship, you can manage on the intermediate model level. For instance, you can add and remove BookAuthor records instead of has_and_belongs_to_many links which are notoriously difficult to tweak on an individual basis.
You can create a simple model:
class BookAuthor < ActiveRecord::Base
belongs_to :book
belongs_to :author
end
Each of your other models is now more easily linked:
class Book < ActiveRecord::Base
has_many :book_authors
has_many :authors, :through => :book_authors
end
class Author < ActiveRecord::Base
has_many :book_authors
has_many :books, :through => :book_authors
end
On your nested form, manage the book_authors relationship directly, adding and removing those as required.

Related

Rails one-way 'has_many' association

I am trying to make Rails-based application (and I'm just learning RoR as I go) and I stumbled upon this problem.
There are two models: Recipe and Item (food items). Recipe can have zero (we can create recipe before adding items) or many items. But a specific food item should not be bound to any recipe. That is why 'has_many' and 'belongs_to' won't work for me, as the latter does not fill this requirement.
If I was to do this without any framework, I would probably put an 'items' column in Recipe table, which would contain a list of item indices. But I have a hunch that this is not an appropriate way to do this in RoR since there are model associations in Rails.
Please, could someone give me an idea how to go about this?
I normally don't use has_and_belongs_to_many, but in your case it seems it might fit. You can use it like this:
class Recipe
has_and_belongs_to_many :items
end
class Item
has_and_belongs_to_many :recipes
end
You can also use has_many :through, but you would have to create a third table to join the Recipe and Item tables together.
class Recipe
has_many :item_recipes
has_many :items, through: :item_recipes
end
class ItemRecipes
belongs_to :recipe
belongs_to :item
end
class Item
has_many :item_recipes
has_many :recipes, through: :item_recipes
end
You can find more information here: Rails Associations

Rails STI design issue

I am working a side project where a user can have multiple clients. Those client can be of type Person or Business.
I was leaning toward the idea to using STI but I am not sure whether this is the right way to go since my models will not share the same attributes.
For instance a Business has a legal_form where a Person might have a marital_status.
Is it ok to use STI in this particular case or (2nd question) is there any way to allow rails to use separate tables for each types.
STI is like inheritance in ruby. You can use it if you have parent and children and they share a lot of attributes and data. If Person and Business share a lot you can use it. Otherwise I'd recommend you use Polymorphic Associations
A slightly more advanced twist on associations is the polymorphic
association. With polymorphic associations, a model can belong to more
than one other model, on a single association. For example, you might
have a picture model that belongs to either an employee model or a
product model. Here's how this could be declared:
class Picture < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
class Employee < ActiveRecord::Base
has_many :pictures, as: :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, as: :imageable
end
I don't really like STI and I'd recommend you try to use Polymorphic Associations.
A common problem with STI
A common problem with STI is that, over time, more types get added to
that table, and it grows more and more columns, and the records in the
table have less and less in common with one another. Each type of
record uses some subset of the table’s columns, and none uses all of
them, so you end up with a very sparsely populated table. Those types
create costs for one another: when you query for articles, you must
remember to filter out all the other types of values and to only
select columns relevant to articles, or else pay a huge performance
cost. You reproduce a lot of work the database would do for you, if
you only had each data type in its own table.
Assuming I would go for the polymorphic association would this be correct
class Client < ActiveRecord::Base
belongs_to :cliental, polymorphic: true
end
class Business < ActionRecord::Base
# Here I am using has_one because I do not want to have duplicates
has_one :client, as: :cliental
end
class Person < ActionRecord::Base
# Here I am using has_one because I do not want to have duplicates
has_one :client, as: :cliental
end
And later I would like to do the following
class User < ActiveRecord::Base
has_many clients
has_many businesses, through: :client
has_many people, through: :client
end

Rails Active Record multiple associations

I have a Rails app using Postgres and Active Record, and can't fathom the most efficient Associations between my models.
I have a Model called Article. Article needs to have a Format and 2/3 Genres.
I need articles to be able to be listed by format i.e. http://myapp.com/format/format-id/article-id
I also need articles to be listed by genre, so: myapp.com/genre/genre-name/article-id
I was thinking the Genres and Formats would be models themselves with has_many_and_belongs_to associations to Articles. Each article has multiple Genres, and 1 format, but each Genre has multiple articles.
I know this should be simple, but I can't find the most efficient route of making this happen.
There's an old Railscasts episode that goes through the two ways to do many-to-many associations with Rails - you should watch it: http://railscasts.com/episodes/47-two-many-to-many. It's old but mostly still relevant.
So, you've got Articles, Formats, and Genres. Since each Article will have only one Format, you don't need a has_and_belongs_to_many relationship there. Article belongs_to Format, and Format has_many articles.
Each row in the articles table will have a format_id field to indicate which Format it belongs to.
The relationship between Genres and Articles is a many-to-many, which is a bit trickier. At the database level, this requires a 'join table'. Your join table would be called something like articles_genres, and each row represents one genre that one particular article has. So, it'd have a genre_id column, and an article_id column.
In terms of rails, there are two ways to do this:
Give the articles_genres table it's own model. In this case you might want to also give the table a different name, to indicate it's a Model in it's own right, not just a join table. You could call it something like genreizations
or
Don't give articles_genres table it's own model, and let rails handle it all.
I generally prefer the first way - I feel more in control, and it leaves things more flexible for future. But, either will work. The railscasts episode I linked to describes both ways.
If you go the first way, this is what models you'll have:
class Article < ActiveRecord::Base
has_many :genreizations
has_many :genres, through: :genreizations
belongs_to :format
end
class Format < ActiveRecord::Base
has_many :articles
end
class Genreization < ActiveRecord::Base
belongs_to :article
belongs_to :genre
end
class Genre < ActiveRecord::Base
has_many :genreizations
has_many :articles, through: :genreizations
end
And in the second way, this is what models you'll have:
class Article < ActiveRecord::Base
has_and_belongs_to_many :genres
belongs_to :format
end
class Format < ActiveRecord::Base
has_many :articles
end
class Genre < ActiveRecord::Base
has_and_belongs_to_many :articles
end

Adding another attribute to a many-to-many table in rails 3

I am currently using has_and_belongs_to_many to implement a many-to-many relationship. I would however want to put in a attribute in the many_to_many table.
Basically I am creating a email system. I have users and conversations. A user can have many conversations and a conversations can also have many users. However, I am trying to make it so that I can have a read/unread attribute to show which messages are read. Since conversations can have many users, it is not practicable to put the attribute in the conversations table as then it would mean that the conversation is read by all. So I think it would work best in the middle table. I am wondering though how I can access that attribute in the middle table. If the attribute is read. What code do I put in to access that and how do I update the attribute. As mention above I am using has_and_belongs_to_many
If you want to have additional attributes to your has-and-belongs-to-many association, you have to build a model class for that relation. See the detailed description in the Rails Guides about it.
After having read it for myself, this is now deprecated with the current version of Rails, so you really should switch to has_many :through. Your models could be (copied and changed from the Rails Guides, I don't know if connection is a good name for the m2n relation):
class User < ActiveRecord::Base
has_many :connections
has_many :conversations, :through => :connections
end
class Connection < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
end
class Conversation < ActiveRecord::Base
has_many :connections
has_many :users, :through => :connections
end
There you are able to add additional attributes to your connections table, and refer in the code to them.

nested has_many :through in rails 3

I know Rails doesn't support nested has_many :through relationships, though there's been talk and an open ticket about a patch since as early as Rails 2.
I did come across a plugin that's pretty slick, but the master branches don't work with Rails 3 and I'm hesitant to use it for mission critical tasks in the app hence the lack of active recent development. So -- what's the best way to deal with these relations.
class Author < ActiveRecord::Base
has_many :contracts
has_many :products, :through => :contracts
class Product < ActiveRecord::Base
has_many :contracts
has_many :orders
has_many :authors, :through => :contracts
class Contracts < ActiveRecord::Base
belongs_to :author
belongs_to :product
So, all the being what it is it would be great to be able to get at orders this by adding this to the Author model:
has_many :orders, :through => :products
But alas, you cannot -- at least without the plugin. So, my question is what's the best approach to accessing all of an author's orders when the only association is between the join model, Contracts?
If you're not trying to create objects through the nested association, and you want to use it for lookup only, then scopes in Rails 3 are a great way to do this. Alternatively you could implement a class method.
I've had this kind of thing as an example in a class I taught recently, the Rails 3 version of the code is here:
https://github.com/wolframarnold/Efficient-TDD-Rails3/blob/master/app/models/user.rb
See the definition of the items method. The specs are here:
https://github.com/wolframarnold/Efficient-TDD-Rails3/blob/master/spec/models/user_orders_spec.rb
Rails 3.1 update: As one commenter already noted, Rails 3.1 does support has_many :through associations more than one level deep.
As I see it you have 2 options:
You may need to re-consider your modelling decisions. For example, establish a many-to-many association between a Customer and a Product through an Order. And then incubate the Contract.
Use a named scope (or scope in Rails 3) to fetch the author's orders.
If pressed, I would go with option 1.
The ticket doesn't seem to be active any more for including the patch in Rails core. I'd submit it ... seems like it should be something that should just work in Rails.

Resources