eirc
eirc

Reputation: 1664

Ruby factory method to call private setter

I want to create a factory for a class, which will call a private setter. Is this possible?

Doing this:

class Foo
  def self.from_bar(bar)
    f = new bar.foo_id # bar has the id of the foo object I want to make
    f.bar = bar        # but I also wanna store the bar object in foo
    f
  end

  def initialize(id)
  end

  private
  attr_writer :bar

  # lazily load and memoize bar if needed and construction was not
  # made from factory method
  def bar
    @bar ||= ...
  end
end

results in a NoMethodError: private method error. I can send the method, but I'm looking for a non hacky way to do it.

EDIT: Changed attr_accessor to attr_writer and added memoized reader to reveal more of my intent here.

Upvotes: 2

Views: 735

Answers (3)

AJFaraday
AJFaraday

Reputation: 2450

Having re-read the question, I believe this may be the answer.

  • You don't need a private attr_accessor, you can just set an instance variable in the initialize method.
  • It seems that storing the object is part of initialization, and shouldn't then be changeable.

I've come to the conclusion that a factory is not needed, and this can all be done in the initialize method.

class Foo

  def initialize(id, bar)
    @id = id
    @bar = bar
  end

end

Note: You could then make bar accessible with an attr_reader. Which will define bar but not bar=.

attr_reader :bar

Upvotes: 1

AJFaraday
AJFaraday

Reputation: 2450

Yes, this is possible.

Ruby allows you to call private methods from anywhere using the send method.

Your factory would look something like this:

class FooFactory

  def FooFactory.build(bar)
    foo = Foo.new(generate_id)
    foo.send('bar=', bar)
    foo 
  end 

  private

  def generate_id
    rand(999999)
  end 

end 

Then simply call:

FooFactory.build('bar')

Note, this is a standard pattern for factories, the send line would also work in your code snippet.

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

This is possible only with a hacky approach:

class Foo
  def self.from_bar(bar)
    new(bar.foo_id).tap { |f| f.send(:bar=, bar) }
    # or, better:
    new(bar.foo_id).tap { |f| f.instance_variable_set(:@bar, bar) }
  end

  private
  attr_accessor :bar
end

Honestly, making attr_accessor private makes a little sense, though.

Upvotes: 1

Related Questions