Futurescale, Inc. PureMVC Home

The PureMVC Framework Code at the Speed of Thought


Over 10 years of community discussion and knowledge are maintained here as a read-only archive.

New discussions should be taken up in issues on the appropriate projects at https://github.com/PureMVC

Pages: [1]
Print
Author Topic: Ruby Port (Standard and MultiCore)  (Read 14030 times)
ofeldt
Newbie
*
Posts: 3


View Profile Email
« on: October 27, 2009, 09:18:07 »

This is a Request for Comments for the Ruby PureMVC implementation: updated (2009-10-27)

1. Package naming & distribution

The gem (package) should be published under a lowercase-underscore name,
which is somewhat a ruby convention for gems (yes, not all adhere to this one, i know).
Nevertheless 'puremvc_multicore' and 'puremvc_standard' would be appropriate names.

With gemcutter.org getting more popular it might be a good platform to publish upcoming
versions, with a fallback on rubyforge.org.


2. Folder structure

As ruby's "require" is different from AS3's "import" the reversed domain notation
is unnecessary. Rubyist tend to have there namespaces cohere with their
folder-structure, at least to some degree.

:
  puremvc_multicore\        # <- gem root
    lib\
      puremvc\
        multicore\
          core\
          patterns\


3. Namespaces

As for now, the ruby standard port has NO namespaces, though this might not
be a problem at first sight, its bad style and limits usability when many moving
parts (gems) are involved. I've chosen PureMVC as main namespace in which
all PureMVC related project should be embedded.

So the MultiCore port lies in PureMVC::MultiCore, while the Standard port
will reside under PureMVC::Standard.
Other Project like Pipes will be under PureMVC::MultiCore::Utilities::Pipes.

This gives developers a cleaner way to access certain classes. For now i ignored
the subspaces ::Core and ::Pattern, but if there is demand for implementing those,
it's like five minutes of refactoring.

If you are concerned that the namespaces are to long, you could always "include"
the module (somewhat closer to AS3's "import"), thus getting rid off all the namespaces.
(e.g. import PureMVC::MultiCore, would make Facade directly available in Object)


4. Constants, Symbols and Strings

Ruby is very indulgent and sometimes this tends to result in bad code.
As for now i just enforce symbols on multiton keys. this is done because
ruby hashes allow nearly any kind of key, let's look at an example:

:
a_hash = Hash.new
a_hash[nil] = "not nil at all"
a_hash[false] = "might be true"
a_hash["key"] = "value"
a_hash[:key] = :another_value

All of the above statements are valid uses of a hash, but just because they're
valid uses doesn't mean they are always good ones. I could think of use-cases
when it might be desirable, to bind nil or false to a certain value, for example
in some kind of error handler, but as a name of a Facade or a Notification it's
inappropriate. Furthermore, People new to ruby often like to mix strings
with symbols, which leads to confusion when a_hash["key"] is not a_hash[:key].

Symbols are only instantiated once, so will only use memory once,
and their purpose is "to stand for something else", hence the name symbol.
However one might argue that assigning a string to a constant solves this problem,
which is true, but if the representation within the string is only used for
comparison, it's best practice to use a symbol.

So, i ask you if the implementation of Notification should enforce symbols too,
as you never need their actual 'name' just to compare that :a is not :b.

Remember, you can still use constants for a clean programing style, just their
assigned value needs to be a symbol.
(e.g. MyStartupNotification = :my_startup_notification)


5. Mediator implementation and meta-programing

Mediators hinder ruby's meta-programing strength. I modified the current
implementation, by changing the way notification_interests and _handling is
done, to enable the use of mixins (called modules in ruby). this allows to bind
certain methods to one or more notifications and vice versa.

The good news, its fully backward compatible with any project already written
(which to this point would only be the one i wrote, hehe) and still abides
to the Reference, because normally one would override #list_notification_interests()
and #handle_notification() anyway.

I introduced three methods:
#initialize_mediator(), #add_notification_handler(), #remove_notification_handler()
with the first one being called by #initialize() and the second one is called within
#initialite_mediator() or #initialize() to bind instance methods to Notifications.
The third one might even be used for heavy meta-programing.

The only way to break this would be to override the @notification_handlers variable
which holds the handlers and NOT to override list_notification_interests()
and #handle_notification() before the mediator gets registered with the facade.
This is very unlikely and i could not think of a mediator which does not have
any notification interests BUT overrides the @notification_handlers variable.

For more information see ./lib/puremvc/multicore/patterns/mediator/mediator.rb
Documentation still needs some tuning and example uses for most methods.
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #1 on: October 27, 2009, 01:16:27 »

Mediators hinder ruby's meta-programing strength.

Could you elaborate on this please, with examples if need be? Before making the class more complex by adding these new methods, I'd like a more substantial understanding of why the change is needed.

Also have a look at the comments I made in the Ruby board: http://forums.puremvc.org/index.php?topic=1512.msg6851#msg6851

-=Cliff>
« Last Edit: October 27, 2009, 01:42:43 by puremvc » Logged
ofeldt
Newbie
*
Posts: 3


View Profile Email
« Reply #2 on: October 28, 2009, 07:08:25 »

Explaining the use and benefit of a module-extendable mediator

Setup

We have 1 proxy and 3 mediators. The proxy will send a notification with a reference to itself. All 3 mediators will listen to the Proxy::Reference notification and handle it by putting a message to STDOUT which includes its own object-name and a reference to the proxy from note.body. The mediators will come in different "flavors", Classic, Alternative & ModuleExtended.


An ordinary proxy which will send a notification for us, with a reference to itself.

:
class Proxy < PureMVC::MultiCore::Proxy
  Reference = :proxy_reference # notification constant

  def initialize(name, data)
    super(name, data)
  end  
 
  def distribute_reference
    facade.send_notification(Proxy::Reference, self)
  end
  
end


The classic mediator as described in the AS3 reference implementation. I'm aware that the additional handler-method is a little bit overkill for this small example, but it makes comparison a little bit easier.

:
class ClassicMediator < PureMVC::MultiCore::Mediator

  def initialize(name, view)
    super(name, view)
  end
  
  def list_notification_interests
    [Proxy::Reference]
  end
  
  def handle_notification(note)
    case note.name
      when Proxy::Reference
        handle_proxy_reference(note)
    end
  end

  def handle_proxy_reference(note)
    p "Hello from mediator #{self} handler method, notified by proxy #{note.body}"
  end
  
end


The alternative mediator still derives from the same class, but allows a different approach for registering and handling notifications. Remember, both ways are fully interchangeable, the only decision you have to make is, to stick to one or the other way. The full potential of the alternative approach is visible in the ModuleExtendedMediator.

:
class AlternativeMediator < PureMVC::MultiCore::Mediator

  def initialize(name, view)
    super(name, view)
  end
  
  def initialize_mediator # super calls this, while initializing
    add_notification_handler(Proxy::Reference, :handle_proxy_reference)
  end
  
  # we can omit list_notification_interests and handle_notification because super-class handles them
  
  def handle_proxy_reference(note)
    p "Hello from mediator #{self} handler method, notified by proxy #{note.body}"
  end
  
end


Maybe we have a certain use-case, which is always the same for some of our mediators. We could extract these portions of code and fabricate them into a module, and with it we extend those mediators which need this behavior.

:
module ProxyAware

  def self.extended(mediator) # constructor-like method, called when this module extends an object
    mediator.add_notification_handler(Proxy::Reference, :handle_proxy_reference)
  end
  
  def handle_proxy_reference(note)
    p "Hello from mediator #{self} handler method, notified by proxy #{note.body}"
  end
  
end


The module-extended mediator just needs to extend itself with our module (ProxyAware) while initializing. We could extend our mediator with as many modules as we like, we could even have multiple handlers for the same notification, each doing something specific with it.

:
class ModuleExtendedMediator < PureMVC::MultiCore::Mediator  

  def initialize(name, view)
    super(name, view)
    extend ProxyAware # we extent our instance via module ProxyAware
  end
  
end


We tie this all together...
:
facade = PureMVC::MultiCore::Facade.new(:facade)

proxy = Proxy.new(:proxy, nil)
facade.register_proxy(proxy)

facade.register_mediator(ClassicMediator.new("classic", nil))
facade.register_mediator(AlternativeMediator.new("alternative", nil))
facade.register_mediator(ModuleExtendedMediator.new("module", nil))

proxy.distribute_reference


...and get something like this when we run it:
:
# => "Hello from mediator #<ClassicMediator:0x2e398cc> handler method, notified by proxy #<Proxy:0x2e39930>"
# => "Hello from mediator #<AlternativeMediator:0x2e397c8> handler method, notified by proxy #<Proxy:0x2e39930>"
# => "Hello from mediator #<ModuleExtendedMediator:0x2e39750> handler method, notified by proxy #<Proxy:0x2e39930>"


We are not limited to proxy-mediator relations.

Example A: Many view components wxruby provides, allow us to call #show and #hide on them, which shows or hides them respectivly. If we follow the "convention over configuration"-idiom for our application-solution, we could say that there will be always a global HidePopupFrames notification, which hides all loose(popup) frames. We build a module called Popupable which adds the notification interest for this and has a handler-methods that calls #hide on its (view)component. So all we have to do is to extend our mediators with that module, if we need this behavior.

Example B: We don't use a gui-framework, but instead build an command-line-interface which implements its own shell-like environment. Lets say certain views (yes, there might be none-monolithic view-component even in shells) needs to print out a table. we implement to two modules, one for our view-component in order to display the table, by writing data to a buffer, and one for our mediator, which adds the necessary notification-interests and handlers for the data to our mediator.

One last thing about the alternative mediator:
it's merely a byproduct we need in order to use modules. It's real power comes when you combine it with modules, if you don't except to write modules there is no real benefit of using it and the classic approach might be more suited for you.
« Last Edit: October 28, 2009, 01:38:12 by ofeldt » Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #3 on: October 28, 2009, 09:10:25 »

Mediators typically retrieve and cache references to their Proxy collaborators in the onRegister method. There usually isn't a need for a Proxy to send itself to a Mediator inside a notification.

And when would the Proxy send this notification containing itself? What would trigger it? It can't be at or immediately after construction, as the Model is generally prepared before the View. This allows the Mediators to retrieve the Proxies they want to collaborate with. This would require an extra mechanism to tell the Proxies to send themselves AFTER Model and View preparation.

One of the big benefits of this framework is that its best practices aren't platform specific. They dictate the collaboration patterns of the actors, who exist in an abstract realm that is portable to many languages. So one of the dangers when porting is that we break the generally applicable best practices when we alter the framework implementation in order to take advantage of some platform specific magic.

For instance, Flex and AIR sport a really powerful data binding implementation. It magically causes things to be updated without a lot of work. Metadata tags placed in the code cause the compiler to make ordinary properties 'bindable', by adding lots of code behind the scenes at compile time. The problem is that the same feature don't exist in Flash itself. So if the AS3 port were implemented to take advantage of binding, then Flash users couldn't use it. And when it came time to port to Frobnitz++, obviously something completely different would have to be dreamed up.

I agree with adhering to platform conventions such as use of lower_case_and_underscore for method names as opposed to camelCase, if that's what developers are used to in the target platform/language. This is a trivial choice and none of the meaning is lost either way.

But best practices governing things like how a Mediator works and how the PureMVC actors collaborate need to remain as similar to the reference as possible, or confusion will reign.

There is currently only one Best Practices document, with AS3 examples, but the advice should remain valid for all ports without exception. The framework is simple enough in scope and implementation for this to be possible. And while ostensibly all the contributors are supposed to tend their forums, the reality is I do the bulk of the support here. I'm in it for the long haul. So, when someone asks me a question about how to move data around in their application, I need to be able to dispense advice based on the core best practices without a lot of disclaimers about how the Frobnitz++ port does things differently and since I'm not a Frobnitz programmer I'm not sure I understand much about it.

And a developer (or team) using more than one port of the framework, should not find themselves having to apply two different approaches when they build or migrate their applications. They will find it particularly frustrating.

In short, my vote is to veto these platform-specific mediator implementation alterations for the sake of of maintaining the core roles, responsibilities and collaborations of the reference.

-=Cliff>
« Last Edit: October 28, 2009, 09:22:54 by puremvc » Logged
ofeldt
Newbie
*
Posts: 3


View Profile Email
« Reply #4 on: November 02, 2009, 01:27:58 »

Mediators typically retrieve and cache references to their Proxy collaborators in the onRegister method. There usually isn't a need for a Proxy to send itself to a Mediator inside a notification.

And when would the Proxy send this notification containing itself? What would trigger it? It can't be at or immediately after construction, as the Model is generally prepared before the View. This allows the Mediators to retrieve the Proxies they want to collaborate with. This would require an extra mechanism to tell the Proxies to send themselves AFTER Model and View preparation.

Heh, this was just an example, maybe sending the notification from the facade without a proxy would have been a better way. The actual implementation is trivial. I'm aware that there is no need to do this, but if i had chosen an example which incorporated wxruby, it would have been more complex to understand.

One of the big benefits of this framework is that its best practices aren't platform specific. They dictate the collaboration patterns of the actors, who exist in an abstract realm that is portable to many languages. So one of the dangers when porting is that we break the generally applicable best practices when we alter the framework implementation in order to take advantage of some platform specific magic.

Agreed, but since ruby is all about open classes and duck-typing in which modules play an important role, i thought it would be a good idea to allow rubyists their daily dose of syntactic sugar.

I agree with adhering to platform conventions such as use of lower_case_and_underscore for method names as opposed to camelCase, if that's what developers are used to in the target platform/language. This is a trivial choice and none of the meaning is lost either way.

Alright. What about package-structure and naming?

But best practices governing things like how a Mediator works and how the PureMVC actors collaborate need to remain as similar to the reference as possible, or confusion will reign.

They work the same way in regards to interaction with others actors of the system. As mentioned, you can stick to the reference implementation if you want to. Sure, if there is an open-source project which would use the module extended mediators, you need to take a look at the rubydocs (documentation) when porting to AS3, but that (again) is the same if they had implemented the mediator themselves against the interface. (as you mentioned in one of your online talks)

There is currently only one Best Practices document, with AS3 examples, but the advice should remain valid for all ports without exception. The framework is simple enough in scope and implementation for this to be possible.

It's fully compliant to the best practices docs.

And while ostensibly all the contributors are supposed to tend their forums, the reality is I do the bulk of the support here. I'm in it for the long haul. So, when someone asks me a question about how to move data around in their application, I need to be able to dispense advice based on the core best practices without a lot of disclaimers about how the Frobnitz++ port does things differently and since I'm not a Frobnitz programmer I'm not sure I understand much about it.

I'm aware that this is quite a hassle, but you could still support people in the AS3 reference "way" and point out solutions without caring for additions.

And a developer (or team) using more than one port of the framework, should not find themselves having to apply two different approaches when they build or migrate their applications. They will find it particularly frustrating.

In short, my vote is to veto these platform-specific mediator implementation alterations for the sake of of maintaining the core roles, responsibilities and collaborations of the reference.

Okay, i could refactor this into a separated "utility" to clearly distinguish between default behavior and additional features, but this is just for clarity and a little bit overkill.
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #5 on: November 04, 2009, 09:25:15 »

I'm aware that there is no need to do this, but if i had chosen an example which incorporated wxruby, it would have been more complex to understand.

Are you saying the Mediator implementation needs to be altered in order to mediate wxruby view components?

Agreed, but since ruby is all about open classes and duck-typing in which modules play an important role, i thought it would be a good idea to allow rubyists their daily dose of syntactic sugar

Again, I'll agree to stylistic concessions, but not to those which defeat the very purpose of the framework which is to employ well known patterns to achieve the benefits of MVC separation and loose coupling.

I don't see a point in duck-typing in the framework. The actors and their roles, responsibilities and collaborations have been clearly defined and they work well together as designed. I have yet to be convinced that they need to change just because it is a prevalent practice to ignore argument types and classes and let any object be imbued with any functionality at any time.

Perhaps another framework would be more appropriate for leveraging that paradigm, but this one came from the land of interfaces, inheritance and encapsulation and strong-typing is its nature.

What about package-structure and naming?

The proposed namespaces look ok to me. I would ask that ::Core an ::Patterns be implemented to remain as similar to the original structure as possible. For someone studying the code it separates the classes into their little compartments, so that you can tell which actors are used for which patterns as opposed to them all residing in one big folder.

On the Gem name, I see on ruby forge a mix of lower case and CamelCase. With the standard port we went through this and decided to keep it as PureMVC-Ruby. I suppose we could change the gem to lower case, but the project name on Rubyforge or Gemcutter remain CamelCase. I see several projects done like this, including RubyGems itself.

Okay, i could refactor this into a separated "utility" to clearly distinguish between default behavior and additional features, but this is just for clarity and a little bit overkill.
That is the right way to handle it. If we can just implement the ports as closely as is humanly possible to the reference and defer all the tangential possibilities to utilities, then we have a separate place to deal with those alternatives. The reason the framework doesn't include all sorts of bells and whistles that could be shoved into it is so that they can be dealt with as ala carte utilities that can be taken or left based on the developer's tastes.

The addition of just one thing to the framework can radically enhance the way you write applications. For instance; the StateMachine utility. Once you've used it, it permanently alters your concept of how to build a PureMVC app. It's so useful as to be almost indispensable. But as much as I love it, I'm not going to fold it into the framework itself because it doesn't belong there. The goal of MVC has been attained in the framework and everything else is easily handled as a utility.

-=Cliff>
« Last Edit: November 04, 2009, 09:28:05 by puremvc » Logged
Pages: [1]
Print