-
Notifications
You must be signed in to change notification settings - Fork 7
ActiveRecord
Let's create a MovieDB app to learn AR :)
rails new moviedb
rails server
$ rails generate model genre name:string
$ rails g model movie name:string release_date:date revenue:decimal genre_id:integer
$ rails g model actor name:string gender:string age:integer
$ rails g model role name:string actor_id:integer movie_id:integer
Open up and see the model files.
Let's define associations.
Associations are defined in the model classes (inherited from ActiveRecord::Base
). Let's define our associations:
- A genre
has_many
movies and a moviebelongs_to
a genre.
class Genre < ActiveRecord::Base
has_many :movies
end
class Movie < ActiveRecord::Base
belongs_to :genre
end
- A movie
has_many
roles and a rolebelongs_to
a movie. - What about association between roles and actors?
- A movie
has_many
actorsthrough
roles.
class Movie < ActiveRecord::Base
belongs_to :genre
has_many :roles
has_many :actors, through: :roles
end
- And vice-versa for actors.
Fire up rails console
.
Create a new Genre..
genre = Genre.new
genre.name = 'Action'
genre.save
In one line...
Genre.create({name: 'Action'})
Now create a movie :)
Let's associate a movie with a genre.
movie = Movie.find(1)
genre = Genre.first
movie.genre = genre
movie.save
Genre..
["Drama", "Science Fiction", "Horror", "Fantasy", "Romance", "Comedy"].each do |name|
Genre.create name: name
end
Movies..
gem 'ffaker'
$ bundle install
Inside Rails console..
genres = Genre.all
10.times do
Movie.create name: Faker::Movie.title, genre: genres.sample, release_date: rand(5.years).seconds.ago, revenue: rand(10_00_000..100_00_00_000)
end
Actors...
20.times do
Actor.create name: Faker::Name.name, gender: ['Male', 'Female'].sample, age: rand(19..75)
end
Roles...
actors = Actor.all
Movie.all.each do |movie|
Role.create name: Faker::Name.first_name, movie: movie, actor: actors.sample
end
- All female actresses:
Actor.where(gender: 'Female')
- All movies after 2013:
Movie.where('release_date > ?', '1-1-2013')
# Sanitizing data from users - Actors below 25 years of age
- Movies released after 2013 and with revenue more than 50 lakhs
-
Top 3 movies by revenue Hint:
.order()
&limit.()
a bit more advanced...
- find genres and their total revenue.
Genre.joins(:movies).select("genres.name, SUM(movies.revenue) as total_revenue").group('genres.name').order('total_revenue')
- All movies featuring female roles
Movie.joins(roles: :actors).where('actors.gender = "Female"')
the N+1 query problem:
- Print movie names along with genre..
Movie.all.each do |movie|
puts movie.name, movie.genre.name
puts '----------------'
end
How many queries were ran? How to fix this?
Movie.all.includes(:genre)
This is called eager loading.
- Use
limit
to limit records. Do not useall
blindly. - Use
eager_loading
.bullet
gem. - Use
select()
to specify only the fields needed when table is large. - If you want just the values from the db - like genre names, use pluck.
Genre.pluck :name
- Create indexes on all association fields (foreign keys) in migrations. For eg. Our app needs a database index on
movies.genre_id
and so on. - Use operations like
sum
,group
andorder
on the database rather than in Ruby
Your users want to rate movies and actors. We could go with two tables as 'movie_ratings' and 'actor_ratings'.
But we get a much simpler implementation when we use polymorphic associations
.
Create model Ratable
.
$ rails g model rating value:integer ratable_id:integer ratable_type:string
migrate...
Update models:
# ratable.rb
class Rating < ActiveRecord::Base
belongs_to :ratable, polymorphic: true
end
# movie.rb
class Movie < ActiveRecord::Base
has_many :ratings, as: :ratable
end
Add ratings to movies and actors
movie.ratings.new(value: 5)
movie.save