Rails data modeling - alternatves to has_many :through, given polymorphism? - ruby-on-rails

I'm working on an application that allows users to associate images with specific events. Events are owned by a user. The easy solution would of course be:
class Image < ActiveRecord::Base
belongs_to :event
end
class Event < ActiveRecord::Base
has_many :images
belongs_to :user
end
class User < ActiveRecord::Base
has_many :events
has_many :images, :through => :events
end
Except! There is one problem. I also want my users to be able to own images directly, without an intermediary event. If I use polymorphic association "conventionally" here, then obviously user.images won't work properly. Should I just hold my nose, use an :as => :event_images to disambiguate, and define user.all_images if that need ever comes up? Should I have all images owned directly by users and optionally associated with events somehow? (With, of course, a validation to ensure consistency... but that seems code-smelly.) Is there a third solution that is prettier than either of these?

I often find it useful to forget the ActiveRecord DSL and work with the data model directly when defining complex relationships. Once the data model is correct you can then map it into model statements.
It is not quite clear from your question if Images can be owned by a User or an Event, of if they can be owned by both at the same time. That issue will determine the data model you use.
If an Image can be owned by both, the Image table will need a reference to both a user_id and an event_id, which may need to be nullable depending on your use-case (user or event being optional relationships). If the Image can only be owned by one, you could set up some sort of polymorphic ownerable relationship that maps owners to the right owner table (owner_id, owner_type etc etc).
Assuming it can belong to both:
class Image < ActiveRecord::Base
belongs_to :event
belongs_to :user
end
class Event < ActiveRecord::Base
has_many :images
belongs_to :user
end
class User < ActiveRecord::Base
has_many :events
has_many :images
has_many :event_images, :through => :events, :class_name => "Image"
end

Related

ActiveRecord: has_many choices limited to has_many of another model

I would like to achieve something as follows where PersonSubject has many topics, but the choices of these topics are limited to the the selection of topics through another model (ie: through the associated subject):
class Topic < ApplicationRecord
belongs_to :subject
end
class Subject < ApplicationRecord
has_many :topics
end
class PersonSubject < ApplicationRecord
belongs_to :person
belongs_to :subject
has_many :topics # where the choices are limited to the subject.skills
end
I would then like if any person_subject.subject.topics are deleted (or association removed), it would automatically update the person_subject.topics to no longer "point" to the Topic(s) that were deleted.
Is this possible?
You can use a lambda to put arbitrary filters on an association. See What is the equivalent of the has_many 'conditions' option in Rails 4?
has_many :topics, -> { where(skill: subject.skills) }
I don't know that this is exact code will work without seeing your schema (what is the data type of subject.skills, and how do you join this with topic?). But hopefully this gets you on the right track
edit
in response to your comment, I think
has_many :topics, through: :skills
would work

Ruby on Rails 3 Multiple Associations

I have the following associations in place:
class User < ActiveRecord::Base
has_many :shopping_requests
has_many :recommendations, :through => :shopping_requests
end
class ShoppingRequest < ActiveRecord::Base
belongs_to :user
has_many :recommendations
end
class Recommendation < ActiveRecord::Base
belongs_to :shopping_request
has_one :user, :through => :shopping_requests
end
Now I need to add a Compliment class. A user can compliment another user (so I have a user_from_id and a user_to_id). A compliment can be given for either a shopping request and/or a recommendation; and there's no limit (a user can be given several compliments by the same user or other users for any number of shopping requests as well as for recommendations).
I do know to make the Compliment polymorphic but not sure what is the best way to set it up in relation to users/shopping requests/recommendations.
I want to be able to run queries like this:
user_to_id.compliments (to get all the compliments for the user);
user_to_id.shopping_request.compliments (to get all that user's compliments for a particular shopping request;
user_to_id.recommendation.compliments (to get all that user's compliments for a particular recommendation; for this particular query, running user_to_id.shopping_request.recommendation.compliments is fine too);
user_from_id.compliments (to get all the compliments that a user gave another);
user_from_id.shopping_request.compliments (to get all the compliments given by this user for a particular shopping_request), etc....
So what is the best way to set up the association for the Compliment class?
Here's my first swing. Your already-written code works, and I haven't reproduced it here.
class User < ActiveRecord::Base
...
has_many :outgoing_compliments, class_name: "Compliment", foreign_key: "from_id"
has_many :incoming_request_compliments, through: :shopping_requests, source: compliments
has_many :incoming_recommendation_compliments, through: :recommendations, source: compliments
...
end
class ShoppingRequest < ActiveRecord::Base
...
has_many compliments, as: :compliment able
...
end
class Recommendation < ActiveRecord::Base
...
has_many compliments, as: :complimentable
...
end
class Compliment < ActiveRecord::Base
belongs_to :complimentable, polymorphic: true
#relies on two DB columns, complimentable_id and complimentable_type
belongs_to :complimenter, class_name: "User", foreign_key: "from_id"
end
I made one change to your database as you've defined it. Compliment knows which Complimentable it belongs to, and since each Complimentable knows its User, saving the complemented-User is redundant. You could choose to add the lines...
class Compliment
belongs_to :complimented, class_name: "User", foreign_key: "from_id"
class User
has_many :incoming_compliments, class_name: "Compliment", foreign_key: "to_id"
...but I don't think I would.
Those are the associations you'll need to create. However, some of your desired method calls aren't specific enough. One example:
user_to_id.shopping_request.compliments (to get all that user's
compliments for a particular shopping request;
Because what you've written is an instance method for User, we can assume the User is known. However, since a User can have many ShoppingRequests, it isn't possible, through what you've written, to hone in on one specific request to show Compliments for.

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

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.

What is the best way to setup my tables and relationships for this use case?

1)A user can have many causes and a cause can belong to many users.
2)A user can have many campaigns and campaigns can belong to many users. Campaigns belong to one cause.
I want to be able to assign causes or campaigns to a given user, individually. So a user can be assigned a specific campaign. OR a user could be assigned a cause and all of the campaigns of that cause should then be associated with a user.
Is that possible? And could I set it up so that the relationships could be simplified like so:
User.causes = all causes that belong to a user
User.campaigns = all campaigns that belong to user whether through a cause association or campaign association
This should work.
class User < ActiveRecord::Base
has_many :causes, :through => :cause_users
has_many :campaigns, :through => :campaign_users
# other model stuff
class Cause < ActiveRecord::Base
has_many :users, :through => :cause_users
has-many :campaigns
# other model stuff
class Campaign < ActiveRecord::Base
belongs_to :cause
has_many :users, :through => :campaign_users
# other model stuff
class CampaignUser < ActiveRecord::Base
belongs_to :campaign
belongs_to :user
# other model stuff
class CauseUser < ActiveRecord::Base
belongs_to :cause
belongs_to :user
# other model stuff
has_many :through requires that you create a new model for each of these joins: campaign_users and cause_users, as is shown but it provides more functionality later on than has_and_belongs_to_many.
I would also suggest using better names than :campaign_users and :cause_users so the relationship is more meaningful.
I believe you should use the following:
class User < ActiveRecord::Base
has_and_belongs_to_many :causes
has_and_belongs_to_many :campaigns
end
class Cause < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :campaigns
end
class Campaign < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :cause
end
This way you can use
User.causes
User.campaigns
Cause.campaing
Cause.users
Campaign.users
Campaign.cause
You can read here about has_and_belongs_to_many relationship, here about has_one and here about belongs_to.
Let me know if this is what you want :]
Edit:
"I would still need User.campaigns to
be campaigns from a user's causes or
individual campaigns associated with a
user"
You can have a method on users model that returns all campaigns. Something like this:
def all_campaigns
self.campaigns + self.causes.collect{ |c| c.campaigns }
end
You can make :has_many :through associations between users and campaigns using a join model, and also between users and causes using the another join model. The you can make a :has_many :campaigns association in the causes model, putting a :belongs_to :cause in the campaign model.
But you won't be able to fetch all the users campaigns or causes by User.campaigns.orders or User.order.campaigns. You should make an iteration over the User.campaigns collection or User.causes, fetching Campaign.cause or Cause.capaigns. Or even making a custom SQL query, using joins and conditions to filter information in the joins.

Resources