Question

J'ai des badges (comme StackOverflow).

Certains d’entre eux peuvent être attachés à des choses infalsifiables (par exemple, un badge pour > X commentaires sur un article sont attachés à l’article). Presque tous viennent à plusieurs niveaux (par exemple & Gt; 20, & Gt; 100, & Gt; 200), et vous ne pouvez avoir qu'un seul niveau par type de badge x (= badgeset_id). / p>

Pour faciliter l'application de la contrainte d'un niveau par badge, je souhaite que les badgings spécifient leur badge par une clé étrangère à deux colonnes - level et badge_id - plutôt que par la clé primaire (<= >), bien que les badges aient aussi une clé primaire standard.

Dans le 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

Comment puis-je spécifier une association belongs_to qui le fait (et n'essaie pas d'utiliser un has_many :through), afin de pouvoir utiliser le undefined local variable or method 'level' for #<User:0x3ab35a8>?

ETA: cela fonctionne partiellement (c'est-à-dire @ badging.badge), mais semble sale:

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

Notez que les conditions sont entre guillemets simples et non pas doubles, ce qui les rend interprétées au moment de l'exécution plutôt qu'au moment du chargement.

Cependant, lorsque j'essaie d'utiliser ceci avec l'association: via l'association, le message d'erreur 'badges.level = #{badgings.level}' s'affiche. Et rien d’évident (par exemple, badge_set_id) ne semble fonctionner ...

ETA 2: Prendre le code EmFi et le nettoyer un peu fonctionne. Il faut ajouter <=> à Badge, qui est redondant, mais bon.

Le 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

Bien que cela fonctionne - et que ce soit probablement une meilleure solution - je ne considère pas cela comme une réponse réelle à la question de savoir comment effectuer a) des clés étrangères multi-clés, ou b) des associations de conditions dynamiques qui fonctionnent avec: à travers les associations. Donc, si quelqu'un a une solution pour cela, s'il vous plaît parlez-en.

Était-ce utile?

La solution

On dirait que cela pourrait fonctionner mieux si vous séparez Badge en deux modèles. Voici comment procéder pour obtenir les fonctionnalités souhaitées. J'ai ajouté des champs nommés pour garder le code propre à la réalité.

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top