So, after thinking on this for a while, I have no idea what the proper way to model this is.
I have a website focused on sharing images. In order to make the users happy, I want them to be able to subscribe to many different collections of images.
So far, there's two types of collections. One is a "creator" relationship, which defines people who worked on a specific image. That looks like this:
class Image < ActiveRecord::Base
has_many :creations
has_and_belongs_to_many :locations
has_many :creators, through: :creations
end
class Creator < ActiveRecord::Base
has_many :images, ->{uniq}, through: :creations
has_many :creations
belongs_to :user
end
class Creation < ActiveRecord::Base
belongs_to :image
belongs_to :creator
end
Users may also tag an image with a subjective tag, which is something not objectively present in the image. Typical subjective tags would include "funny" or "sad," that kind of stuff. That's implemented like this:
class SubjectiveTag < ActiveRecord::Base
# Has a "name" field. The reason why the names of tags are a first-class DB model
# is so we can display how many times a given image has been tagged with a specific tag
end
class SubjectiveCollection < ActiveRecord::Base
# Basically, "User X tagged image Y with tag Z"
belongs_to :subjective_tag
belongs_to :user
has_many :images, through: :subjective_collection_member
end
class SubjectiveCollectionMember < ActiveRecord::Base
belongs_to :subjective_collection
belongs_to :image
end
I want users to be able to subscribe to both Creators and SubjectiveTags, and to display all images in those collections, sequentially, on the home page when they log in.
What is the best way to do this? Should I have a bunch of different subscription types - for example, one called SubjectiveTagSubscription and one called CreatorSubscription? If I do go this route, what is the most efficient way to retrieve all images in each collection?
What you want to use is a Polymorphic Association.
In your case, it would look like this:
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :subscribeable, polymorphic: true
end
The subscriptions table would need to include the following fields:
user_id (integer)
subscribeable_id (integer)
subscribeable_type (string)
This setup will allow a subscription to refer to an instance of any other model, as ActiveRecord will use the subscribeable_type field to record the class name of the thing being subscribed to.
To produce a list of images for the currently logged in user, you could do this:
Subscription.where(user_id: current_user.id).map do |subscription|
subscription.subscribeable.images.all
end.flatten
If the performance implications of the above approach are intolerable (one query per subscription), you could collapse your two types of subscribeables into a single table via STI (which doesn't seem like a good idea here, as the two tables aren't very similar) or you could go back to your initial suggestion of having two different types of subscription models/tables, querying each one separately for subscriptions.
Related
I'm having trouble deciding between Single Table Inheritance and a simple has_one relationship for my two models.
Background: I'm creating a betting website with a "Wager" model. Users may create a wager, at which point it is displayed to all users who may accept the wager if they choose. The wager model has an enum with three statuses: created, accepted, and finished.
Now, I want to add the feature of a "Favorite Wager". The point of this is to make it more convenient for users to create a wager, if they have ones they commonly create. One click instead of ten.
FavoriteWagers exist only as a saved blueprint. They are simply the details of a wager -- when the User wants to create a Wager, they may view FavoriteWagers and click "create", which will take all the fields of the FavoriteWager and create a Wager with them. So the difference is that FavoriteWagers acts as only as a storage for Wager, and also includes a name specified by the user.
I read up on STI, and it seems that a lot of examples have multiple subclassing - eg. Car, Motorcycle, Boat for a "Vehicle" class. Whereas I won't have multiple subclasses, just one (FavoriteWager < Wager). People have also said to defer STI until I can have more classes. I can't see myself subclassing the Wagers class again anytime soon, so that's why I'm hesitant to do STI.
On the other hand, has_one doesn't seem to capture the relationship correctly. Here is an example:
Class User < ApplicationRecord
has_many :favorite_wagers, dependent: :destroy
has_many :wagers, dependent: destroy
end
Class FavoriteWager < ApplicationRecord
has_one :wager
belongs_to: user, index: true, foreign_key: true
end
Class Wager < ApplicationRecord
belongs_to :favorite_wager, optional: true
belongs_to :user
end
I've also thought about just copying the fields directly, but that's not very DRY. Adding an enum with a "draft" option seems too little, because I might need to add more fields in the future (eg. time to auto-create), at which point it starts to evolve into something different. Thoughts on how to approach this?
Why not just do a join table like:
Class User < ApplicationRecord
has_many :favorite_wagers, dependent: :destroy
has_many :wagers, through: :favorite_wagers
end
Class FavoriteWager < ApplicationRecord
belongs_to :wager, index: true, foreign_key: true
belongs_to :user, index: true, foreign_key: true
end
Class Wager < ApplicationRecord
has_one :favorite_wager, dependent: destroy
has_one :user, through: :favorite_wager
end
Your FavoriteWager would have the following fields:
|user_id|wager_id|name|
That way you can access it like:
some_user.favorite_wagers
=> [#<FavoriteWager:0x00007f9adb0fa2f8...
some_user.favorite_wagers.first.name
=> 'some name'
some_user.wagers.first.amount
=> '$10'
some_user.wagers.first.favorite_wager.name
=> 'some name'
which returns an array of favorite wagers. If you only want to have ONE favorite wager per user you can tweak it to limit that. But this gives you the ability to have wagers and users tied together as favorites with a name attribute. I don't quite understand your use case of 'a live wager never has a favorite' but that doesn't matter, you can tweak this to suit your needs.
I'm wracking my brain about how to start setting up the following set of model relationships in my project. I'm not looking for the complete solution (models, tables, migrations, etc), just a little help to get me going in the right direction. I learn more by struggling on my own :)
In short, I want my Users (created with Devise) to have a set of Inclinations that represent preferences they have to certain things (i.e. warmer weather vs colder weather, watching sports vs playing sports, etc) on a sliding scale (coded 5 to -5, zero excluded). Each Inclination (or InclinationData?) will be in a separate table with a name, description, etc, and all Inclinations will get their data pulled from that table (so I don't repeat myself throughout my application). Also, every User will have one Inclination record for each InclinationData(?) by default.
As for the setup, will I need:
5 tables (3 main ones and two join tables)?
Just three (main three models)?
Four (main three models & a joining table for Inclinations and InclinationData)?
Four (main three models & a joining table for Users & Inclinations)?
Perhaps something simpler like a single Profile or Preferences model that's bound to a User?
I can never figure out how to decide if joining tables are necessary, and this is the most complex association I've ever tried.
I appreciate any help, and please let me know if you need more information to be able to help me out.
You say, all Users have the same set of Inclinations. You can put the description of the Inclinations in one model/table and the users ratings in the joining table UserInclination , according to eabraham:
class User < ActiveRecord::Base
has_many :users_inclinations
has_many :inclinations, through: :users_inclinations
end
class UsersInclination < ActiveRecord::Base
belongs_to :users
belongs_to :inclinations
attr_accessible :user_id, :inclination_id, preference_score
end
class Inclination < ActiveRecord::Base
has_many :users_inclinations
attr_accessible :description, :name, :low_label, :high_label
end
So you can pull the description (i.e. to build the rating form) from Inclinations and store the individual ratings with
an_inclination= Inclination.find_by_name('weather')
user.user_inclination.create(inclination: an_inclination, preference_score: the_users_score)
I think I've reasoned through your request:
Users has_many UserInclinations and Inclinations has_one UserInclinations. Roughly, the models should look like this:
class User < ActiveRecord::Base
has_many :user_inclinations
#default Devise attr_accessible
end
class UserInclinations < ActiveRecord::Base
belongs_to :users
belongs_to :inclinations
attr_accessible :user_id, :inclination_id, preference_score
end
class Inclination < ActiveRecord::Base
has_one :user_inclination
attr_accessible :preference
end
I am designing a vocabulary quiz application in ruby/rails.
I have basic model/association setup which will work, but i am worried about scalability.
There will be a set number of words in the application. For simplicity, lets say 100.
A user will be able to proceed to a question, which will be generated from looking at which questions they have had before. The question will provide a word and 4 choices for the definition (one being the definition, three other definitions being randomly chosen).
Here is the way my models and associations are set up currently;
class User < ActiveRecord::Base
has_many :user_questions
end
class UserQuestion < ActiveRecord::Base
belongs_to :user
belongs_to :vocab_word
end
class VocabWord < ActiveRecord::base
has_many :user_question
end
Assuming i were to keep this basic model, which of the following approaches should i use?
Have a set number of UserQuestion objects (100) per user and use
calculated columns to store statistics the users performance on
particular words. (e.g. user 502 has attempted the word 'arid' 3
times and correctlty answered 2 times).
For each question
attempt, create a new UserQuestion object. (e.g. user 502 attempted
to guess 'arid' and was incorrect)
Are either of these approaches scalable? If the application had one million users, the first strategy would have 100 million rows in user_questions. the second could have much more than that.
You're almost there. I would recommend extending your model with the following, plus the slight correction on UserQuestion and User association.
class User < ActiveRecord::Base
has_and_belongs_to_many :user_questions
end
class UserQuestion < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :vocab_word
end
class VocabWord < ActiveRecord::base
has_many :user_question
end
class Attempt < ActiveRecord::base
belongs_to :vocab_word
belongs_to :user
belongs_to :user_question
attr_accessible :result
end
You'd need a user_questions_users association table to have a many-to-many association between questions and users. I believe it would be scalable. Make sure you set your indexes correctly.
I am developing an application like the stackoverflow, which questions or articles have at less one tag. And one tags must have one or more articles.
So, I am doing this in migration in RoR. I am consider which relationship is suitable for both table. In article table, should use a "has_many", and in the tag table, should use "has_many".
But I am thinking is it necessary to add one more table in the middle, something like....
So, the first one is like that:
class Article < ActiveRecord::Base
has_many :tags
end
class Tag < ActiveRecord::Base
has_many :articles
end
or something like this:
class Article < ActiveRecord::Base
has_many :articleTagList
has_many :tags, :through => : articleTagLists
end
class Tag < ActiveRecord::Base
has_many :articleTagList
has_many :articles, :through => :articleTagLists
end
class ArticleTagList < ActiveRecord::Base
belongs_to :article
belongs_to :tag
end
Many-to-Many relationships in a normalized database will always need a third "look-up table."
If you denormalize you can get away with just having the tag id's in one field with a delimiter between them. But you also have to provide the logic to handle retrieval.
I'd personally just go with the normalized option.
If you don't want to store any information on the middle table (for example the name of the user who added tag X to the question Y), you can use the has_and_belongs_to_many:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
If you want to store something, you need to create the middle model, as your example. In your example, the ArticleTagList model should be called ArticlesTag and the database table should be articles_tags, by convention.
I'm trying to build a application that has different kinds of users, I'm using authlogic for user authentication.
So I have one user model that has the required field for authlogic to do its magic. I now want to add a couple of different models that would describe the extra fields for the different kinds of users.
Lets say that a user registers, he would then select his user type, when he is done registering he would be able to add information that is specific for his user model.
What would be the best way to do this? I am currently looking into polymorphic models but I'm not sure that's the best route to take. Any help would be greatly appreciated, thanks.
You can create different profile tables and just tie the profile to the user. So for each user type you can create a table and store the specific info there and have a user_id column to point back to users.
class User < ActiveRecord::Base
has_one :type_1
has_one :type_2
end
class Type1 < ActiveRecord::Base
belongs_to :user
end
class Type2 < ActiveRecord::Base
belongs_to :user
end
Now this isn't very DRY and could lead to problems if you are constantly adding user types. So you could look into polymorphism.
For polymorphism, the users table would define what type the user is (profileable_id and profileable_type). So something like this:
class User < ActiveRecord::Base
belongs_to :profileable, :polymorphic => true
end
class Type1 < ActiveRecord::Base
has_one :user, :as => :profileable
end
class Type2 < ActiveRecord::Base
has_one :user, :as => :profileable
end
Then there is a third option of STI (single table inheritance) for the user types. But that doesn't scale well if the user type fields differ dramatically.
The best approach I saw it here
http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/