Module: Changes::ClassMethods

Includes:
Gather::ClassMethods
Defined in:
lib/changes.rb

Instance Method Summary collapse

Methods included from Gather::ClassMethods

#own_properties, #properties

Instance Method Details

#blankslate!Object

Remove all but the absolutely essential methods from this class. Useful if you want a dynamic property list, but can have some surprising side effects, obviously.



129
130
131
132
133
134
135
# File 'lib/changes.rb', line 129

def blankslate!
  # remove unused methods that might clash with user accessors
  keep_methods = %w( __send__ __id__ self send class inspect instance_eval instance_variables )
  instance_methods.each do |method|
    undef_method( method ) unless keep_methods.include?( method )
  end
end

#dynamic!Object



267
# File 'lib/changes.rb', line 267

def dynamic!; @dynamic = true; end

#dynamic?Boolean

Returns:

  • (Boolean)


270
271
272
273
274
275
# File 'lib/changes.rb', line 270

def dynamic?
  # don't optimise this to @dynamic ||= true, because it will reset
  # an @dynamic of false to true
  @dynamic = false if @dynamic.nil?
  @dynamic
end

#dynamic_properties(*syms) ⇒ Object

Allow accessors to be added dynamically, the default.



262
263
264
265
# File 'lib/changes.rb', line 262

def dynamic_properties( *syms )
  @dynamic = true
  property( *syms )
end

#module_name(symbol) ⇒ Object



137
138
139
# File 'lib/changes.rb', line 137

def module_name( symbol )
  "#{symbol.to_s.capitalize}Property"
end

#operation(name, &body) ⇒ Object

define a method ‘name’ (which can be symbol or a string) which takes args and a block calls gather and then executes the block. Any changes made from args and block are rolled back name! will not roll back values



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/changes.rb', line 287

def operation( name, &body )
  # save the block to be executed later
  class_eval "@@#{name.to_s}_operation_block = body"
  
  # define the new method called name
  class_eval <<-EOF, __FILE__, __LINE__.to_i + 1
    def #{name.to_s}( args = {}, &block )
      preserve do
        gather( args, &block )
        instance_eval( &@@#{name.to_s}_operation_block )
      end
    end
  EOF
  
  # define new method called name!
  class_eval <<-EOF, __FILE__, __LINE__.to_i + 1
    def #{name.to_s}!( args = {}, &block )
      gather( args, &block )
      instance_eval( &@@#{name.to_s}_operation_block )
    end
  EOF
end

#property(*symbols) ⇒ Object

For each property in symbols, add methods:

instance.property as reader
instance.property( value ) as writer
instance.property = value as writer
instance.before_property {|old,new| ... }
instance.after_property {|old,new| ... }
instance.on_property {|old,new| ... }
instance.property_changed?
instance.undo_property!


150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/changes.rb', line 150

def property( *symbols )
  @stripper ||= /^([^\= ]+)\s*\=?\s*$/
  symbols.each do |sym|
    property = @stripper.match( sym.to_s )[1]
    
    # TODO false here until I figure out how to remove properties
    unless false && const_defined?( module_name( property ).to_sym )
      own_properties << property.to_sym
      line, st = __LINE__, <<-EOF
        module #{module_name( property )}
        
        public
        def #{property}(*val)
          if val.empty?
            @#{property}
          else
            self.#{property} = *val
          end
        end
        
        def on_#{property}( &block )
          @on_#{property}_block = block
        end
        
        def after_#{property}( &block )
          @after_#{property}_block = block
        end
        
        def before_#{property}( &block )
          @before_#{property}_block = block
        end
        
        def #{property}=(*new_value)
          unwrapped_value = new_value.size == 1 ? new_value[0] : new_value
          return if unwrapped_value == @#{property}
          
          @before_#{property}_block.call_if( @#{property}, unwrapped_value )
          
          changes[:#{property}] ||= Change.new
          changes[:#{property}].old << @#{property}
          changes[:#{property}].new = unwrapped_value
          
          @#{property} = unwrapped_value
          
          @after_#{property}_block.call_if( changes[:#{property}].old, unwrapped_value )
          @on_#{property}_block.call_if( changes[:#{property}].old, unwrapped_value )
          on_change( :#{property}, changes[:#{property}].old, unwrapped_value )
        end
        
        def #{property}_changed?
          changes.has_key?(:#{property}) && !changes[:#{property}].old.empty?
        end
        
        # Undo the last change and return the undone value.
        # return nil if there were no changes to undo
        def undo_#{property}!
          undo( :#{property} )
        end
        
        def reset_#{property}!
          changes[x] = Change.new
        end
        
        private
        def direct_#{property}=( value )
          @#{property} = value
        end
        
        end # module
      EOF
      class_eval st, __FILE__, line + 1
    end
    
    # always include the module, for re-definitions
    class_eval "include #{property.capitalize}Property"
  end
end

#static!Object



268
# File 'lib/changes.rb', line 268

def static!; @dynamic = false; end

#static_properties(*syms) ⇒ Object

Do not allow accessors to be added dynamically. In other words, a subclass like this

class IndexCollector
  static_properties :row, :column
end

will fail if used like this

collector = IndexCollector.new( :row => 4, :column => 6 ) do
  other 'oops'
end

because :other isn’t added by static_properties.



256
257
258
259
# File 'lib/changes.rb', line 256

def static_properties( *syms )
  @dynamic = false
  property( *syms )
end