nested has_many :through in rails 3 - ruby-on-rails

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.

Related

Which assosciation to use in Rails?

I have a user and project model created in Rails. I have to perform an association that will create an relationship which is described below:
1 User has many Projects
1 project has many Users
How can I go about creating an association for the same in Rails? I need some help on which type of association in rails will help me to achieve this.
You are describing a many-to-many relationship type. Rails allows you to create this relationship using has_many :through and has_and_belongs_to_many directives. You can learn the difference here.
Shortly, has_many :through allows you to add additional columns into the intermediate table, has_and_belongs_to_many doesn't. If you don't need to have additional attributes in the intermediate table than use has_and_belongs_to_many syntax. You can always change to has_many :through later.
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :projects
end
You are basically trying to have a many-to-many relationship.
In Rails you can do this based on two association concept:
has_and_belongs_to_many (HABTM)
has_many :through
Note:
You should have HABTM if you just do not care about the way these two tables are joined (relationship model) and you do not want to have any logic/validation for your join data. It will just keep your foreign keys in a table and based on that data will be fetched.
You need has_many :through if you want to have an intermediate model in between Project and User which can be called as UserProject model. This way your association could look like as follows:
User Model:
has_many :user_projects
has_many :projects, through: :user_projects
Project Model:
has_many :user_projects
has_many :users, through: :user_projects
UserProject Model:
belongs_to :user
belongs_to :project
You can use has_and_belongs_to_many or has_many through.Here is the link I am providing which will help you to sort out difference between them and which one will be good for you.Here is the video tutorial for you association.ALso there is a good link link.In your case you need has and belongs to many
The best thing to do in this situation is ,
In your user.rb model file:
has_and_belongs_to_many :projects
and In your project.rb model file:
has_and_belongs_to_many :users
You may want too use many to many relationship between project and user. on top of that you may want to visit rails official guide which describes all of these relations in great detail.
http://guides.rubyonrails.org/association_basics.html

Ruby on Rails ActiveRecord task belongs to company & has_many users

I'm new to Ruby on Rails and was wonder if this is a good setup, or if there is a better configuration.
Background:
The system will be used to assign tasks to users, track the assignor, and allow multiple people to be assigned the task.
Create a company model, user model, task model, and a user_tasks model.
Company Class
has_many :users
has_many :tasks
User Class
belongs_to :company
has_many :user_tasks
has_many :tasks, through: :user_tasks
Task Class
belongs_to :company
has_many :user_tasks
has_many :users, through: :user_tasks
UserTasks Class
belongs_to :user
belongs_to :task
*Tracks assignor with boolean
I think this is perfect. There is one big advantage of having a has_many (model1), through: (model2) association when compared to has_and_belongs_to_many association in that you can access the join model (UserTasks your case) through the ActiveRecord query interface (UserTask.where() or UserTask.find_by(user_id: 1) and so forth). Querying the join table directly can sometimes shorten your queries. If you use the has_and_belongs_to_many association you will have a database table that you cannot directly access in Rails without resorting to SQL.
You probably don't need the UserTasks class if it is just a habtm table. So just create a migration for that table, but skip adding the model. Then in User, do has_and_belongs_to_many :tasks and in Task do has_and_belongs_to_many :users
The other thing that I see is that company is set for both tasks and users. You might have business rules to why this has to be, but if not, you might just be able to say Company -> has_many :tasks, through: :users
I favor :has_many, :through over has_many_and_belongs_to_many or HABTM. For example, I can be assigned to a Task twice, one as a programmer and another time as a designer. So I need some kind of "JobDescription" column on the UserTask column, and I'd need two rows of the UserTask table to represent me being a programmer on a task, and another to represent me being a designer on a task.
You want to write software that is easier to change, even though HABTM is shorter, you might change the software in the future, like in the case of a person doing two subtasks in the same task. Remember that a row in a database is a piece of data, or "a thing", if that makes sense. At the very least, you get timestamps.

Advice on RoR database schema and associations

New to RoR, but am having a stab at building an app - and I'm looking for some advice on my db structure.
I've got 4 models/tables: Users > Clients > Jobs > Tasks
The app will work as follows:
Users will login
They can add Clients
They can add Jobs to Clients
They can add Tasks to Jobs
So, Tasks belong to Jobs, Jobs belong to Clients, and Clients belong to Users.
I want to query the DB for any one of a Client, a Job, or a Task and also make sure that it belongs to the currently logged-in User. I'm struggling to write a 'railsy' join query and design my associations so I can do this.
I know this would be super easy if I had a user_id field in every table, but that doesn't seem like the right way to do it.
I've read the guide at http://guides.rubyonrails.org/association_basics.html, but am still in the dark a little. Can anyone shed some light on how I might structure my DB - and more importantly my associations?
Thx.
It seems you have your associations set up right from one side, all you have to do is add the other end of the associations using has_many:
class Task < ActiveRecord::Base
belongs_to :job
end
class Job < ActiveRecord::Base
belongs_to :client
has_many :tasks
end
class Client < ActiveRecord::Base
belongs_to :user
has_many :jobs
has_many :tasks, :through => :jobs
end
class User < ActiveRecord::Base
has_many :clients
has_many :jobs, :through => :clients
has_many :tasks, :through => :jobs
end
Now ActiveRecord will take care of the joins for you. It's true that in a pure db schema, you should not have the user_id in more than one place(here that would the clients table). However sometimes it would be added also to the tasks and jobs table for a performance boost, because then the db queries would not be so big. Nevertheless you have to put more effort into making your data consistent - you have to ensure that a job has the same user_id as its client for example.
Then you would be able to define shortcut associations like:
class Task < ActiveRecord::Base
belongs_to :user
end
But in this case I would not do it unless you notice the queries are too slow for your needs. Premature optimization is evil :-)
I'm not sure, if I understood your question correctly, but I believe this can be a solution:
If you write your associations like this, ActiveRecord will create automatically the joins if you ask for user.jobs or user.tasks.
class User < ActiveRecord::Base
has_many :clients
has_many :jobs, :through => :clients
has_many :tasks, :through => :jobs
end
For more Information see the Rails-Api-Documentation
If you want to get everything for a user in one request. You can do this:
user.clients.joins(:jobs => :tasks)

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.

HABTM and accepts_nested_attributes_for

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.

Resources