Frage

Ich habe Abzeichen (sorta wie Stackoverflow).

Einige von ihnen befestigt werden kann, Dinge badgeable (zum Beispiel ein Abzeichen für> X Kommentare zu einem Beitrag an der Säule angebracht ist). Fast alle in mehreren Ebenen kommen (zum Beispiel> 20,> 100> 200), und Sie können nur eine Stufe pro badgeable x Abzeichen Typ haben (= badgeset_id).

Um es einfacher, die auf einer Ebene-per-Abzeichen Einschränkung zu erzwingen, ich badgings will ihre Abzeichen von einem zweispaltigen Fremdschlüssel spezifizieren - badgeset_id und level - und nicht durch Primärschlüssel (badge_id), obwohl Abzeichen tun habe einen Standard-Primärschlüssel zu.

Code:

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

Wie kann ich einen belongs_to Verein angeben, die das tut (und nicht versucht, eine badge_id zu verwenden), so dass ich die has_many :through verwenden kann?

ETA: Diese teilweise funktioniert (d @ badging.badge Werke), fühlt sich aber schmutzig:

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

Beachten Sie, dass die Bedingungen in Single Zitate, nicht verdoppeln, die es zur Laufzeit interpretiert macht anstatt Ladezeit.

Wenn jedoch versucht, dies mit der zu verwenden: durch Assoziation, erhalte ich die Fehler undefined local variable or method 'level' for #<User:0x3ab35a8>. Und nichts offensichtlich (z 'badges.level = #{badgings.level}') scheint zu funktionieren ...

ETA 2: Unter Emfi Code und es auf ein bisschen Reinigungsarbeiten. Es erfordert das Hinzufügen badge_set_id Abzeichen, die überflüssig ist, aber na ja.

Der Code:

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

Während das funktioniert - und es ist wahrscheinlich eine bessere Lösung - ich halte dies nicht für eine tatsächliche Antwort auf die Frage, wie a) Multi-Keys Fremdschlüssel, oder b) dynamic-Zustand Assoziationen zu tun, die mit der Arbeit: durch Verbände. Also, wenn jemand eine Lösung dafür hat, bitte sprechen.

War es hilfreich?

Lösung

Scheint, wie es vielleicht Training am besten, wenn Sie Abzeichen in zwei Modellen trennen. Hier ist, wie ich es brechen würde Sie die Funktionalität erreichen Sie wollen. Ich warf in einigen Scopes, den Code zu halten, die tatsächlich tut Dinge sauber.

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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top