barsoom / attr_extras

Takes some boilerplate out of Ruby with methods like attr_initialize.
MIT License
560 stars 31 forks source link

Writer that returns `self` #19

Closed tdsmith closed 8 years ago

tdsmith commented 8 years ago

I'm implementing a builder pattern that has lots of code like:

    def id(id)
      @id = id
      self
    end

    def container_type(container_type)
      @container_type = container_type
      self
    end

    def store(store)
      @store = store
      self
    end

A attr_fluent_writer or something that covers this case would be handy to have here.

henrik commented 8 years ago

Hey,

Thanks for the suggestion! I'm going to have to think a bit on this and discuss it with the team – it's not a pattern we use ourselves. Could you give an example of how you would use it?

tdsmith commented 8 years ago

Yes, sorry! This is useful so you can implement method chaining to get a so-called "fluent interface," which looks something like this in use:

pizza = PizzaBuilder.new(12).
    crust(:whole_wheat).
    withTopping(:spinach).
    build

instead of

builder = PizzaBuilder.new(12)
builder.crust(:whole_wheat)
builder.withTopping(:spinach)
pizza = builder.build

The primary advantage is to avoid the need to name a temporary (e.g.) PizzaBuilder object and it's arguably easier to read.

henrik commented 8 years ago

@tdsmith Oh, sorry, I was unclear – I know about the kind of interface, I was more curious what you use it for specifically :) Since it's something I've seen as a design choice in certain libs, but it's not a pattern our team uses day to day.

The team is currently leaning towards not wanting to include this, since it doesn't seem core to what we see attr_extras as: reducing some boilerplate around extracting small objects (though we do have some stuff in currently that is outside that – we've been thinking about (re)moving those things).

This seems less general purpose and more like something we'd use in very specific situations if at all – I think in the example above, we'd probably go with something like PizzaBuilder.call(id: 12, crust: :whole_wheat, …) using attr_extra's method_object.

A fluent interface makes a lot of sense for stuff like Active Record queries and scopes, but that's a rather specific case, where it would probably be fine to have to do it manually or write your own abstraction inside that library.

But all this is based on our usage patterns – would love to learn about others :)

tdsmith commented 8 years ago

Oops, sorry, my mistake. I don't think I'm sophisticated or prolific enough to give you a useful answer but I was fleetingly inspired by the Docile documentation which I think is intending to give advice on how to build a class that can be both a) used with instance_eval (via Docile's dsl_eval) to define a DSL (which just requires writers, but is where the appeal of the builder pattern arises) and b) nicely instantiated "by hand" (which afaict is where the fluent aspect comes in).

I don't think I see what the method_object approach adds over calling a Pizza constructor (or what the parameter to method_object should be), which makes me worry I'm misanswering you again.

henrik commented 8 years ago

Ah, no worries :)

We tend use method_object for things like builders – when a class effectively acts as a single public method/function. It's basically a shortcut for PizzaBuilder.new(…).build with the benefits of instance-level code (easier to refactor and such), but with a shallower (and thus less fragile, and easier to stub in unit tests) API like PizzaBuilder.call(…). And it communicates that we're dealing with a "method object". Doced here: https://github.com/barsoom/attr_extras#method_object

So currently we feel that this isn't functionality that fits into our vision for attr_extras, but there seem to be other libraries for that. And if someone wants an API more like ours, maybe someone could make an attr_fluent_writer gem?

Closing this ticket – hope that's OK!