Whats a best practice for implementing a hierarchical relation in Rails? - ruby-on-rails

I'd like to model users that belong to groups, and groups that belong to groups, so I'm thinking along the lines of (forgive the newb syntax):
class Group < ActiveRecord::Base
attr_accessible :description, :group_id, :name
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
attr_accessible :email, :name
has_and_belongs_to_many :groups
end
Would the above be a preferred way to implement this? Is there a simple way of deleting a 'parent' group and have it delete it's children?
At the time of the writing, I'm learning with rails 3.2.x ...

I hate to be a gem-pusher, but I recently started using ancestry, and it works very well. It has a unique way of indexing ancestors and descendants for great performance.
There's also a Railscast that covers it.
Ancestry is a gem/plugin that allows the records of a Ruby on Rails
ActiveRecord model to be organised as a tree structure (or hierarchy).
It uses a single, intuitively formatted database column, using a
variation on the materialised path pattern. It exposes all the
standard tree structure relations (ancestors, parent, root, children,
siblings, descendants) and all of them can be fetched in a single SQL
query. Additional features are STI support, scopes, depth caching,
depth constraints, easy migration from older plugins/gems, integrity
checking, integrity restoration, arrangement of (sub)tree into hashes
and different strategies for dealing with orphaned records.
Source: https://github.com/stefankroes/ancestry#readme

I'm guessing here - but would users be able to be in multiple groups and groups would only be in one (a parent group)?
class Group < ActiveRecord::Base
attr_accessible :description, :name
has_many :groups, :dependent => :destroy
has_many :group_users, :dependent => :destroy
has_many :users, :through => :group_users
belongs_to :parent_group, :class_name => :group
end
class GroupUsers < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
class User < ActiveRecord::Base
attr_accessible :email, :name
has_many :group_users, :dependent => :destroy
has_many :groups, :through => :group_users
end

Related

Getting ActiveRecord associations belongs_to through

I have models associated with has_many through. I am able to get a relationship in one direction but not the other (there is no belongs_to through setting)
Projects have_many datasets through taskflows. Taskflows are associated to datasets in a join table.
I am able to do Project.datasets simply by using the has_many through setting. I'd also like to call dataset.project to get the associated project of a dataset (through taskflow).
Is this possible? Many thanks for any help.
I have four models (I've tried the delegate setting but it doesn't seem to work):
class Project < ActiveRecord::Base
validates :title, presence: true, length: {minimum: 3}
has_many :taskflows
has_many :datasets, :through => :taskflows
end
class Taskflow < ActiveRecord::Base
belongs_to :project
has_many :dataset_assignments
has_many :datasets, :through => :dataset_assignments
end
class Dataset < ActiveRecord::Base
has_many :dataset_assignments
has_many :taskflows, :through => :dataset_assignments
delegate :project, :to => :taskflows, :has_nil =>true
end
class DatasetAssignment < ActiveRecord::Base
belongs_to :dataset
belongs_to :taskflow
end
has_many_through works both ways.
If you put the has_many_through in reverse in your Dataset Model it should work as you wish.
A good example I use - and I learned on
Recipe Model
Ingredient Model
Component Model
A Recipe has many ingredients through components
An Ingredient has many recipes through components.
In my limited experience with it, your logic makes sense, the has_many through usually flows 'one way' in terms of creation, but in terms of rails it's the same relation type - an ingredient 'has_many' recipes through the components

Modeling Relationships with Four or More Models in Rails

I have these models in my Rails app:
group.rb
has_many :group_members
has_many :group_foo_types
group_member.rb
# fields
:group_id, :user_id
belongs_to:group
has_many :group_foo_types, :through=>:groups
has_one :user
has_many :foo, :through=>:user
has_many :bar, :through=>:user
group_foo_type.rb
# fields
:group_id, :foo_type_id
belongs_to :group
belongs_to :foo_type
user.rb
has_many :foo
has_many :bar
foo.rb
# fields
:user_id, :foo_type_id
belongs_to :user
belongs_to :foo_type
bar.rb
# fields
:user_id, :foo_type_id
belongs_to :user
belongs_to :foo_type
What I'm trying to do is, for a group, find the foos and bars per user taking into account the foo_type_id for the group (from group_foo_type).
However, I'm not sure if I have this modeled correctly.
From this, it seems like I can do group.group_members.user to get the users and then user.foos' and 'user.bars to get a user's foos and bars. This doesn't take into account the foo_type_id for the group though (from group_foo_type).
Can someone please help recommend an approach to accomplish what I want to do? Thank you!
As mentioned in this answer, since Rails 3.1 you can nest has_many :through associations. So to have easy access to all of a group's foos and bars, you could add the following associations:
# group.rb
has_many :users, through: :group_members
has_many :foos, through: :users
has_many :bars, through: :users
With this, group.foos would give you all of the group's foos (via group_member and user).
The association methods in rails can be used with the various finder methods, so to limit the foos by group_foo_type:
group_foos = group.foos.where(foo_type_id: group.group_foo_type_ids)
(group_foo_type_ids being another useful association method, check the Association Basics guide for more info.)
A caution: there are a lot of steps that have to go on in the background to achieve any of this stuff, even if it does get relatively easy to actually code. You may want to keep an eye on the queries that get generated (either through the console or logs) and how they perform, as they're likely to get fairly involved. It looks like you have a fairly complex set of associations there, so it may be worth looking at whether you can simplify anything there. Just something to keep in mind!

Using Rails, not sure if I should use belongs_to or not

Very new to Rails... I'm building out functionality that lets people compare photos, and I can't decide exactly how I should structure it. Ideally what I'd like is to have a "comparisons" table which keeps a record of the IDs of the photos compared as well as the user that compared them, but I'm not quite sure whether this warrants use of the "belongs_to" function or not. If so, how do I specify that each comparison belongs to TWO separate photos?
The following has_many, :through => Model structure will let you have additonal properties on the join table, e.g. 'comparing_user_id'.
class Photo < ActiveRecord::Base
has_many :appearances
has_many :users, :through => :appearances
end
class Appearance < ActiveRecord::Base
belongs_to :photo
belongs_to :user
end
class User < ActiveRecord::Base
has_many :appearances
has_many :photos, :through => :appearances
end

How to establish associations for a model with two belongs_to relationships?

I am building an application with the following model functions
Groups have many Users
Groups have many Expenses (each expense has a :name, :total, :added_by_user_id fields)
Expenses have many owings (1 for each user in the group)
Owings have an :amount and a :user_id, to reference which user the owing is referring
So far, I have set up the models as followings:
# user.rb
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password
has_many :memberships, :foreign_key => "member_id", :dependent => :destroy
has_many :groups, :through => :memberships
has_many :owings
end
# group.rb
class Group < ActiveRecord::Base
attr_accessible :name
has_many :memberships, :dependent => :destroy
has_many :members, :through => :memberships
has_many :expenses
end
# expense.rb
class Expense < ActiveRecord::Base
attr_accessible :total_dollars, :name, :owings_attributes, :added_by_user_id
belongs_to :group, :inverse_of => :expense
has_many :owings, :dependent => :destroy
end
# owing.rb
class Owing < ActiveRecord::Base
attr_accessible :amount_dollars, :user_id
belongs_to :expense, :inverse_of => :owings
belongs_to :user, :inverse_of => :owings
end
# NB - have left off memberships class (and some attributes) for simplicity
To create an expense, I'm using #group.expenses.build(params[:expenses]), where params come from a nested model form that includes attributes for the owings that need to be created. The params include the 'user_id' for each of the 'owing' instances for that expense.
I have two concerns:
Firstly - I've made 'user_id' accessible in the owings model, meaning that a malicious user can change who owes what in an expense (I think?). I don't know how to get around this, though, because the user needs to see the names of all the other members of the group when they fill out the expense/owings form.
Secondly - I've also made 'added_by_user_id' accessible in the expense model - I also wouldn't want malicious users to be able to change this, since this user_id has special edit/delete priveleges for the expense. Is there some clever way to make an expense 'belong_to' a User AND a group, and set both of these associations when creating WITHOUT having to make either an accessible attribute? If it helps, the 'added_by_user_id' can always be set to the current_user.
Any ideas? Very possible I'm missing something fairly fundamental here.
Thanks in advance!
PS. Long time listener, first time caller. Thanks to all of you for teaching me ruby on rails to date; this website is an incredible resource!
Have you thought about setting them dynamically?
dynamic attr-accessible railscast

Rails modeling for a user

When building a rails app that allows a User to login and create data, is it best to setup a belongs_to :user association on every single model? For example, let's say a user can create Favorites, Colors and Tags.
And let's say Favorites has_many :tags and Colors also has_many :tags. Is it still important for Tags to belong_to :user assuming the User is the only person who has authority to edit those tags?
And a similar question along the same lines: When updating data in FavoritesController, I've come to the conclusion that you perform CRUD operations by always doing something like current_user.favorites.find(param[:id].update_attributes(param[:favorite]) so that they can definitely only update models that belong to them. Right?
Update Wasn't too happy with any of the answers, as no one really answered my question but instead went after the for-example-only Tags model suggesting better ways to do that. I'm assuming I was right, and models should belong_to :user. I also discovered some great security tips that address my questions here: http://asciicasts.com/episodes/178-seven-security-tips
As you describe the tags it seems that they are more of an aspect, so you can implement them as a polymorphic association. But you should do it many-to-many, as tags can be reused among users and taggable objects. Let's call the join model Tagging, which will be the one that belongs to user if you want to remember who created the tagging.
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :colors, :through => :taggings, :source => :taggable, :source_type => "Color"
has_many :favorites, :through => :taggings, :source => :taggable, :source_type => "Favorite"
end
class Tagging < ActiveRecord::Base
belongs_to :user
belongs_to :taggable, :polymorphic => true
belongs_to :tag
end
class Color < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class Favorite < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class User < ActiveRecord::Base
has_many :favorites
has_many :colors
has_many :taggings
has_many :tags, :through => :taggings
end
As for the Favorite updating, I agree with you: you will mostly work within the scope of a user (most likely the currently logged in user).
It depends on your model. Both cases are valid but I'd discorage making a circular relationships like that. Having a hierarchy is more flexible. For example: User->Favorites->Tags (unless you want to tag users as well)
User.favorites.find(params[:id]).update_attributes(param[:favorite])
is what you mean I guess (syntax). Whoever calls the URL will perform that action. Dont rely on the fact that that URL is visible to one user only (owner of the favorite). You should have checks in place that the currently logged in user is the only one performing actions on the objects that belong to him.
The proposed mechanism sounds a bit too complex for me. I prefer the current_user way. Assume there is a current_user (following the authlogic way) in your authentication system, then simple add a user references (user_id) in every relevant table. Update the current_user for new or update record via a controller filter.
In the models, put relevant belongs_to :users accordingly, put enough has_many in users model if needed.
:has_many and :belongs_to in AR will explains the relationship between models, but not necessarily you have to use them in your models, the associaton between them will be already present in the tables as a foreign key.
But adding :has_many or :belongs_to to your models will give you extra methods to your model
ex:
class User < ActiveRecord::Base
has_many :favorites
#def favorites
# Favorite.find_all_by_user_id(self.id)
# end
end
If you mention has_many it will give a new method in your model called favorites, that method will be invisible (will be present in the AR).
Similarly for any association, if you are planning to use this kind of methods you should use associations in your models.

Resources