ActiveRecord/Rails-da bir nechta ustunli tashqi kalitlar/assotsiatsiyalar

Menda nishonlar bor (StackOverflow kabi).

Ulardan ba'zilari nishonli narsalarga biriktirilishi mumkin (masalan, postga > X ta fikr bildirish belgisi postga biriktirilgan). Deyarli barchasi bir nechta darajalarda keladi (masalan, >20, >100, >200) va har bir nishonli x nishon turi uchun faqat bitta darajaga ega boʻlishingiz mumkin (= badgeset_id).

Har bir nishon uchun bir darajali cheklovni qoʻllashni osonlashtirish uchun nishonlar oʻz nishonlarini asosiy kalit (badge_id) bilan emas, ikki ustunli tashqi kalit – badgeset_id va level bilan belgilashini xohlayman, lekin nishonlarda standart mavjud asosiy kalit ham.

Kodda:

class Badge < ActiveRecord::Base
  has_many :badgings, :dependent => :destroy
  # integer: badgeset_id, level

  validates_uniqueness_of :badgeset_id, :scope => :level
end

class Badging < ActiveRecord::Base
  belongs_to :user
  # integer: badgset_id, level instead of badge_id
  #belongs_to :badge # <-- how to specify? 
  belongs_to :badgeable, :polymorphic => true

  validates_uniqueness_of :badgeset_id, :scope => [:user_id, :badgeable_id]
  validates_presence_of :badgeset_id, :level, :user_id  

  # instead of this:
  def badge
    Badge.first(:conditions => {:badgeset_id => self.badgeset_id, :level => self.level})
  end
end

class User < ActiveRecord::Base
  has_many :badgings, :dependent => :destroy do
    def grant badgeset, level, badgeable = nil
      b = Badging.first(:conditions => {:user_id => proxy_owner.id, :badgeset_id => badgeset,
        :badgeable_id => badgeable.try(:id), :badgeable_type => badgeable.try(:class)}) ||
        Badging.new(:user => proxy_owner, :badgeset_id => badgeset, :badgeable => badgeable)
      b.level = level
      b.save
    end
  end
  has_many :badges, :through => :badgings
  # ....
end

has_many :through dan foydalanishim uchun buni amalga oshiradigan (va badge_id dan foydalanishga urinmaydigan) belongs_to assotsiatsiyasini qanday belgilashim mumkin?

ETA: Bu qisman ishlaydi (masalan, @badging.badge ishlaydi), lekin o'zini iflos his qiladi:

belongs_to :badge, :foreign_key => :badgeset_id, :primary_key => :badgeset_id, :conditions => 'badges.level = #{level}'

E'tibor bering, shartlar ikki emas, balki bitta qo'shtirnoq ichida, bu esa uni yuklanish vaqtida emas, balki ish vaqtida izohlaydi.

Biroq, buni :through assotsiatsiyasi bilan ishlatmoqchi bo'lganimda, men undefined local variable or method 'level' for #<User:0x3ab35a8> xatosini olaman. Va hech narsa aniq emas (masalan, 'badges.level = #{badgings.level}') ishlayotganga o'xshaydi ...

ETA 2: EmFi kodini olish va uni biroz tozalash ishlaydi. Bu Badgega badge_set_id qo'shishni talab qiladi, bu ortiqcha, lekin yaxshi.

Kod:

class Badge < ActiveRecord::Base
  has_many :badgings
  belongs_to :badge_set
  has_friendly_id :name

  validates_uniqueness_of :badge_set_id, :scope => :level

  default_scope :order => 'badge_set_id, level DESC'
  named_scope :with_level, lambda {|level| { :conditions => {:level => level}, :limit => 1 } }

  def self.by_ids badge_set_id, level
    first :conditions => {:badge_set_id => badge_set_id, :level => level} 
  end

  def next_level
    Badge.first :conditions => {:badge_set_id => badge_set_id, :level => level + 1}
  end
end

class Badging < ActiveRecord::Base
  belongs_to :user
  belongs_to :badge 
  belongs_to :badge_set
  belongs_to :badgeable, :polymorphic => true

  validates_uniqueness_of :badge_set_id, :scope => [:user_id, :badgeable_id]
  validates_presence_of :badge_set_id, :badge_id, :user_id  

  named_scope :with_badge_set, lambda {|badge_set|
    {:conditions => {:badge_set_id => badge_set} }
  }

  def level_up level = nil
    self.badge = level ? badge_set.badges.with_level(level).first : badge.next_level
  end

  def level_up! level = nil
    level_up level
    save
  end
end

class User < ActiveRecord::Base
  has_many :badgings, :dependent => :destroy do
    def grant! badgeset_id, level, badgeable = nil
      b = self.with_badge_set(badgeset_id).first || 
         Badging.new(
            :badge_set_id => badgeset_id,
            :badge => Badge.by_ids(badgeset_id, level), 
            :badgeable => badgeable,
            :user => proxy_owner
         )
      b.level_up(level) unless b.new_record?
      b.save
    end
    def ungrant! badgeset_id, badgeable = nil
      Badging.destroy_all({:user_id => proxy_owner.id, :badge_set_id => badgeset_id,
        :badgeable_id => badgeable.try(:id), :badgeable_type => badgeable.try(:class)})
    end
  end
  has_many :badges, :through => :badgings
end

Bu ishlayotgan bo'lsa-da - va bu yaxshiroq yechim bo'lsa-da, men buni a) ko'p kalitli tashqi kalitlar yoki b) assotsiatsiyalar orqali ishlaydigan dinamik shartli assotsiatsiyalar qanday qilish kerakligi haqidagi savolga haqiqiy javob deb hisoblamayman. Shunday ekan, kimdir bunga yechim topsa, ayting.


person Sai    schedule 27.10.2009    source manba


Javoblar (1)


Badgeni ikkita modelga ajratsangiz, u eng yaxshi ish bo'lishi mumkin. Siz xohlagan funksiyaga erishish uchun men buni qanday qilib ajrataman. Men aslida narsalarni toza qiladigan kodni saqlash uchun ba'zi nomlangan doiralarni tashladim.

class BadgeSet
  has_many :badges
end

class Badge
  belongs_to :badge_set
  validates_uniqueness_of :badge_set_id, :scope => :level

  named_scope :with_level, labmda {|level
    { :conditions => {:level => level} }
  }

  named_scope :next_levels, labmda {|level
    { :conditions => ["level > ?", level], :order => :level }
  }

  def next_level 
    Badge.next_levels(level).first
  end
end

class Badging < ActiveRecord::Base
  belongs_to :user
  belongs_to :badge 
  belongs_to :badge_set
  belongs_to :badgeable, :polymorphic => true

  validates_uniqueness_of :badge_set_id, :scope => [:user_id, :badgeable_id]
  validates_presence_of :badge_set_id, :badge_id, :user_id  

  named_scope :with_badge_set, lambda {|badge_set|
    {:conditions => {:badge_set_id => badge_set} }
  }

  def level_up(level = nil)
    self.badge = level ? badge_set.badges.with_level(level).first 
      : badge.next_level
    save
  end
end

class User < ActiveRecord::Base
  has_many :badgings, :dependent => :destroy do
    def grant badgeset, level, badgeable = nil
      b = badgings.with_badgeset(badgeset).first() || 
         badgings.build(
            :badge_set => :badgeset,
            :badge => badgeset.badges.level(level), 
            :badgeable => badgeable
         )

      b.level_up(level) unless b.new_record?

      b.save
    end
  end
  has_many :badges, :through => :badgings
  # ....
end
person EmFi    schedule 27.10.2009
comment
Bu ko'proq yoki kamroq ishlaydi. Bu savolga to'liq javob emas, garchi bu muammoga javob bo'lsa ham, men buni shunday deb hisoblayman. Men sizning kodingizni tozaladim va uni savolga qo'ydim. - person Sai; 28.10.2009
comment
Bilaman. Siz so'ragan narsa Rails bilan osonlik bilan amalga oshiriladigan narsalardan tashqariga o'xshab tuyuldi. Plaginlarni qidirdingizmi? Bir qarashda, compositekeys.rubyforge.org siz izlayotgan narsaga o‘xshaydi. - person EmFi; 28.10.2009