Here is a short update for the Ruby Inox Part3: Actions article. Some stuff worked out just fine, some stuff had to be changed to facilitate subclassing.
Behavioral changes
when an action is added to a class:
class A include Actions action :clicked
the following methods are created: clicked=, on_clicked, clicked!, clicked each of these methods have a specific role.
clicked=
Assigns a Method, a Proc, or nil to the action. When action_assigned? than it executes the method or the block. If the action is nil action_assigned? returns false, and nothing is executed.
clicked= lambda { do_something; }
clicked= other_obj.method(sym)
on_click
This method is syntactic sugar for assign a Proc to the action. For a simple UI the MVC paradigm is simple overkill, and I want the code to be ass readable as possible. Si if I write a simple script that only displays a dialog with two buttons on it I don’t what to go through MVC.
button.on_click { do_something; }
clicked!
This method has a bang to denote its importance and difference from clicked. This method is for subclasses to override and not meant to be called directly, I figured out that a subclass which wants to perform something when an action is called had to initialize the action with a block( complicating the action assignment for the user, as it should chain the actions ) or overriding the default clicked method which could lead to inconsistency and also complication with the original action.
Subclasses can safely override their subclassing-friendly action bang and do what they please without breaking any related code.
class Component include Actions action :destroy end class Button < Component def destroy! # destroy the native button @qt_button.release end end
clicked
Carry out the clicked action. If the action is assigned calls the assigned method or block and then calls the override-friendly clicked!.
Inheritance cleaned
I had some trouble with inheritance, it is now definitely working better than the first couple of tries.
class A include Actions end class B < A action :foo end # B.has_actions? => true class C < B end # B.has_action?(:foo) => true # C.has_action?(:foo) => true
At this state this behavior is enough. There is one todo left for the next update of the Actions module. Make it meta_programming friendly, changes to the parent class should reflect down the inheritance tree.
B.action :baa C.has_action?(:baa) => false # should be true
Current Code
Here is the source code for the Action module at the time of this writing:
module Actions
def self.included(base)
# add class methods
with base do
extend ClassMethods
end
# add instance methods
with base do
include InstanceMethods
end
end
##########
# Class methods
##########
module ClassMethods
attr_accessor :actions
def has_actions?; true; end
def has_action?(sym); self.actions.include?(sym); end
def actions(*args)
return @actions ||= [] if args.empty?
args.each { |a| self.define_action(a) }
end
def define_action(sym)
assert2(self.actions).not.include?(sym).end
self.actions << sym
self.class_eval %{
def #{"on_#{sym}".to_sym}(&block)
self.action_assign("#{sym}".to_sym, block)
end
def #{sym}=(p)
assert2(p).nil?.or.kind_of?(Proc).or.kind_of?(Method).end
action_assign("#{sym}".to_sym, nil)
end
def #{sym}(*args, &block)
s = "#{sym}".to_sym
if self.has_action?(s) and self.action_assigned?(s)
self.action_call("#{sym}".to_sym, *args, &block)
end
self.#{sym}!(*args,&block)
end
def #{sym}!(*args, &block)
end
}
action_added(sym)
end
alias action define_action
def remove_action(sym)
res = actions.delete(sym)
unless res.nil?
with self do
remove_method(sym)
remove_method("#{sym}=".to_sym)
end
action_removed(sym)
end
end
#
# callbacks
#
def action_added(sym); end
def action_removed(sym); end
##
def inherited(subclass)
if has_properties?
subclass.properties = self.properties.clone
end
if has_actions?
subclass.actions = self.actions.clone
end
end
end #Class Methods
##########
# Instance methods
##########
module InstanceMethods
# returns a list of defined actions
def actions; self.class.actions; end
# set all the actions to call only the methods of one
# delegate object
# if obj if nil than resets all the actions to nil
def actions=(obj)
actions.each { |a| action_assign(a, obj.method(a)) }
end
# return the block/proc/method associated with the action
def action(sym); @actions.nil? ? nil : @actions[sym] end
# associates an action with a proc/block/method
def action_assign(sym, proc)
(@actions ||= Hash.new)[sym] = proc
send! :action_assigned, sym, proc
end
# returns true or false whenever a block/proc/method is
# assicated with the action
def action_assigned?(sym); not action(sym).nil?; end
# returns if this object has a given action
def has_action?(sym); self.class.has_action?(sym); end
# callback(s)
def action_assigned(sym, block); end
def action_unassigned(sym)
raise "Action #{sym} not assigned"
end
def action_missing(sym)
raise "Action #{sym} not defined"
end
protected
# executes the action if assigned
#
# obj.actions[:foo].call
def action_call(sym, *args)
return action_missing(sym) unless has_action?(sym)
action = self.action(sym)
return action_unassigned(sym) if action.nil?
return instance_exec(*args, &action)
end
end # Instance Methods
end # Module Actions
Ruby Inox on GitHub
This file is part of the Ruby Inox project. Inox is a work in progress and this file might not be up to date. The status and latest files are available on GitHub
Related posts:
- Ruby Inox Part 3: Actions Here is the pursuit of a series of related articles...
- Ruby Inox Part 5: Component Something promised is something due. Only two days have passed...
- Ruby Inox Part 4.1: Property In Ruby Inox Part 4: Property I described I would...
- Ruby Inox Part 4: Property Last time, we took a look at Actions. A standard...
- Module structure Following the article Keyword With I present here how I...
Tags: Actions, cross language, Inox, Inox Actions, oo model, Point class, Ruby, Ruby Inox Actions