programing

해시 인수를 사용한 DRY Ruby 초기화

yoursource 2021. 1. 17. 12:23
반응형

해시 인수를 사용한 DRY Ruby 초기화


특히 구성을위한 DSL 또는 최종 사용자가 노출 될 다른 API 비트를 작성할 때 생성자에 해시 인수를 사용하는 경우가 많습니다. 내가 끝내는 것은 다음과 같은 것입니다.

class Example

    PROPERTIES = [:name, :age]

    PROPERTIES.each { |p| attr_reader p }

    def initialize(args)
        PROPERTIES.each do |p|
            self.instance_variable_set "@#{p}", args[p] if not args[p].nil?
        end
    end

end

이것을 달성하는 더 이상 관용적 인 방법이 없습니까? 버리는 상수와 기호에서 문자열로의 변환은 특히 심각해 보입니다.


상수는 필요하지 않지만 기호-문자열을 제거 할 수 있다고 생각하지 않습니다.

class Example
  attr_reader :name, :age

  def initialize args
    args.each do |k,v|
      instance_variable_set("@#{k}", v) unless v.nil?
    end
  end
end
#=> nil
e1 = Example.new :name => 'foo', :age => 33
#=> #<Example:0x3f9a1c @name="foo", @age=33>
e2 = Example.new :name => 'bar'
#=> #<Example:0x3eb15c @name="bar">
e1.name
#=> "foo"
e1.age
#=> 33
e2.name
#=> "bar"
e2.age
#=> nil

BTW, Struct클래스 생성기 클래스를 살펴볼 수 있습니다 (아직 보지 않은 경우) , 수행하는 것과 다소 비슷하지만 해시 유형 초기화는 없습니다 (하지만 적절한 생성기를 만드는 것은 어렵지 않을 것 같습니다) 수업).

HasProperties

hurikhan의 아이디어를 구현하려고 시도한 결과 다음과 같습니다.

module HasProperties
  attr_accessor :props

  def has_properties *args
    @props = args
    instance_eval { attr_reader *args }
  end

  def self.included base
    base.extend self
  end

  def initialize(args)
    args.each {|k,v|
      instance_variable_set "@#{k}", v if self.class.props.member?(k)
    } if args.is_a? Hash
  end
end

class Example
  include HasProperties

  has_properties :foo, :bar

  # you'll have to call super if you want custom constructor
  def initialize args
    super
    puts 'init example'
  end
end

e = Example.new :foo => 'asd', :bar => 23
p e.foo
#=> "asd"
p e.bar
#=> 23

메타 프로그래밍에 능숙하지 않기 때문에 누구나 자유롭게 구현을 변경할 수 있도록 답변 커뮤니티 위키를 만들었습니다.

Struct.hash_initialized

Marc-Andre의 답변을 확장 Struct하면 해시 초기화 클래스를 만드는 일반적인 기반 방법이 있습니다.

class Struct
  def self.hash_initialized *params
    klass = Class.new(self.new(*params))

    klass.class_eval do
      define_method(:initialize) do |h|
        super(*h.values_at(*params))
      end
    end
    klass
  end
end

# create class and give it a list of properties
MyClass = Struct.hash_initialized :name, :age

# initialize an instance with a hash
m = MyClass.new :name => 'asd', :age => 32
p m
#=>#<struct MyClass name="asd", age=32>

Struct제공자 라이선스 계약은 이러한 클래스를 구축 할 수 있습니다. 이니셜 라이저는 해시가 아닌 하나씩 인수를 취하지 만 변환하기 쉽습니다.

class Example < Struct.new(:name, :age)
    def initialize(h)
        super(*h.values_at(:name, :age))
    end
end

좀 더 일반적인 상태로 유지하려면 values_at(*self.class.members)대신 전화 할 수 있습니다 .


루비에는 이런 일을하는 데 유용한 몇 가지가 있습니다. OpenStruct 클래스는 초기화 메소드에 전달 된 값을 클래스의 속성으로 사용할 수 있도록합니다.

require 'ostruct'

class InheritanceExample < OpenStruct
end

example1 = InheritanceExample.new(:some => 'thing', :foo => 'bar')

puts example1.some  # => thing
puts example1.foo   # => bar

문서는 여기에 있습니다 : http://www.ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html

OpenStruct에서 상속하지 않으려면 (또는 이미 다른 것을 상속하고 있기 때문에 상속 할 수없는 경우) 어떻게해야합니까? Forwardable을 사용하여 모든 메서드 호출을 OpenStruct 인스턴스에 위임 할 수 있습니다.

require 'forwardable'
require 'ostruct'

class DelegationExample
  extend Forwardable

  def initialize(options = {})
    @options = OpenStruct.new(options)
    self.class.instance_eval do
      def_delegators :@options, *options.keys
    end
  end
end

example2 = DelegationExample.new(:some => 'thing', :foo => 'bar')

puts example2.some  # => thing
puts example2.foo   # => bar

Forwardable 용 문서는 여기에 있습니다 : http://www.ruby-doc.org/stdlib-1.9.3/libdoc/forwardable/rdoc/Forwardable.html


해시에이 포함 ActiveSupport::CoreExtensions::Hash::Slice되어 있으면 매우 좋은 솔루션이 있습니다.

class Example

  PROPERTIES = [:name, :age]

  attr_reader *PROPERTIES  #<-- use the star expansion operator here

  def initialize(args)
    args.slice(PROPERTIES).each {|k,v|  #<-- slice comes from ActiveSupport
      instance_variable_set "@#{k}", v
    } if args.is_a? Hash
  end
end

이를 포함 할 수 있고 속성을 설정하고 적절한 초기화를 수행하기 위해 "has_properties"메서드를 정의하는 일반 모듈로 추상화합니다 (이는 테스트되지 않았으며 의사 코드로 간주).

module HasProperties
  def self.has_properties *args
    class_eval { attr_reader *args }
  end

  def self.included base
    base.extend InstanceMethods
  end

  module InstanceMethods
    def initialize(args)
      args.slice(PROPERTIES).each {|k,v|
        instance_variable_set "@#{k}", v
      } if args.is_a? Hash
    end
  end
end

My solution is similar to Marc-André Lafortune. The difference is that each value is deleted from the input hash as it is used to assign a member variable. Then the Struct-derived class can perform further processing on whatever may be left in the Hash. For instance, the JobRequest below retains any "extra" arguments from the Hash in an options field.

module Message
  def init_from_params(params)
    members.each {|m| self[m] ||= params.delete(m)}
  end
end

class JobRequest < Struct.new(:url, :file, :id, :command, :created_at, :options)
  include Message

  # Initialize from a Hash of symbols to values.
  def initialize(params)
    init_from_params(params)
    self.created_at ||= Time.now
    self.options = params
  end
end

Please take a look at my gem, Valuable:

class PhoneNumber < Valuable
  has_value :description
  has_value :number
end

class Person < Valuable
  has_value :name
  has_value :favorite_color, :default => 'red'
  has_value :age, :klass => :integer
  has_collection :phone_numbers, :klass => PhoneNumber
end

jackson = Person.new(name: 'Michael Jackson', age: '50', phone_numbers: [{description: 'home', number: '800-867-5309'}, {description: 'cell', number: '123-456-7890'})

> jackson.name
=> "Michael Jackson"
> jackson.age
=> 50
> jackson.favorite_color
=> "red"
>> jackson.phone_numbers.first
=> #<PhoneNumber:0x1d5a0 @attributes={:description=>"home", :number=>"800-867-5309"}>

I use it for everything from search classes (EmployeeSearch, TimeEntrySearch) to reporting ( EmployeesWhoDidNotClockOutReport, ExecutiveSummaryReport) to presenters to API endpoints. If you add some ActiveModel bits you can easily hook these classes up to forms for gathering criteria. I hope you find it useful.

ReferenceURL : https://stackoverflow.com/questions/2680523/dry-ruby-initialization-with-hash-argument

반응형