Reputation: 21
I'm stuck with this application ) I have 3 models - User, Photo and Like Users will log in, view photos and mark them "like" or "dislike". Here's my DB schema, insignificant fields skipped for brewity:
create_table "likes", :force => true do |t|
t.integer "oid" # oid is photos.id
t.integer "user_id" # user who liked this photo
t.boolean "mark", :default => true
end
create_table "photos", :force => true do |t|
t.string "photo" #filename of the photo
end
create_table "users", :force => true do |t|
t.string "firstname",
t.string "lastname",
t.string "email",
end
Here's models:
class Photo < ActiveRecord::Base
has_many :likes, :foreign_key => 'oid'
end
class Like < ActiveRecord::Base
belongs_to :photo
end
class User < ActiveRecord::Base
has_many :likes, :through => :photos
end
One photo will have one mark per user. i.e. user can't 'like' photo twice or more.
I want users to be able to see which photos they have given estimates after they relogin.
For now, I select photos with this statement: @photos = Photo.limit(30).offset(0) then in template: <%= photo.likes.where("user_id=#{current_user.id}") %> After this, I have 30+ SQL queries, or, in other words, N+1 problem.
One option to avoid the problem is to include likes when selecting photos.
@photos = Photo.includes(:likes ).limit(30).offset(0)
But this will include ALL likes for photo, from all users, and adversely affect the performance of application. Plus I'll have to extract record for current user.
Second option is to create dynamic relation
class User < ActiveRecord::Base
has_many :likes, :through => :photos, :conditions => "user_id = current_user.id"
end
For this option, I'll have to pass current_user.id from controller to model, which doesn't look correct for me.
Please help to solve this
Upvotes: 0
Views: 171
Reputation: 12554
First of all, this is a design issue. Think about this : "like" is some kind of bond between a user and a photo. Just say it loud : "a photo may be liked by many users ; a user may like many photos".
Isn't it clear that your Like
model should stand between your Photo
and User
model ?
.----------.1 0..*.----------.0..* 1.----------.
| users |<-------| likes |-------->| photos |
'----------' '----------' '----------'
class User < ActiveRecord::Base
has_many :likes
has_many :liked_photos, through: :likes, class: 'Photo'
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :photo
# this will ensure that your users can only like a photo once.
# you can also add a unique index to the two columns using
# add_index in a migration for more safety and performance.
validates_uniqueness_of [:user, :photo]
end
class Photo < ActiveRecord::Base
has_many :likes
has_many :liking_users, through: :likes, class: 'User'
end
Now, you just have to do this to retrieve efficiently the relevant pictures:
@user = User.includes( likes: :photos ).find( current_user.id )
@photos = @user.liked_photos
Upvotes: 3