I'm trying to create a web application to organize a user's TV interests, to do this, I need to store data of three types: Shows, Seasons, and Episodes.
I would like to query my data like this: Show.find(1).season(2).episode.each. This should return each episode of the second season of the show with the id 1. How can I set my model up to a achieve this?
I've tried having values of season_id and show_id on the episodes, but its unable to find the episodes belonging to each season.
Define relationship in mode,
Show
has_many :seasons
Season
has_many :episodes
belongs_to :show
Episode
belongs_to :season
Then you can call like this,
Show.find(1).seasons.first.episodes.each {}
Maybe it's a good idea to read through the guides. Assuming that your entity relationships looking like this:
You can implement this with activerecord easily. The models would look like this:
require 'active_record'
class Show < ActiveRecord::Base
has_many :seasons
end
class Season < ActiveRecord::Base
belongs_to :show
has_many :episodes
end
class Episode < ActiveRecord::Base
belongs_to :season
end
Your migrations could look like:
require 'active_record'
class CreateShows < ActiveRecord::Migration
def change
create_table :shows do |t|
t.timestamps
end
end
end
class CreateSeasons < ActiveRecord::Migration
def change
create_table :seasons do |t|
t.references :show, :null => false
t.timestamps
end
end
end
class CreateEpisodes < ActiveRecord::Migration
def change
create_table :episodes do |t|
t.references :season, :null => false
t.timestamps
end
end
end
Put some data into your database and query them with:
Show.find(1).seasons.first.episodes.each{ |e| puts e.title }
The answers above are great; I'd take it a step further and use has_many's :through option in the Show model and has_one :through on the Episode model:
# Show
has_many :seasons
has_many :episodes, through: :seasons
# Season
belongs_to :show
has_many :episodes
# Episode
belongs_to :season
has_one :show, through: :season
This lets you make calls like this:
Show.first.episodes
Episode.first.show
... and will also allow you to write some query-minimizing scopes, and write delegate methods that simplifying finding related information.
# Episode
delegate :name, to: :show, prefix: :show
Episode.first.show_name # => Episode.first.show.name
Related
I have a simple relationship
class School < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :schools
end
A user can be part of many schools but at the same time a user might be the admin of a number of schools. I set up a many-to-many relationship to represent this however I'm not sure how I would distinguish between admins and simple users.
I initially thought of setting a table which has a school_id and a user_id and every entry will represent the school id and the user id of any admins that the school has however I'm not sure how I would represent this in rails or if it's the best way to solve this problem? And if it is, how do I access the table without a model associated to it?
What I mean by what I said above:
school_id user_id
1 3
1 4
Which means that the school with id 1 has 2 admins (3 and 4)
What you are looking for is a more complex many_to_many relationship between school and user called has_many :through. This relationship allows you to have many to many relationship with access to the table that represents the relationship. If you use that relationship, your models should look something like this:
class User < ActiveRecord::Base
has_many :school_roles
has_many :schools, through: :school_roles
end
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
end
class School < ActiveRecord::Base
has_many :school_roles
has_many :users, through: :school_roles
end
And the migrations of those tables would look something like this:
class CreateSchoolRoles < ActiveRecord::Migration
def change
create_table :schools do |t|
t.string :name
t.timestamps null: false
end
create_table :users do |t|
t.string :name
t.timestamps null: false
end
create_table :school_roles do |t|
t.belongs_to :school, index: true
t.belongs_to :user, index: true
t.string :role
t.timestamps null: false
end
end
end
I would suggest to make the "role" field in the "school_roles" migration an integer and then use an enum in the model like so:
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
enum role: [ :admin, :user ]
end
which allows you to add more roles in the future, but it's your call
combining polymorphic association with has_many :through in my opinion is best option.
Let's say you create supporting model SchoolRole, which
belongs_to :user
belongs_to :school
belongs_to :rolable, polymorphic:true
This way:
class School ...
has_many :administrators, :as => :schoolroles
has_many :users, :through => :administators
#school.administrators= [..., ...]
It is quite agile.
#user=#school.administrators.build()
class User
has_many :roles, :as => :rolable
def admin?
admin=false
self.roles.each do |r|
if r.role_type == "administator"
admin=true
break
end
end
admin
end
....
So. I have users and movies. Users have watched some movies and not others. I want to express this relationship something like this:
Note:
Not sure if it matters; but movies don't have to be connected to a user; they can exist independently (i.e. Movie 1 has no relationship to User 2). Users can also exist independently; they don't have to have watched or unwatched movies (not pictured here, but you get the idea)
One movie can be watched by one user but unwatched by another (grey vs. black connections)
My initial reaction is that this a has_many :through relationship, something like:
/models/user.rb:
def User
has_many :movies, :through => :unwatched_movies
has_many :movies, :through => :watched_movies
end
/models/movie.rb:
def Movie
has_many :users, :through => :unwatched_movies
has_many :users, :through => :watched_movies
end
But first of all, that code definitely doesn't work...
I want to be able to query for, say, u.unwatched_movies (where u is an instance of User, which doesn't seem to jive with the above.
I have a feeling this has something to do with :source or :as... but I'm feeling a little lost. Am I right in thinking that this is a 3-level hierarchy, where I need models for User, UnwatchedMovieList/WatchedMovieList, and Movie? This question feels very close but I can't seem to make it work in this context.
Any help on how to write these models and migrations would be super helpful. Thank you!
You're trying to create a relationship of omission - "unmatched movies". Which isn't a good idea, you should build up a history of movies watch (which is watched_movies) but then for unwatched you would want to find all movies minus watched movies. Then stick it in a function in User, like so:
def unwatched_movies
Movie.where("id NOT IN ?", self.watched_movies.collect(&:movie_id))
end
Here is my solution
Create these models
class User < ActiveRecord::Base
has_many :user_movies
# Use a block to add extensions
has_many :movies, through: :user_movies, source: 'movie' do
# this is an extension
def watched
where('user_movies.watched = ?', true)
end
def unwatched
where('user_movies.watched = ?', false)
end
end
end
class Movie < ActiveRecord::Base
has_many :user_movies
has_many :watchers, through: :user_movies, source: :user do
# users who is an effective watcher
def watchers
where('user_movies.watched = ?', true)
end
# users how marked it but did not watch it yet
def markers
where('user_movies.watched = ?', false)
end
end
end
class UserMovie < ActiveRecord::Base
belongs_to :user
belongs_to :movie
end
class CreateUserMovies < ActiveRecord::Migration
def change
create_table :user_movies do |t|
t.belongs_to :user, index: true
t.belongs_to :movie, index: true
t.boolean :watched, default: false, null: false
t.timestamps null: false
end
add_foreign_key :user_movies, :users
add_foreign_key :user_movies, :movies
end
end
then for queries
#user = User.first
#user.movies.watched
#user.movies.unwatched
#movie = Movie.first
#movie.watchers.watchers
#movie.watchers.markers
The following set of associations should cover your use case of being able to explicitly mark movies watched and unwatched. It makes use of a join table called user_movies that simply contains the following fields: user_id, movie_id, and watched
class User
has_many :unwatched_user_movies, -> { where(watched: false) }, class_name: 'UserMovie'
has_many :unwatched_movies, through: :unwatched_user_movies, class_name: 'Movie'
has_many :watched_user_movies, -> { where(watched: true) }, class_name: 'UserMovie'
has_many :watched_movies, through: :watched_user_movies, class_name: 'Movie'
end
class UserMovie
belongs_to :movie
belongs_to :user
end
class Movie
has_many :user_movies
end
In my Ruby on Rails project I have a User model and a Content model.
A User has_many :contents and a Content belongs_to :user.
Now, I want to create the idea of playlist. There will be more than one playlists, and each one will have some contents in some order. At this moment, it doesn't matter if a user owns a playlist or not, they'll be general.
A playlist doesn't have any kind of association with a User. They will be general, owned by the system.
I think the solution will be something like having a model Playlist and another table with these attributes: playlist_id:integer content_id:integer order:integer. But do I really need to create all the MVC parts to this new relationship?
As I looked into Rails associations, I got confused and I don't know how to do this, if using the through property, using has_and_belongs_to_many in both Content and Playlist or even how to create this new relationship.
I'd be glad if someone could help me, as you can see, I'm a bit confused.
The solution for you is use has_many through
class User < ActiveRecord::Base
... user code in here with no association
end
class Playlist < ActiveRecord::Base
has_many :content_playlists
has_many :contents, through: :content_playlists
end
class Content < ActiveRecord::Base
has_many :content_playlists
has_many :playlists, through: :content_playlists
end
class ContentPlaylist < ActiveRecord::Base
belongs_to :content
belongs_to :playlist
end
The migration:
class CreateAll < ActiveRecord::Migration
def change
create_table :contents do |t|
t.string :name
t.timestamps
end
create_table :playlists do |t|
t.string :name
t.timestamps
end
create_table :content_playlists do |t|
t.belongs_to :content
t.belongs_to :playlist
t.integer :order
t.timestamps
end
add_index(:content_playlists, :content_id)
add_index(:content_playlists, :playlist_id)
end
end
Now, you can assign a order integer on content_playlists, and in the future you can reorder your playlist changing the value on contents_playlists.
To add a new content_playlist:
c = Content.create(name: "Song 2")
p = Playlist.create(name: "My Playlists2)
ContentPlaylist.create(content: c, playlist: p, order: 1)
Reference:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
You can see (fork, clone, do whatever you want) here:
https://github.com/bdfantini/hmt_example
I'm guessing something like this is what you want:
class User < ActiveRecord::Base
...
has_many :contents
has_many :playlists
has_many :playlisted_contents, :through => :playlists
...
end
class Playlist < ActiveRecord::Base
...
has_many :contents
...
end
class Content < ActiveRecord::Base
...
belongs_to :user
belongs_to :playlist
...
end
I'd start there, and write some tests to see if it's behaving as you want. If your design has other constraints, we might need to adjust this some.
I got the following use-case:
I got three types of Users: Advertisers, Publishers and Administrators. Each user has some common properties (like name or surname) but also several unique associations. The advertiser has an association with Ad(verttisement)s and Campaigns. Each of with is another model of its own.
My question is how would I go about and model that using ActiveRecords? What would the migration code look like?
Here are the model classes:
User:
class User < ActiveRecord :: Base
require 'pbkdf2'
require 'date'
has_many :messages
attribute :name, :surname, :email, :password_hash, :password_salt
attr_accessor :password, :password_confirmation, :type
attribute :user_since, :default => lambda{ Date.today.to_s }
[...]
end
Publisher:
class Publisher < User
has_many :websites
end
Advertiser:
class Advertiser < User
has_many :campaigns
has_many :ads
end
I got the following migration file to create the User:
class AddUser < ActiveRecord::Migration
def up
create_table :users do |t|
t.string :name
t.string :surname
t.string :email
t.string :password_hash
t.string :password_salt
t.date :user_since
t.string :type
end
create_table :messages do |t|
t.belongs_to :user
t.string :account_number
t.timestamps
end
end
def down
drop_table :user
end
end
How do I modify this file in order to incorporate the aforementioned associations?
Edit: Corrected the associations to use plural form.
Polymorphic relationships is one way to solve this, while another way would be to use Single Table Inheritance (STI). Each approach has its benefits and drawbacks, and your decision would probably depend in how different the subclasses of User would tend to be. The more drastically they would differ, the more the decision would tend toward polymorphic relationships.
Using STI approach:
# a single :users table
# one table for each of the other (non-user) models
class User < ActiveRecord::Base
has_many :messages
end
class Publisher < User
has_many :websites
end
class Advertiser < User
# if :campaign supports multiple user-types (polymorphic)
has_many :campaigns, :as => :user
# otherwise
has_many :campaigns
has_many :ads
end
class Message < ActiveRecord::Base
belongs_to :user
end
class Campaign < ActiveRecord::Base
# if multiple user-types will have campaigns
belongs_to :user # referential column should be :user_id
# otherwise
belongs_to :advertiser # referential column should be :advertiser_id
end
Using Polymorphic approach:
# there should be no :users table, as User will be an abstract model class
# instead make a table for each of all the other models
class User < ActiveRecord::Base
self.abstract_class = true
has_many :messages, :as => :messageable
end
class Publisher < User
has_many :websites
end
class Advertiser < User
has_many :campaigns
has_many :ads
end
class Message < ActiveRecord::Base
belongs_to :messageable, polymorphic: true # referential columns should be :messageable_id and :messageable_type
end
class Campaign < ActiveRecord::Base
# if multiple user-types will have campaigns
belongs_to :user, polymorphic: true # referential columns should be :user_id and :user_type
# otherwise
belongs_to :advertiser # referential column should be :advertiser_id
end
I have the following 3 models and relationship between them:
class Calendar < ActiveRecord::Base
has_many:fiscalcalendars
has_many:voucherdatas ,:through => :fiscalcalendars
end
class Fiscalcalendar < ActiveRecord::Base
belongs_to :calendar
has_many :voucherdatas
end
class Voucherdata < ActiveRecord::Base
has_many :fiscalcalendars
has_many :calendars, :through => :fiscalcalendars
end
fields in calendar : id,monthname
fields in fiscalcalendar :id, calendar_id,fiscalyearweek
fields in voucherdata :id, vhour, fiscalyear week
I want the sum of the vhour for each month
I can get it to group by fiscal week by doing
Voucherdata.sum(:vhour,:group=>:fiscalyearweek)
is there a simpler way to get it by month?
This should do what you're asking for, assuming I understand your database relationship.
Voucherdata.sum(:vhour, :joins => :calendars, :group=> 'calendars.monthname)
However this statement won't work without a little modification. You're not telling Rails how to link Voucherdata and Fiscalcalendar. With two :has_many relationships Rails doesn't know where to find the foreign key to link to the other one.
You need to make a join model and either make it a :has_many, :through relationship or use a :has_and_belongs_to_many relationship. Once you've set that up the above statement will work without modification.
Corrected model relationship and migration required. Using a :has_and_belongs_to_many relationship (cleaner syntax):
class Calendar < ActiveRecord::Base
has_many:fiscalcalendars
has_many:voucherdatas ,:through => :fiscalcalendars
end
class Fiscalcalendar < ActiveRecord::Base
belongs_to :calendar
has_and_belongs_to_many :voucherdatas
end
class Voucherdata < ActiveRecord::Base
has_many :calendars, :through => :fiscalcalendars
has_and_belongs_to_many :fiscalcalendars
end
class CreateFiscalcalendarsVoucherdatasJoinTable ActiveRecord::Migration
def self.up
create_table :fiscalcalendars_voucherdatas :id => false do |t|
t.integer :fiscalcalendar_id
t.integer :voucherdata_id
end
end
def self.down
drop_table :fiscalcalendars_voucherdatas
end
end