Rails: How do self-referential has_many models work? - ruby-on-rails

So, I'm working on an app where I want to users to be able to group objects in "folders". Basically:
User has_many :foos
Foos don't have to be in a folder, but they can be. In that case:
Folder has_many :foos and Foo belongs_to :folder
Now, I'd like to be able to set up folders so they can be nested. I think this is something like...
Folder has_many :folders
I have heard that this kind of self-referential relationship is no big deal, but I don't really get how it works. I haven't been able to figure out how this is supposed to be declared in the model and what columns I need to provide in the database.
Could anyone offer an example? I'd also value any suggestions/heads-up/warnings/lessons learned that you might be able to offer about setting up this kind of relationship in an app.
Thanks!

Checkout coreyward's answer to the question here: Creating a model that has a tree structure
Basically you want to add a "parent_id" field to your folders table and then set up a relationship in your Folder model like this:
belongs_to :parent, :class_name => "Folder"
has_many :folders, :foreign_key => "parent_id"

Related

Relationship advice/association logic in rails

I'm designing an application to create goals for a classroom - at the moment, the data relationships are modeled like this
I knew that out of inexperience I might run into problems and now I'm beginning to - specifically in my routes file - but also just for generally organizing the site and the data relationships.
When a user signs up, they add a student_group (or class), populate it with students and then add subjects. Later they add goals for each subject - although there should also be goals for a student_group, a student, or even the user. I was thinking of something like this - but would it be better as a has_many, through relationship?
Right now, I've only really done work on the User, Student_group, and Student models and these are fairly straight-forward. A user has many student_groups, and a student_group has many students. I'd like a second opinion before I proceed however, so that I don't have to end up going back and doing things over. Thanks!
I think you might be thinking too far ahead. Once you have your app built around your current data model, you'll know better whether you even want to expand it to include the concept of a goal that isn't part of a student's subject. If you decide that it is, then making goals belong_to a subject, student, or user will be pretty simple. At that point, you could also do something like
Class Student
has_many :personal_goals, class_name: "Goal"
has_many :goals, through: :subjects
def all_goals
goals + personal_goals
end
There's probably a more elegant way to model that last relationship. Would you need to go beyond that? Does it make sense to talk about a student group having a goal of its own? I don't know.
As I gone through your database design, I have found that you should have to use different type of relationships, that rails has provided us. I tried my best to design your schema as per my knowledge. you should define relationship in your model as I suggested below. Any good modification are highly appreciated.
User
has_many :student_groups
has_many :students, through: :student_groups
has_many :goals, as: :goalable
StudentGroup
belongs_to :user
has_many :students
has_many :goals, as: :goalable
Student
belongs_to :student_group
has_many :subjects
has_many :characteristics
has_many :goals, as: :goalable
Characteristic
belongs_to :student
Goal
belongs_to :goalable, polymorphic => true
I have defined some polymorphic associations in your schema. If you need any reference related to these association. visit http://guides.rubyonrails.org/association_basics.html
Hope it will help you. Thanks.

Rails naming conventions when having two relations to the same model

My question is more related to naming conventions than programmation I guess.
Let's assume an application where users can create new articles (so they are the owner of these articles) and where you can add article "editors", who can only update the article content.
class User
include Mongoid::Document
has_many :articles # as owner of the articles
has_and_belongs_to_many :articles # as editor of the articles
end
class Article
include Mongoid::Document
belongs_to :user
has_and_belongs_to_many :editors, :class_name => 'User'
end
What I want to know is how I should call the articles association in my User model. I mean, an article has an author and editors, which seems strong naming conventions to me, but a user has articles he created and articles he is the editor. How would you call/name/declare the last 2 associations?
I would call them as :edited_articles, and :authored_articles or :owned_articles, or something similarly straightforward names. Just dont forget to add the :class_name and :foreign_key or :through qualifiers to them.
Update:
For has_and_belongs_to_many relation you need a connection table, which is by default, is named of the two joined table. E.g. articles_users in your case. In this table you will propably have two ids, user_id and article_id. This way rails connects your models automatically.
has_and_belongs_to_many :editors, :class_name => 'User', :foreign_id => 'user_id'
Of course if you call it editor_id in the join table then use that. And the opposite, on the user side should work too.

Category child of another category in rails

How to implement in rails a category that can be child of another category (self-referential has_one) ?
Thanks
First, I believe you want a has_many relationship, not a has_one. I can't imagine a situation where you would want categories to have at most one child category. The previous answer also only gets you one direction - categories know about their parents, but not about their children.
The full solution is simple enough. The categories table should have a category_id column, and the model should look like this:
class Category < ActiveRecord::Base
belongs_to :category
has_many :categories
end
If you want to go a step further and call them parents and children, you can:
class Category < ActiveRecord::Base
belongs_to :parent, :class_name => 'Category', :foreign_key => 'category_id'
has_many :children, :class_name => 'Category', :foreign_key => 'category_id'
end
Good luck with your app!
The simple approach is to use a foreign key of category_id as others have already pointed out. However, if you're talking about nesting lots of categories this can be pretty inefficient. (Note: Posting more of your requirements would be helpful.)
I really, really like the ancestry gem. Here is a relevant snip from the docs (emphasis mine).
As can be seen in the previous
section, Ancestry stores a path from
the root to the parent for every node.
This is a variation on the
materialised path database pattern. It
allows Ancestry to fetch any relation
(siblings, descendants, etc.) in a
single sql query without the
complicated algorithms and
incomprehensibility associated with
left and right values. Additionally,
any inserts, deletes and updates only
affect nodes within the affected
node’s own subtree.
And, here's a bonus freebie, just because it's not immediately obvious. If you need to treat siblings as a list with positions, you can scope them as follows.
acts_as_list :scope => 'ancestry #{(ancestry.blank? ? "IS NULL" : "=\'" + ancestry + "\'")}'

Intermediate model to join two seperate models in Ruby on Rails

I am trying to create an intermediate model between two models that I'd like to have a many to many relationship. I am creating an atypical book checkout project and have two models setup Book and Person. I'd like to setup an intermediate model BookCheckOut to track OutDate and ReturnDate.
Dan Singerman provided what looks like the answer I am looking for to the question Ruby On Rails Relationships - One to Many. My inexperience with out model generation and my reliance upon scaffolding are probably causing my problem. I am trying to determine how to not only generate the model but a working database migration that would accompany it.
I am not a rails maestro, but there are two ways to do it that I know of: has_many :through and has_and_belongs_to_many. This article has a decent overview of how. I suspect you would want to use has_many :through so you can access the data in the join table cleanly.
To generate the intermediate model you would do something like:
script/generate model checkouts person_id:int, book_id:int, checked_out:date, returned:date
In your Book model you would add (does Rails know Person --> "People"? I'm guessing yes):
has_many :people, :through => :checkouts
has_many :checkouts, :dependent => true
In your Person model you would add:
has_many :books, :through => :checkouts
has_many :checkouts, :dependent => true
In your Checkout (sorry, I renamed it from your example) model, you would add:
belongs_to :person
belongs_to :book
Use caution with my examples - I am going from memory.

Rails: Many to many polymorphic relationships

See comments for updates.
I've been struggling to get a clear and straight-forward answer on this one, I'm hoping this time I'll get it! :D
I definitely have a lot to learn still with Rails, however I do understand the problem I'm facing and would really appreciate additional help.
I have a model called "Task".
I have an abstract model called "Target".
I would like to relate multiple instances of subclasses of Target to Task.
I am not using single table inheritance.
I would like to query the polymorphic relationship to return a mixed result set of subclasses of Target.
I would like to query individual instances of subclasses of Target to obtain tasks that they are in a relationship with.
So, I figure a polymorphic many to many relationship between Tasks and subclasses of Targets is in order.
In more detail, I will be able to do things like this in the console (and of course elsewhere):
task = Task.find(1)
task.targets
[...array of all the subclasses of Target here...]
But! Assuming models "Store", "Software", "Office", "Vehicle", which are all subclasses of "Target" exist, it would be nice to also traverse the relationship in the other direction:
store = Store.find(1)
store.tasks
[...array of all the Tasks this Store is related to...]
software = Software.find(18)
software.tasks
[...array of all the Tasks this Software is related to...]
The database tables implied by polymorphic relationships appears to be capable of doing this traversal, but I see some recurring themes in trying to find an answer which to me defeat the spirit of polymorphic relationships:
Using my example still, people appear to want to define Store, Software, Office, Vehicle in Task, which we can tell right away isn't a polymorphic relationship as it only returns one type of model.
Similar to the last point, people still want to define Store, Software, Office and Vehicle in Task in one way shape or form. The important bit here is that the relationship is blind to the subclassing. My polymorphs will initially only be interacted with as Targets, not as their individual subclass types. Defining each subclass in Task again starts to eat away at the purpose of the polymorphic relationship.
I see that a model for the join table might be in order, that seems somewhat correct to me except that it adds some complexity I assumed Rails would be willing to do away with. I plea inexperience on this one.
It seems to be a small hole in either rails functionality or the collective community knowledge. So hopefully stackoverflow can chronicle my search for the answer!
Thanks to everyone who help!
You can combine polymorphism and has_many :through to get a flexible mapping:
class Assignment < ActiveRecord::Base
belongs_to :task
belongs_to :target, :polymorphic => true
end
class Task < ActiveRecord::Base
has_many :targets, :through => :assignment
end
class Store < ActiveRecord::Base
has_many :tasks, :through => :assignment, :as => :target
end
class Vehicle < ActiveRecord::Base
has_many :tasks, :through => :assignment, :as => :target
end
...And so forth.
Although the answer proposed by by SFEley is great, there a some flaws:
The retrieval of tasks from target (Store/Vehicle) works, but the backwards wont. That is basically because you can't traverse a :through association to a polymorphic data type because the SQL can't tell what table it's in.
Every model with a :through association need a direct association with the intermediate table
The :through Assignment association should be in plural
The :as statement wont work together with :through, you need to specify it first with the direct association needed with the intermediate table
With that in mind, my simplest solution would be:
class Assignment < ActiveRecord::Base
belongs_to :task
belongs_to :target, :polymorphic => true
end
class Task < ActiveRecord::Base
has_many :assignments
# acts as the the 'has_many targets' needed
def targets
assignments.map {|x| x.target}
end
end
class Store < ActiveRecord::Base
has_many :assignments, as: :target
has_many :tasks, :through => :assignment
end
class Vehicle < ActiveRecord::Base
has_many :assignments, as: :target
has_many :tasks, :through => :assignment, :as => :target
end
References:
http://blog.hasmanythrough.com/2006/4/3/polymorphic-through
The has_many_polymorphs solution you mention isn't that bad.
class Task < ActiveRecord::Base
has_many_polymorphs :targets, :from => [:store, :software, :office, :vehicle]
end
Seems to do everything you want.
It provides the following methods:
to Task:
t = Task.first
t.targets # Mixed collection of all targets associated with task t
t.stores # Collection of stores associated with task t
t.softwares # same but for software
t.offices # same but for office
t.vehicles # same but for vehicles
to Software, Store, Office, Vehicle:
s = Software.first # works for any of the subtargets.
s.tasks # lists tasks associated with s
If I'm following the comments correctly, the only remaining problem is that you don't want to have to modify app/models/task.rb every time you create a new type of Subtarget. The Rails way seems to require you to modify two files to create a bidirectional association. has_many_polymorphs only requires you to change the Tasks file. Seems like a win to me. Or at least it would if you didn't have to edit the new Model file anyway.
There are a few ways around this, but they seem like way too much work to avoid changing one file every once in a while. But if you're that dead set against modifying Task yourself to add to the polymorphic relationship, here's my suggestion:
Keep a list of subtargets, I'm going to suggest in lib/subtargets formatted one entry per line that is essentially the table_name.underscore. (Capital letters have an underscore prefixed and then everything is made lowercase)
store
software
office
vehicle
Create config/initializers/subtargets.rb and fill it with this:
SubtargetList = File.open("#{RAILS_ROOT}/lib/subtargets").read.split.reject(&:match(/#/)).map(&:to_sym)
Next you're going to want to either create a custom generator or a new rake task. To generate your new subtarget and add the model name to the subtarget list file, defined above. You'll probably end up doing something bare bones that makes the change and passes the arguments to the standard generator.
Sorry, I don't really feel like walking you through that right now, but here are some resources
Finally replace the list in the has_many_polymorphs declaration with SubtargetList
class Task < ActiveRecord::Base
has_many_polymorphs :targets, :from => SubtargetList
end
From this point on you could add a new subtarget with
$ script/generate subtarget_model home
And this will automatically update your polymorphic list once you reload your console or restart the production server.
As I said it's a lot of work to automatically update the subtargets list. However, if you do go this route you can tweak the custom generator ensure all the required parts of the subtarget model are there when you generate it.
Using STI:
class Task < ActiveRecord::Base
end
class StoreTask < Task
belongs_to :store, :foreign_key => "target_id"
end
class VehicleTask < Task
belongs_to :vehicle, :foreign_key => "target_id"
end
class Store < ActiveRecord::Base
has_many :tasks, :class_name => "StoreTask", :foreign_key => "target_id"
end
class Vehicle < ActiveRecord::Base
has_many :tasks, :class_name => "VehicleTask", :foreign_key => "target_id"
end
In your databse you'll need:
Task type:string and Task target_id:integer
The advantage is that now you have a through model for each task type which can be specific.
See also STI and polymorphic model together
Cheers!
This may not be an especially helpful answer, but stated simply, I don't think there is an easy or automagic way to do this. At least, not as easy as with simpler to-one or to-many associations.
I think that creating an ActiveRecord model for the join table is the right way to approach the problem. A normal has_and_belongs_to_many relationship assumes a join between two specified tables, whereas in your case it sounds like you want to join between tasks and any one of stores, softwares, offices, or vehicles (by the way, is there a reason not to use STI here? It seems like it would help reduce complexity by limiting the number of tables you have). So in your case, the join table would also need to know the name of the Target subclass involved. Something like
create_table :targets_tasks do |t|
t.integer :target_id
t.string :target_type
t.integer :task_id
end
Then, in your Task class, your Target subclasses, and the TargetsTask class, you could set up has_many associations using the :through keyword as documented on the ActiveRecord::Associations::ClassMethods rdoc pages.
But still, that only gets you part of the way, because :through won't know to use the target_type field as the Target subclass name. For that, you might be able to write some custom select/finder SQL fragments, also documented in ActiveRecord::Associations::ClassMethods.
Hopefully this gets you moving in the right direction. If you find a complete solution, I'd love to see it!
I agree with the others I would go for a solution that uses a mixture of STI and delegation would be much easier to implement.
At the heart of your problem is where to store a record of all the subclasses of Target. ActiveRecord chooses the database via the STI model.
You could store them in a class variable in the Target and use the inherited callback to add new ones to it. Then you can dynamically generate the code you'll need from the contents of that array and leverage method_missing.
Have you pursued that brute force approach:
class Task
has_many :stores
has_many :softwares
has_many :offices
has_many :vehicles
def targets
stores + softwares + offices + vehicles
end
...
It may not be that elegant, but to be honest it's not that verbose, and there is nothing inherently inefficient about the code.

Resources