Ruby Read&;使用默认值和验证写入属性

Ruby Read&;使用默认值和验证写入属性,ruby,Ruby,我正在构建一个类来表示自动生成的玩家角色。字符具有性别,可接受的值为:male和:female。我需要一个属性读取器和写入器来允许访问和自定义,但也需要一个“默认”值,以防尚未选择某个值,并且用户不想选择某个值。我有以下代码: class Character attr :gender def gender= g g = nil unless g == :male or g == :female @gender = g || [:male, :fe

我正在构建一个类来表示自动生成的玩家角色。字符具有性别,可接受的值为
:male
:female
。我需要一个属性读取器和写入器来允许访问和自定义,但也需要一个“默认”值,以防尚未选择某个值,并且用户不想选择某个值。我有以下代码:

class Character
    attr :gender

    def gender= g
        g = nil unless g == :male or g == :female
        @gender = g || [:male, :female].sample
    end

    def gender
        @gender ||= gender=(nil)
    end
end

这样做的目的是始终通过setter来确保值是有效的,并使代码更易于维护,以防我决定在以后的性别列表中添加新值。我面临的问题是a)验证行有多余的值,下面有设置行(在gender=method中),b)从reader方法调用writer方法,传递“nil”看起来很奇怪。有什么想法可以让它更优雅吗?

如果在构造函数中设置了有效值,那么在getter中不需要setter。将来,如果要添加新的性别,可以一次性更新现有角色。这可以让你的代码更干净

class Character
    attr :gender

    def initialize
       @gender = valid_genders.sample
    end

    def gender= g
       @gender = g if valid_genders.include? g
    end

    private

    def valid_genders
      [:male, :female]
    end

end

如果在构造函数中设置了有效值,则不需要getter中的setter。将来,如果要添加新的性别,可以一次性更新现有角色。这可以让你的代码更干净

class Character
    attr :gender

    def initialize
       @gender = valid_genders.sample
    end

    def gender= g
       @gender = g if valid_genders.include? g
    end

    private

    def valid_genders
      [:male, :female]
    end

end
这真的很幼稚,也许是矫枉过正。如果你真的想以一种干净时尚的方式来做这件事,并且计划拥有不仅仅是性别特征的东西,那么你可能想投资一些东西,比如

module CoolAttributes

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def attribute(name, type, options = {})
      get(name, type, options)
      set(name, type, options)
    end

    def get(name, type, options)
      define_method(name) do
        instance_variable_get(:"@#{name}") || if options[:default].is_a?(Proc)
                                                options[:default].call
                                              else
                                                options[:default]
                                              end
      end
    end

    def set(name, type, options)
      define_method("#{name}=") do |value|
        instance_variable_set(:"@#{name}", self.class.cast_to(type, value))
      end
    end

    def cast_to(type, value)
      if type == :boolean
        if ['false', 0, '0', false].include?(value)
          false
        else
          true
        end
      elsif type == :symbol
        value.to_s.to_sym
      end
    end
  end
end

class Character
  include CoolAttributes

  attribute :gender, :symbol, :default => :male
end

c = Character.new
puts c.gender # male 
c.gender = 'male'
puts c.gender.class # Symbol
这与
ActiveModel::Attributes
非常相似

编辑:我最终用这个做了一块宝石

这真是太天真了,也许是矫枉过正了。如果你真的想以一种干净时尚的方式来做这件事,并且计划拥有不仅仅是性别特征的东西,那么你可能想投资一些东西,比如

module CoolAttributes

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def attribute(name, type, options = {})
      get(name, type, options)
      set(name, type, options)
    end

    def get(name, type, options)
      define_method(name) do
        instance_variable_get(:"@#{name}") || if options[:default].is_a?(Proc)
                                                options[:default].call
                                              else
                                                options[:default]
                                              end
      end
    end

    def set(name, type, options)
      define_method("#{name}=") do |value|
        instance_variable_set(:"@#{name}", self.class.cast_to(type, value))
      end
    end

    def cast_to(type, value)
      if type == :boolean
        if ['false', 0, '0', false].include?(value)
          false
        else
          true
        end
      elsif type == :symbol
        value.to_s.to_sym
      end
    end
  end
end

class Character
  include CoolAttributes

  attribute :gender, :symbol, :default => :male
end

c = Character.new
puts c.gender # male 
c.gender = 'male'
puts c.gender.class # Symbol
这与
ActiveModel::Attributes
非常相似


编辑:我最终用这个做了一块宝石

我的版本,您也可以随意初始化:

class Character
    attr :gender

    def initialize(g = nil)
      valid? g ? @gender = g : @gender = random_gender
    end

    def gender=(g)
      @gender = g if valid? g
    end

    def self.list_genders
      new.genders.each_with_index { |gender, idx| puts "#{idx+1} - #{gender}" }
    end

    def valid?(g)
      g && genders.include?(g)
    end

    def genders
      [:male, :female]
    end

    def random_gender
      genders.sample
    end
end


Character.list_genders
character = Character.new
p character.gender
character = Character.new(:male)
p character.gender
character.gender = :female
character.gender = :none
p character.gender

我的版本,您也可以随意初始化:

class Character
    attr :gender

    def initialize(g = nil)
      valid? g ? @gender = g : @gender = random_gender
    end

    def gender=(g)
      @gender = g if valid? g
    end

    def self.list_genders
      new.genders.each_with_index { |gender, idx| puts "#{idx+1} - #{gender}" }
    end

    def valid?(g)
      g && genders.include?(g)
    end

    def genders
      [:male, :female]
    end

    def random_gender
      genders.sample
    end
end


Character.list_genders
character = Character.new
p character.gender
character = Character.new(:male)
p character.gender
character.gender = :female
character.gender = :none
p character.gender

如果您绝对必须有一个随机选择的“默认”性别,则在初始值设定项中设置该性别,而不是在setter中设置该性别,并简单地拒绝setter中的无效值。这样,您总是有一个有效的性别,因此在getter中不需要额外的逻辑,而setter只需要验证新输入不是:male或:female。如果您绝对必须有一个随机选择的“default”性别,请在初始值设定项中设置,而不是在setter中设置,并简单地拒绝setter中的无效值。这样,你总是有一个有效的性别,所以你永远不需要在getter中有额外的逻辑,你的setter只需要验证新的输入不是:男或:女。