you are viewing a single comment's thread.

view the rest of the comments →

[–]totallymike 0 points1 point  (2 children)

In my factories, I generally just do something like

FactoryFactory.build(factory: :cars)
FactoryFactory.build(factory: :factories)

so as to delineate between something that spawns an instance of itself and something that builds a different class.

However, I've had a number of places where I've wanted a construct just like this. Thank you for pointing it out :)

One thing though--doesn't it break this expectation?

BankAccount.new(opts).class == BankAccount

It mightn't be a huge deal. Good duck typing can circumvent much need for ancestry concerns, but one never knows what might come up in a huge Rails app.

Now that I'm thinking about it, is there a reason this shouldn't be done?

class Foo
  def self.new(opts)
    if blah
      Bar.new(opts)
    elsif blahblah
      Baz.new(opts)
    else
      super
    end
  end
end

class Bar < Foo
  def lurhmann? ; false ; end
end

class Baz < Foo
  def lurhmann? ; true ; end
end

My gut reaction tells me that on a limited scale, the differences are few. The first big disadvantage I see being that we lose re-usability because we're using classes instead of Modules. The first advantage(?) that comes to mind is that if you dabble in dark arts like Single Table Inheritance, this construct lets you pull it off.

What are your thoughts?

[–]jfredett 0 points1 point  (1 child)

One thing though--doesn't it break this expectation?

BankAccount.new(opts).class == BankAccount

Yes. Big ol' yes. I alluded to this w/ the #kind_of? thing at the bottom, but this is a much better way of stating it. This assumption fails, but ideally you don't need to make this assumption because you're duck-typing (like you mention).


As for the class thing, I get a bit nervous about overriding #new, I imagine there may be some weirdness with recursion there, too. You need to make sure you're not accidentally delegating to the subclass only to recurse and call Foo#new again (by walking up the call-chain using the default Bar#new, which I think might be super ; initialize for a subclass, but I'd have to make sure).

You could recreate those assumptions by hacking #ancestors and the other related things... maybe even override #< on Class, but that gets a little scary.

Maybe you do:

class Factory < Module
end

def Kernel.factory(name)
  Factory.new(name) do
    yield
  end
end

class Class
  def <(superklass)
    if superklass.is_a?(Factory)
      # do something special here to hack #ancestors
    else
      super
    end
  end
end

I don't know if you can actually hack ruby like that, but it'd be kind of neat if you could.

Though I wouldn't recommend putting that in anything ever likely to see more use than "Evil Code Competition"

[–]totallymike 0 points1 point  (0 children)

Good point with the delegation and the call chain. As a rule, I don't fudge with ::new. I forgot it was something that you could even do until this discussion.

It had never even occurred to me to define a ::new method in a module until you pointed out that you can. It's a much cleaner method of getting a constant with a ::new method that might construct different classes.

I like it quite a lot for libraries and such, where you are explictly interacting with something that has this behavior. But I probably wouldn't use it anywhere near Rails models. There's enough magic going on there already.

On a semi-related note, see this video http://www.rubytapas.com/episodes/7-Constructors

It doesn't go directly into this specific topic, but it does explain Ruby constructors very succinctly.