3

I have a class with a constructor that takes two parameters and sets the fields to those values. I want to also be able to initialize objects from this class by setting fields explicitly inside of a block passed to the constructor.

The constructor:

class Grammar
    attr_reader :rules, :start, ...
    def initialize(rules, start)
        @rules = rules
        @start = start
        ...
    end
end

The parameters passed to the constructor involve creating several objects that are only used as intermediate building blocks for the parameters rules and start and it would make sense to limit the existence of these objects to the block passed to the constructor. In this block I would expect to first build the intermediate objects and then directly set the fields of the new object.

I want to be able to instantiate Grammar like so:

grammar = Grammar.new { |g|
    ...
    # build rules and start
    ...
    g.start = start
    g.rules = rules
}

How should I modify the constructor to allow both methods of initialisation?

2 Answers 2

5

It's actually really easy to add this functionality to most classes provided they already have attr_writer and/or attr_accessor in place:

class Grammar
  attr_accessor :rules, :start, ...

  def initialize(rules, start)
    @rules = rules
    @start = start
    ...

    yield self if block_given?
  end
end

Where you can now do exactly what you wanted. The yield self part will supply the object being initialized to the block, and block_given? is only true if you've supplied a block to the new call. You'll want to run this after setting all your defaults.

Sign up to request clarification or add additional context in comments.

3 Comments

I think they would need attr_writer or attr_accessor here.
So do I have to change the access to these variables in order to be able to set them from within a block? I want to leave this data structure immutable.
You could do it with a short-term proxy object but that's a whole other solution.
3

You might be looking for Object#Tap which is built-in to Ruby — yields self to the block, and then returns self.

class Person
  attr_accessor :name 
end

person = Person.new.tap do |p|
  p.name = "Jan"
end

puts person.name # hi

1 Comment

Clever, but fairly unconventional, so best reserved for cases where a first-class yield in the initializer is not an option, like using a third-party library.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.