state-fu 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. data/LICENSE +40 -0
  2. data/README.textile +293 -0
  3. data/Rakefile +114 -0
  4. data/lib/binding.rb +292 -0
  5. data/lib/event.rb +192 -0
  6. data/lib/executioner.rb +120 -0
  7. data/lib/hooks.rb +39 -0
  8. data/lib/interface.rb +132 -0
  9. data/lib/lathe.rb +538 -0
  10. data/lib/machine.rb +184 -0
  11. data/lib/method_factory.rb +243 -0
  12. data/lib/persistence.rb +116 -0
  13. data/lib/persistence/active_record.rb +34 -0
  14. data/lib/persistence/attribute.rb +47 -0
  15. data/lib/persistence/base.rb +100 -0
  16. data/lib/persistence/relaxdb.rb +23 -0
  17. data/lib/persistence/session.rb +7 -0
  18. data/lib/sprocket.rb +58 -0
  19. data/lib/state-fu.rb +56 -0
  20. data/lib/state.rb +48 -0
  21. data/lib/support/active_support_lite/array.rb +9 -0
  22. data/lib/support/active_support_lite/array/access.rb +60 -0
  23. data/lib/support/active_support_lite/array/conversions.rb +202 -0
  24. data/lib/support/active_support_lite/array/extract_options.rb +21 -0
  25. data/lib/support/active_support_lite/array/grouping.rb +109 -0
  26. data/lib/support/active_support_lite/array/random_access.rb +13 -0
  27. data/lib/support/active_support_lite/array/wrapper.rb +25 -0
  28. data/lib/support/active_support_lite/blank.rb +67 -0
  29. data/lib/support/active_support_lite/cattr_reader.rb +57 -0
  30. data/lib/support/active_support_lite/keys.rb +57 -0
  31. data/lib/support/active_support_lite/misc.rb +59 -0
  32. data/lib/support/active_support_lite/module.rb +1 -0
  33. data/lib/support/active_support_lite/module/delegation.rb +130 -0
  34. data/lib/support/active_support_lite/object.rb +9 -0
  35. data/lib/support/active_support_lite/string.rb +38 -0
  36. data/lib/support/active_support_lite/symbol.rb +16 -0
  37. data/lib/support/applicable.rb +41 -0
  38. data/lib/support/arrays.rb +197 -0
  39. data/lib/support/core_ext.rb +90 -0
  40. data/lib/support/exceptions.rb +106 -0
  41. data/lib/support/has_options.rb +16 -0
  42. data/lib/support/logger.rb +165 -0
  43. data/lib/support/methodical.rb +17 -0
  44. data/lib/support/no_stdout.rb +55 -0
  45. data/lib/support/plotter.rb +62 -0
  46. data/lib/support/vizier.rb +300 -0
  47. data/lib/tasks/spec_last.rake +55 -0
  48. data/lib/tasks/state_fu.rake +57 -0
  49. data/lib/transition.rb +338 -0
  50. data/lib/transition_query.rb +224 -0
  51. data/spec/custom_formatter.rb +49 -0
  52. data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
  53. data/spec/features/method_missing_only_once_spec.rb +28 -0
  54. data/spec/features/not_requirements_spec.rb +118 -0
  55. data/spec/features/plotter_spec.rb +97 -0
  56. data/spec/features/shared_log_spec.rb +7 -0
  57. data/spec/features/singleton_machine_spec.rb +39 -0
  58. data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
  59. data/spec/features/transition_boolean_comparison_spec.rb +101 -0
  60. data/spec/helper.rb +13 -0
  61. data/spec/integration/active_record_persistence_spec.rb +202 -0
  62. data/spec/integration/binding_extension_spec.rb +41 -0
  63. data/spec/integration/class_accessor_spec.rb +117 -0
  64. data/spec/integration/event_definition_spec.rb +74 -0
  65. data/spec/integration/example_01_document_spec.rb +133 -0
  66. data/spec/integration/example_02_string_spec.rb +88 -0
  67. data/spec/integration/instance_accessor_spec.rb +97 -0
  68. data/spec/integration/lathe_extension_spec.rb +67 -0
  69. data/spec/integration/machine_duplication_spec.rb +101 -0
  70. data/spec/integration/relaxdb_persistence_spec.rb +97 -0
  71. data/spec/integration/requirement_reflection_spec.rb +270 -0
  72. data/spec/integration/state_definition_spec.rb +163 -0
  73. data/spec/integration/transition_spec.rb +1033 -0
  74. data/spec/spec.opts +9 -0
  75. data/spec/spec_helper.rb +132 -0
  76. data/spec/state_fu_spec.rb +948 -0
  77. data/spec/units/binding_spec.rb +192 -0
  78. data/spec/units/event_spec.rb +214 -0
  79. data/spec/units/exceptions_spec.rb +82 -0
  80. data/spec/units/lathe_spec.rb +570 -0
  81. data/spec/units/machine_spec.rb +229 -0
  82. data/spec/units/method_factory_spec.rb +366 -0
  83. data/spec/units/sprocket_spec.rb +69 -0
  84. data/spec/units/state_spec.rb +59 -0
  85. metadata +171 -0
data/lib/machine.rb ADDED
@@ -0,0 +1,184 @@
1
+ module StateFu
2
+ class Machine
3
+
4
+ def self.BINDINGS
5
+ @@_bindings ||= {}
6
+ end
7
+
8
+ include Applicable
9
+ include HasOptions
10
+
11
+ attr_reader :hooks
12
+
13
+ #
14
+ # Class methods
15
+ #
16
+
17
+ def self.for_class(klass, name, options={}, &block)
18
+ options.symbolize_keys!
19
+ name = name.to_sym
20
+
21
+ unless machine = klass.state_fu_machines[ name ]
22
+ machine = new(options)
23
+ machine.bind! klass, name, options[:field_name]
24
+ end
25
+ if block_given?
26
+ machine.apply! &block
27
+ end
28
+ machine
29
+ end
30
+
31
+ # make it so that a class which has included StateFu has a binding to
32
+ # this machine
33
+ def self.bind!( machine, owner, name, field_name)
34
+ name = name.to_sym
35
+ # define an accessor method with the given name
36
+ if owner.class == Class
37
+ owner.state_fu_machines[name] = machine
38
+ owner.state_fu_field_names[name] = field_name
39
+ # method_missing to catch NoMethodError for event methods, etc
40
+ StateFu::MethodFactory.define_once_only_method_missing( owner )
41
+ unless owner.respond_to?(name)
42
+ owner.class_eval do
43
+ define_method name do
44
+ state_fu( name )
45
+ end
46
+ end
47
+ end
48
+ # prepare the persistence field
49
+ StateFu::Persistence.prepare_field owner, field_name
50
+ else
51
+ _binding = StateFu::Binding.new machine, owner, name, :field_name => field_name, :singleton => true
52
+ MethodFactory.define_singleton_method(owner, name) { _binding }
53
+ end
54
+ end
55
+
56
+ ##
57
+ ## Instance Methods
58
+ ##
59
+
60
+ attr_reader :states, :events, :options, :helpers, :named_procs, :requirement_messages, :tools
61
+
62
+ def initialize( options={}, &block )
63
+ @states = [].extend( StateArray )
64
+ @events = [].extend( EventArray )
65
+ @helpers = [].extend( HelperArray )
66
+ @tools = [].extend( ToolArray )
67
+ @named_procs = {}
68
+ @requirement_messages = {}
69
+ @options = options
70
+ @hooks = Hooks.for( self )
71
+ apply!( &block ) if block_given?
72
+ end
73
+
74
+ # merge the commands in &block with the existing machine; returns
75
+ # a lathe for the machine.
76
+ def apply!( &block )
77
+ StateFu::Lathe.new( self, &block )
78
+ end
79
+ alias_method :lathe, :apply!
80
+
81
+ def helper_modules
82
+ helpers.modules
83
+ end
84
+
85
+ def inject_helpers_into( obj )
86
+ helpers.inject_into( obj )
87
+ end
88
+
89
+ def inject_tools_into( obj )
90
+ tools.inject_into( obj )
91
+ end
92
+
93
+ def inject_methods_into( obj )
94
+ #puts 'inject_methods_into'
95
+ end
96
+
97
+ # the modules listed here will be mixed into Binding and
98
+ # Transition objects for this machine. use this to define methods,
99
+ # references or data useful to you during transitions, event
100
+ # hooks, or in general use of StateFu.
101
+ #
102
+ # They can be supplied as a string/symbol (as per rails controller
103
+ # helpers), or a Module.
104
+ #
105
+ # To do this globally, just duck-punch StateFu::Machine /
106
+ # StateFu::Binding.
107
+ def helper *modules_to_add
108
+ modules_to_add.each { |mod| helpers << mod }
109
+ end
110
+
111
+ # same as helper, but for extending Lathes rather than the Bindings / Transitions.
112
+ # use this to extend the Lathe DSL to suit your problem domain.
113
+ def tool *modules_to_add
114
+ modules_to_add.each { |mod| tools << mod }
115
+ end
116
+
117
+ # make it so a class which has included StateFu has a binding to
118
+ # this machine
119
+ def bind!( owner, name= DEFAULT, field_name = nil )
120
+ field_name ||= Persistence.default_field_name( name )
121
+ self.class.bind!(self, owner, name, field_name)
122
+ end
123
+
124
+ def empty?
125
+ states.empty?
126
+ end
127
+
128
+ def initial_state=( s )
129
+ case s
130
+ when Symbol, String, StateFu::State
131
+ unless init_state = states[ s.to_sym ]
132
+ init_state = StateFu::State.new( self, s.to_sym )
133
+ states << init_state
134
+ end
135
+ @initial_state = init_state
136
+ else
137
+ raise( ArgumentError, s.inspect )
138
+ end
139
+ end
140
+
141
+ def initial_state()
142
+ @initial_state ||= states.first
143
+ end
144
+
145
+ def state_names
146
+ states.map(&:name)
147
+ end
148
+
149
+ def event_names
150
+ events.map(&:name)
151
+ end
152
+
153
+ # given a messy bunch of symbols, find or create a list of
154
+ # matching States.
155
+ def find_or_create_states_by_name( *args )
156
+ args = args.compact.flatten
157
+ raise ArgumentError.new( args.inspect ) unless args.all? { |a| [Symbol, StateFu::State].include? a.class }
158
+ args.map do |s|
159
+ unless state = states[s.to_sym]
160
+ # TODO clean this line up
161
+ state = s.is_a?( StateFu::State ) ? s : StateFu::State.new( self, s )
162
+ self.states << state
163
+ end
164
+ state
165
+ end
166
+ end
167
+
168
+ # Marshal, the poor man's X-Ray photocopier.
169
+ # TODO: a version which will not break its teeth on procs
170
+ def deep_clone
171
+ Marshal::load(Marshal.dump(self))
172
+ end
173
+ alias_method :deep_copy, :deep_clone
174
+
175
+ def inspect
176
+ "#<#{self.class} ##{__id__} states=#{state_names.inspect} events=#{event_names.inspect} options=#{options.inspect}>"
177
+ end
178
+
179
+ def graphviz
180
+ @graphviz ||= Plotter.new(self).output
181
+ end
182
+
183
+ end
184
+ end
@@ -0,0 +1,243 @@
1
+ module StateFu
2
+ # This class is responsible for defining methods at runtime.
3
+ #
4
+ # TODO: all events, simple or complex, should get the same method signature
5
+ # simple events will be called as: event_name! nil, *args
6
+ # complex events will be called as: event_name! :state, *args
7
+
8
+ class MethodFactory
9
+ attr_accessor :method_definitions
10
+ attr_reader :binding
11
+
12
+ # An instance of MethodFactory is created to define methods on a specific StateFu::Binding, and
13
+ # on the object it is bound to.
14
+
15
+ def initialize( _binding )
16
+ @binding = _binding
17
+ simple_events, complex_events = @binding.machine.events.partition(&:simple?)
18
+ @method_definitions = {}
19
+
20
+ # simple event methods
21
+ # all arguments are passed into the transition / transition query
22
+
23
+ simple_events.each do |event|
24
+ method_definitions["#{event.name}"] = lambda do |*args|
25
+ _binding.find_transition(event, event.target, *args)
26
+ end
27
+
28
+ method_definitions["can_#{event.name}?"] = lambda do |*args|
29
+ _binding.can_transition?(event, event.target, *args)
30
+ end
31
+
32
+ method_definitions["#{event.name}!"] = lambda do |*args|
33
+ _binding.fire_transition!(event, event.target, *args)
34
+ end
35
+ end
36
+
37
+ # complex event methods
38
+ # the first argument is the target state
39
+ # any remaining arguments are passed into the transition / transition query
40
+
41
+ # object.event_name [:target], *arguments
42
+ #
43
+ # returns a new transition. Will raise an IllegalTransition if
44
+ # it is not given arguments which result in a valid combination
45
+ # of event and target state being deducted.
46
+ #
47
+ # object.event_name [nil] suffices if the event has only one valid
48
+ # target (ie only one transition which would not raise a
49
+ # RequirementError if fired)
50
+
51
+ # object.event_name! [:target], *arguments
52
+ #
53
+ # as per the method above, except that it also fires the event
54
+
55
+ # object.can_event_name? [:target], *arguments
56
+ #
57
+ # tests that calling event_name or event_name! would not raise an error
58
+ # ie, the transition is legal and is valid with the arguments supplied
59
+
60
+ complex_events.each do |event|
61
+ method_definitions["#{event.name}"] = lambda do |target, *args|
62
+ _binding.find_transition(event, target, *args)
63
+ end
64
+
65
+ method_definitions["can_#{event.name}?"] = lambda do |target, *args|
66
+ begin
67
+ t = _binding.find_transition(event, target, *args)
68
+ t.valid?
69
+ rescue IllegalTransition
70
+ false
71
+ end
72
+ end
73
+
74
+ method_definitions["#{event.name}!"] = lambda do |target, *args|
75
+ _binding.fire_transition!(event, target, *args)
76
+ end
77
+ end
78
+
79
+ # methods dedicated to a combination of event and target
80
+ # all arguments are passed into the transition / transition query
81
+
82
+ (simple_events + complex_events).each do |event|
83
+ event.targets.each do |target|
84
+ method_definitions["#{event.name}_to_#{target.name}"] = lambda do |*args|
85
+ _binding.find_transition(event, target, *args)
86
+ end
87
+
88
+ method_definitions["can_#{event.name}_to_#{target.name}?"] = lambda do |*args|
89
+ _binding.can_transition?(event, target, *args)
90
+ end
91
+
92
+ method_definitions["#{event.name}_to_#{target.name}!"] = lambda do |*args|
93
+ _binding.fire_transition!(event, target, *args)
94
+ end
95
+ end unless event.targets.nil?
96
+ end
97
+
98
+ @binding.machine.states.each do |state|
99
+ method_definitions["#{state.name}?"] = lambda do
100
+ _binding.current_state == state
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+
107
+ #
108
+ # Class Methods
109
+ #
110
+
111
+ # This should be called once per class using StateFu. It aliases and redefines
112
+ # method_missing for the class.
113
+ #
114
+ # Note this happens when a machine is first bound to the class,
115
+ # not when StateFu is included.
116
+
117
+ def self.prepare_class( klass )
118
+ raise caller.inspect
119
+ self.define_once_only_method_missing( klass )
120
+ end # prepare_class
121
+
122
+ # When triggered, method_missing will first call state_fu!,
123
+ # instantating all bindings & installing their attendant
124
+ # MethodFactories, then check if the object now responds to the
125
+ # missing method name; otherwise it will call the original
126
+ # method_missing.
127
+ #
128
+ # method_missing will then revert to its original implementation.
129
+ #
130
+ # The purpose of all this is to allow dynamically created methods
131
+ # to be called, without worrying about whether they have been
132
+ # defined yet, and without incurring the expense of loading all
133
+ # the object's StateFu::Bindings before they're likely to be needed.
134
+ #
135
+ # Note that if you redefine method_missing on your StateFul
136
+ # classes, it's best to either do it before you include StateFu,
137
+ # or thoroughly understand what's happening in
138
+ # MethodFactory#define_once_only_method_missing.
139
+
140
+ def self.define_once_only_method_missing( klass )
141
+ raise ArgumentError.new(klass.to_s) unless klass.is_a?(Class)
142
+
143
+ klass.class_eval do
144
+ return false if @_state_fu_prepared
145
+ @_state_fu_prepared = true
146
+
147
+ alias_method(:method_missing_before_state_fu, :method_missing) # if defined?(:method_missing, true)
148
+
149
+ def method_missing(method_name, *args, &block)
150
+ # invoke state_fu! to ensure event, etc methods are defined
151
+ begin
152
+ state_fu! unless defined? initialize_state_fu!
153
+ rescue NoMethodError => e
154
+ raise e
155
+ end
156
+
157
+ # reset method_missing for this instance
158
+ class << self; self; end.class_eval do
159
+ alias_method :method_missing, :method_missing_before_state_fu
160
+ end
161
+
162
+ # call the newly defined method, or the original method_missing
163
+ if respond_to?(method_name, true)
164
+ # it was defined by calling state_fu!, which instantiated bindings
165
+ # for its state machines, which defined singleton methods for its
166
+ # states & events when it was constructed.
167
+ __send__( method_name, *args, &block )
168
+ else
169
+ # call the original method_missing (method_missing_before_state_fu)
170
+ method_missing( method_name, *args, &block )
171
+ end
172
+ end # method_missing
173
+ end # class_eval
174
+ end # define_once_only_method_missing
175
+
176
+ # Define the same helper methods on the StateFu::Binding and its
177
+ # object. Any existing methods will not be tampered with, but a
178
+ # warning will be issued in the logs if any methods cannot be defined.
179
+ def install!
180
+ define_event_methods_on( @binding )
181
+ define_event_methods_on( @binding.object )
182
+ end
183
+
184
+ #
185
+ # For each event, on the given object, define three methods.
186
+ # - The first method is the same as the event name.
187
+ # Returns a new, unfired transition object.
188
+ # - The second method has a "?" suffix.
189
+ # Returns true if the event can be fired.
190
+ # - The third method has a "!" suffix.
191
+ # Creates a new Transition, fires and returns it once complete.
192
+ #
193
+ # The arguments expected depend on whether the event is "simple" - ie,
194
+ # has only one possible target state.
195
+ #
196
+ # All simple event methods pass their entire argument list
197
+ # directly to transition. These arguments can be accessed inside
198
+ # event hooks, requirements, etc by calling Transition#args.
199
+ #
200
+ # All complex event methods require their first argument to be a
201
+ # Symbol containing a valid target State's name, or the State
202
+ # itself. The remaining arguments are passed into the transition,
203
+ # as with simple event methods.
204
+ #
205
+ def define_event_methods_on( obj )
206
+ method_definitions.each do |method_name, method_body|
207
+ define_singleton_method( obj, method_name, &method_body)
208
+ end
209
+ end # define_event_methods_on
210
+
211
+ def define_singleton_method( object, method_name, &block )
212
+ MethodFactory.define_singleton_method object, method_name, &block
213
+ end
214
+
215
+ # define a a method on the metaclass of the given object. The
216
+ # resulting "singleton method" will be unique to that instance,
217
+ # not shared by other instances of its class.
218
+ #
219
+ # This allows us to embed a reference to the instance's unique
220
+ # binding in the new method.
221
+ #
222
+ # existing methods will never be overwritten.
223
+
224
+ def self.define_singleton_method( object, method_name, options={}, &block )
225
+ if object.respond_to?(method_name, true)
226
+ msg = !options[:force]
227
+ Logger.info "Existing method #{method(method_name) rescue [method_name].inspect} "\
228
+ "for #{object.class} #{object} "\
229
+ "#{options[:force] ? 'WILL' : 'won\'t'} "\
230
+ "be overwritten."
231
+ else
232
+ metaclass = class << object; self; end
233
+ metaclass.class_eval do
234
+ define_method( method_name, &block )
235
+ end
236
+ end
237
+ end
238
+ alias_method :define_singleton_method, :define_singleton_method
239
+
240
+ end # class MethodFactory
241
+ end # module StateFu
242
+
243
+
@@ -0,0 +1,116 @@
1
+ module StateFu
2
+
3
+ # the persistence module has a few simple tests which help decide which
4
+ # persistence mechanism to use
5
+
6
+ # TODO add event hooks (on_change etc) ...
7
+ # after benchmarking
8
+
9
+ # To create your own custom persistence mechanism,
10
+ # subclass StateFu::Persistence::Base
11
+ # and define prepare_field, read_attribute and write_attribute:
12
+
13
+
14
+ # class StateFu::Persistence::MagneticCarpet < StateFu::Persistence::Base
15
+ # def prepare_field
16
+ #
17
+ #
18
+ # def read_attribute
19
+ # object.send "magnetised_#{field_name}"
20
+ # end
21
+ #
22
+ # def write_attribute( string_value )
23
+ # Logger.debug "magnetising ( #{field_name} => #{string_value} on #{object.inspect}"
24
+ # object.send "magnetised_#{field_name}=", string_value
25
+ # end
26
+ # end
27
+
28
+ module Persistence
29
+ DEFAULT_SUFFIX = '_field'
30
+ @@class_for = {}
31
+ @@fields_prepared = {}
32
+
33
+ #
34
+ # Class Methods
35
+ #
36
+
37
+ def self.default_field_name( machine_name )
38
+ machine_name == DEFAULT ? DEFAULT_FIELD : "#{machine_name.to_s.underscore.tr(' ','_')}#{DEFAULT_SUFFIX}"
39
+ end
40
+
41
+ # returns the appropriate persister class for the given class & field name.
42
+ def self.class_for( klass, field_name )
43
+ raise ArgumentError if [klass, field_name].any?(&:nil?)
44
+ @@class_for[klass] ||= {}
45
+ @@class_for[klass][field_name] ||=
46
+ if active_record_column?( klass, field_name )
47
+ self::ActiveRecord
48
+ elsif relaxdb_document_property?( klass, field_name )
49
+ self::RelaxDB
50
+ else
51
+ self::Attribute
52
+ end
53
+ end
54
+
55
+ def self.for_class( klass, binding, field_name )
56
+ persister_class = class_for klass, field_name
57
+ prepare_field( klass, field_name, persister_class)
58
+ returning persister_class.new( binding, field_name ) do |persister|
59
+ Logger.debug( "#{persister_class}: method #{binding.method_name} as field #{persister.field_name}" )
60
+ end
61
+ end
62
+
63
+ def self.for_instance( binding, field_name )
64
+ metaclass = class << binding.object; self; end
65
+ for_class( metaclass, binding, field_name )
66
+ end
67
+
68
+ # returns a new persister appropriate to the given binding and field_name
69
+ # also ensures the persister class method :prepare_field has been called
70
+ # once for the given class & field name so the field can be set up; eg an
71
+ # attr_accessor or a before_save hook defined
72
+ def self.for( binding )
73
+ field_name = binding.field_name.to_sym
74
+ if binding.singleton?
75
+ for_instance( binding, field_name )
76
+ else
77
+ for_class( binding.target, binding, field_name )
78
+ end
79
+ end
80
+
81
+ # ensures that <persister_class>.prepare_field is called only once
82
+ def self.prepare_field(klass, field_name, persister_class=nil)
83
+ @@fields_prepared[klass] ||= []
84
+ unless @@fields_prepared[klass].include?(field_name)
85
+ persister_class ||= class_for(klass, field_name)
86
+ persister_class.prepare_field( klass, field_name )
87
+ @@fields_prepared[klass] << field_name
88
+ end
89
+ end
90
+
91
+ #
92
+ # Heuristics - simple test methods to determine which persister to use
93
+ #
94
+
95
+ # checks to see if the field_name for persistence is a
96
+ # RelaxDB attribute.
97
+ # Safe to use (skipped) if RelaxDB is not included.
98
+ def self.relaxdb_document_property?( klass, field_name )
99
+ Object.const_defined?('RelaxDB') &&
100
+ klass.ancestors.include?( ::RelaxDB::Document ) &&
101
+ klass.properties.map(&:to_s).include?( field_name.to_s )
102
+ end
103
+
104
+ # checks to see if the field_name for persistence is an
105
+ # ActiveRecord column.
106
+ # Safe to use (skipped) if ActiveRecord is not included.
107
+ def self.active_record_column?( klass, field_name )
108
+ Object.const_defined?("ActiveRecord") &&
109
+ ::ActiveRecord.const_defined?("Base") &&
110
+ klass.ancestors.include?( ::ActiveRecord::Base ) &&
111
+ klass.table_exists? &&
112
+ klass.columns.map(&:name).include?( field_name.to_s )
113
+ end
114
+
115
+ end
116
+ end