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 4.1: Property In Ruby Inox Part 4: Property I described I would...
- Ruby Inox Part 5: Component Something promised is something due. Only two days have passed...
- 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...
- Ruby Inox Part 6: Widget We are steering toward a graphical user interface. In this...



ShareThis
[...] More on actions: Ruby Part 3.1: Actions [...]