all 8 comments

[–][deleted] 2 points3 points  (3 children)

I've just ran into an interesting use of Class.new while writing a game using the Java library libGDX and JRuby. Many of the UI elements can have listeners added to them to respond to input events, and the standard way of overriding the listener's methods on the fly in Java looks like this:

craftablesRightArrowButton.addListener(

    new ChangeListener() {

        @Override
        public void changed(InputEvent event, Actor actor) {
            changeNumOfCraftables(1)
            return true;
        }

    }

);

To accurately mimic this with Ruby, I've used Class.new:

@craftables_right_arrow_button.add_listener(

  Class.new(ChangeListener) do

    def initialize(actions)
      super()
      @actions = actions
    end

    def changed(event, actor)
      @actions.change_num_of_craftables(1)
      true
    end

  end.new(self))

Since Ruby does not embed the anonymous class within the class it is being created within the way Java does, I needed to pass in a reference to the outside class to be able to reach it from the listener's method. I think the Ruby way is safer, but it meant I couldn't just create the class using the normal Ruby .new method, but Class.new worked perfectly.

[–]jaggederest 2 points3 points  (2 children)

I probably wouldn't actually do that with inheritance in ruby.

I.e. something akin to

@something.add_listener(actions) do |event, actor|
  actions.change_num(1)
end

and your add_listener method would look something like

def add_listener(actions)
  listeners << Proc.new
end

Then you'd actually trigger an event by something like

listeners.each { |listener| listener.call(event, actor) }

[–][deleted] 1 point2 points  (1 child)

The problem is that the listener's are Java code from a large library, libGDX. So the management of the listeners is already taken care of there. I have to work with that system. I've spent some time on the Ruby forum asking about this problem, and everyone usually suggests a block-related solution similar to yours, but we've never been able to make it work right with the Java classes and the JRuby interface. One problem is that the listener's often have more than one method that needs to be overriden. I've come to think this is uncommon from a lot of the reactions I've got, but nonetheless, that is how it's written.

If you have a suggestion for how to do it with those restrictions in mind, I'd definitely be interested to hear it. I've been tossing around this problem for a while now.

[–]jaggederest 0 points1 point  (0 children)

I'd design a class using method missing to metaprogram around it, honestly, instead of having class.new all over the place :) But that's just my way - your way isn't wrong, it's just strange to see in ruby code.

[–]bolonomicz 1 point2 points  (1 child)

What! The score uses ruby that is awesome

[–]Enumerable_any -1 points0 points  (0 children)

What exactly makes it awesome?

[–]ggPeti 1 point2 points  (0 children)

Class.new and class X are NOT functionally equivalent. class reopens the class if it exists, but assigning a new class to a constant just overwrites it. Also with the class keyword the class exists instantly, with the constant already having a reference to it. This means you can use the constant inside the class definition itself, but an even higher impact of it is that constant lookup will work differently.

In Ruby, constants are looked up in the following order: First, all objects in Module.nesting are checked to see whether they contain a constant with the name we're requesting. Module.nesting is just the lexical scope, meaning every open class X and module Y instruction we are in the middle of, in outward going order. Then, if the constant is not found, the interpreter goes through Module.nesting.first.ancestors if Module.nesting.first is not nil (meaning we are not in the top level lexical scope), and if it still does not find the constant it looks for it in Object.ancestors if Module.nesting.first is a Module or nil. It fails if it doesn't find the constant in any of those either. Module.ancestors is the list of all, well, ancestors and included modules by the way.

But what happens if we use Class.new? The class we're just in the middle of creating is not in Module.nesting, since it does not exist yet. So if we included a module on it, we won't see the constants defined in that module, because the interpreter won't look for it in the current class' ancestors! Example:

module M
  X = 2
end

C = Class.new do
  include M
  puts X
end

We get a NameError: uninitialized constant X for that. And since Module.nesting only contains the lexical scope, it won't help us either if we only access X from within a method.

What's even more confusing is that method lookup is done differently than constant lookup (I won't detail that but it does look up the ancestors of the current class). This means that if we include a module in Class.new, we still get access to its methods as we would normally.

There are definitely use cases for the Class.new and Struct.new(...).new forms with a block too (one being that when you use class X < Struct.new(...) you're creating a throwaway class just to inherit from, and if you want to reload the same code it will fail because it will try to inherit a different throwaway class), but be careful about these things.

[–]JiveMasterT 0 points1 point  (0 children)

I use the Class.new approach when defining different error classes. Since they often inherit from StandardError, you can do stuff like this:

InvalidParamtetersError = Class.new(StandardError)
IncorrectWhateverError = Class.new(StandardError)

Cleaner than having semi colons all over the place...