简体   繁体   中英

ruby on rails, how to implement relations

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. ie 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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM