ninja-model 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/Gemfile +2 -0
  2. data/README.md +1 -0
  3. data/Rakefile +38 -0
  4. data/lib/generators/ninja_model/model/model_generator.rb +19 -0
  5. data/lib/generators/ninja_model/model/templates/model.rb +7 -0
  6. data/lib/generators/ninja_model/scaffold/scaffold_generator.rb +69 -0
  7. data/lib/generators/ninja_model.rb +12 -0
  8. data/lib/ninja-model.rb +1 -0
  9. data/lib/ninja_model/adapters/abstract_adapter.rb +63 -0
  10. data/lib/ninja_model/adapters/adapter_manager.rb +67 -0
  11. data/lib/ninja_model/adapters/adapter_pool.rb +128 -0
  12. data/lib/ninja_model/adapters/adapter_specification.rb +11 -0
  13. data/lib/ninja_model/adapters.rb +70 -0
  14. data/lib/ninja_model/associations/active_record_proxy.rb +53 -0
  15. data/lib/ninja_model/associations/association_proxy.rb +8 -0
  16. data/lib/ninja_model/associations/belongs_to_association.rb +31 -0
  17. data/lib/ninja_model/associations/has_many_association.rb +36 -0
  18. data/lib/ninja_model/associations/has_one_association.rb +19 -0
  19. data/lib/ninja_model/associations/ninja_model_proxy.rb +46 -0
  20. data/lib/ninja_model/associations.rb +198 -0
  21. data/lib/ninja_model/attributes.rb +134 -0
  22. data/lib/ninja_model/base.rb +106 -0
  23. data/lib/ninja_model/callbacks.rb +14 -0
  24. data/lib/ninja_model/configuration.rb +20 -0
  25. data/lib/ninja_model/core_ext/symbol.rb +7 -0
  26. data/lib/ninja_model/errors.rb +5 -0
  27. data/lib/ninja_model/identity.rb +24 -0
  28. data/lib/ninja_model/log_subscriber.rb +18 -0
  29. data/lib/ninja_model/persistence.rb +76 -0
  30. data/lib/ninja_model/predicate.rb +43 -0
  31. data/lib/ninja_model/railtie.rb +27 -0
  32. data/lib/ninja_model/reflection.rb +118 -0
  33. data/lib/ninja_model/relation/finder_methods.rb +74 -0
  34. data/lib/ninja_model/relation/query_methods.rb +62 -0
  35. data/lib/ninja_model/relation/spawn_methods.rb +59 -0
  36. data/lib/ninja_model/relation.rb +80 -0
  37. data/lib/ninja_model/scoping.rb +50 -0
  38. data/lib/ninja_model/validation.rb +38 -0
  39. data/lib/ninja_model/version.rb +3 -0
  40. data/lib/ninja_model.rb +38 -0
  41. data/spec/ninja_model/adapters/adapter_pool_spec.rb +72 -0
  42. data/spec/ninja_model/adapters_spec.rb +8 -0
  43. data/spec/ninja_model/attributes_spec.rb +85 -0
  44. data/spec/ninja_model/base_spec.rb +66 -0
  45. data/spec/ninja_model/identity_spec.rb +41 -0
  46. data/spec/ninja_model/persistence_spec.rb +9 -0
  47. data/spec/ninja_model/predicate_spec.rb +13 -0
  48. data/spec/ninja_model/query_methods_spec.rb +76 -0
  49. data/spec/ninja_model/relation_spec.rb +26 -0
  50. data/spec/ninja_model/scoping_spec.rb +40 -0
  51. data/spec/ninja_model_spec.rb +11 -0
  52. data/spec/spec_helper.rb +9 -0
  53. data/spec/support/active_model_lint.rb +17 -0
  54. metadata +214 -0
@@ -0,0 +1,46 @@
1
+ module NinjaModel
2
+ module Associations
3
+ class NinjaModelProxy
4
+ attr_reader :proxy_klass
5
+ def initialize(active_record)
6
+ @klass = active_record
7
+ @klass.class_eval do
8
+ def ninja_proxy
9
+ @ninja_proxy ||= begin
10
+ self.class.ninja_proxy.instance(self)
11
+ end
12
+ @ninja_proxy.attributes = self.attributes.delete_if { |k,v| k.eql?('id') }
13
+ @ninja_proxy
14
+ end
15
+ end
16
+
17
+ @proxy_klass = active_record.parent.const_set("#{@klass.model_name}Proxy", Class.new(NinjaModel::Base))
18
+
19
+ @klass.columns_hash.each_pair do |k,v|
20
+ @proxy_klass.send :attribute, k, v.type, v.default, @proxy_klass
21
+ end
22
+ end
23
+
24
+ def instance(obj)
25
+ proxy = @proxy_klass.new
26
+ proxy.send :instantiate, {'attributes' => obj.attributes}
27
+ proxy
28
+ end
29
+
30
+ def handle_association(macro, association_id, options)
31
+ unless macro.eql?(:belongs_to)
32
+ options = {:foreign_key => derive_foreign_key}.merge(options)
33
+ end
34
+
35
+ @proxy = nil
36
+ @proxy_klass.send macro, association_id, options
37
+ end
38
+
39
+ private
40
+
41
+ def derive_foreign_key
42
+ "#{@klass.name.underscore}_id".to_sym
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,198 @@
1
+ require 'active_support/concern'
2
+ require 'ninja_model/associations/active_record_proxy'
3
+ require 'ninja_model/associations/ninja_model_proxy'
4
+
5
+ module ActiveRecord
6
+ module Associations
7
+ module ClassMethods
8
+ alias :has_one_without_ninja_model :has_one
9
+ def has_one(association_id, options = {})
10
+ if ninja_model?(:has_one, options[:class_name] || association_id)
11
+ ninja_proxy.handle_association(:has_one, association_id, options)
12
+ else
13
+ has_one_without_ninja_model(association_id, options)
14
+ end
15
+ end
16
+
17
+ def ninja_proxy
18
+ read_inheritable_attribute(:ninja_proxy) || write_inheritable_attribute(:ninja_proxy, NinjaModel::Associations::NinjaModelProxy.new(self))
19
+ end
20
+
21
+ private
22
+
23
+ def ninja_model?(macro, association)
24
+ klass = association.to_s.camelize
25
+ klass = klass.singularize unless [:has_one, :belongs_to].include?(macro)
26
+ klass = klass.constantize
27
+ klass.ancestors.include?(NinjaModel::Base)
28
+ end
29
+ end
30
+
31
+ def method_missing(method, *args)
32
+ begin
33
+ super
34
+ rescue NoMethodError => ex
35
+ if self.class.read_inheritable_attribute(:ninja_proxy) && ninja_proxy.respond_to?(method)
36
+ ninja_proxy.send(method, *args)
37
+ else
38
+ raise ex
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ module Reflection
45
+ module ClassMethods
46
+ alias :reflect_on_association_without_ninja_model :reflect_on_association
47
+ def reflect_on_association(association)
48
+ if read_inheritable_attribute(:ninja_proxy) && ninja_proxy.proxy_klass.reflections.include?(association)
49
+ ninja_proxy.proxy_klass.reflect_on_association(association)
50
+ else
51
+ reflect_on_association_without_ninja_model(association)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ module NinjaModel
59
+ module Associations
60
+ extend ActiveSupport::Concern
61
+
62
+ autoload :AssociationProxy, 'ninja_model/associations/association_proxy'
63
+ autoload :HasOneAssociation, 'ninja_model/associations/has_one_association'
64
+ autoload :HasManyAssociation, 'ninja_model/associations/has_many_association'
65
+ autoload :BelongsToAssociation, 'ninja_model/associations/belongs_to_association'
66
+
67
+
68
+ module ClassMethods
69
+ def has_one(association_id, options = {})
70
+ if ninja_model?(:has_one, options[:class_name] || association_id)
71
+ reflection = create_has_one_reflection(association_id, options)
72
+ association_accessor_methods(reflection, HasOneAssociation)
73
+ #association_constructor_method(:build, reflection, HasOneAssociation)
74
+ #association_constructor_method(:create, reflection, HasOneAssociation)
75
+ #configure_dependency_for_has_one(reflection)
76
+ else
77
+ #puts "Setting up has_one proxy for #{association_id}"
78
+ proxy.handle_association(:has_one, association_id, options)
79
+ end
80
+ end
81
+
82
+ def belongs_to(association_id, options = {})
83
+ if ninja_model?(:belongs_to, options[:class_name] || association_id)
84
+ reflection = create_belongs_to_reflection(association_id, options)
85
+ association_accessor_methods(reflection, BelongsToAssociation)
86
+ #association_constructor_method(:build, reflection, BelongsToAssociation)
87
+ #association_constructor_method(:create, reflection, BelongsToAssociation)
88
+ else
89
+ proxy.handle_association(:belongs_to, association_id, options)
90
+ end
91
+ end
92
+
93
+ def has_many(association_id, options = {})
94
+ if ninja_model?(:has_many, association_id)
95
+ reflection = create_has_many_reflection(association_id, options)
96
+ collection_accessor_methods(reflection, HasManyAssociation)
97
+ #collection_accessor_methods(reflection, HasManyAssociation)
98
+ else
99
+ proxy.handle_association(:has_many, association_id, options)
100
+ end
101
+ end
102
+
103
+ def proxy
104
+ read_inheritable_attribute(:proxy) || write_inheritable_attribute(:proxy, ActiveRecordProxy.new(self))
105
+ end
106
+
107
+ private
108
+
109
+ def create_has_one_reflection(association, options = {})
110
+ create_reflection(:has_one, association, options, self)
111
+ end
112
+
113
+ def create_has_many_reflection(association, options = {})
114
+ create_reflection(:has_many, association, options, self)
115
+ #options[:extend] = create_extension_modules(association, extension
116
+ end
117
+
118
+ def create_belongs_to_reflection(association, options = {})
119
+ create_reflection(:belongs_to, association, options, self)
120
+ end
121
+
122
+ def association_accessor_methods(reflection, association_proxy_class)
123
+ redefine_method(reflection.name) do |*params|
124
+ association = association_instance_get(reflection.name)
125
+
126
+ if association.nil?
127
+ association = association_proxy_class.new(self, reflection)
128
+ retval = association.reload
129
+ if retval.nil? and association_proxy_class == BelongsToAssociation
130
+ association_instance_set(reflection.name, nil)
131
+ return nil
132
+ end
133
+ association_instance_set(reflection.name, association)
134
+ end
135
+ association.target.nil? ? nil : association
136
+ end
137
+
138
+ redefine_method("loaded_#{reflection.name}?") do
139
+ association = association_instance_get(reflection.name)
140
+ association && association.loaded?
141
+ end
142
+
143
+ redefine_method("#{reflection.name}=") do |new_value|
144
+ association = association_instance_get(reflection.name)
145
+ if association.nil? || association.target != new_value
146
+ association = association_proxy_class.new(self, reflection)
147
+ end
148
+
149
+ association.replace(new_value)
150
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
151
+ end
152
+
153
+ redefine_method("set_#{reflection.name}_target") do |target|
154
+ return if target.nil? and association_proxy_class == BelongsToAssociation
155
+ association = association_proxy_class.new(self, reflection)
156
+ association.target = target
157
+ association_instance_set(reflection.name, association)
158
+ end
159
+ end
160
+
161
+ def collection_accessor_methods(reflection, association_proxy_class)
162
+ collection_reader_method(reflection, association_proxy_class)
163
+ end
164
+
165
+ def collection_reader_method(reflection, association_proxy_class)
166
+ redefine_method(reflection.name) do |*params|
167
+ association = association_instance_get(reflection.name)
168
+
169
+ if association.nil?
170
+ association = association_proxy_class.new(self, reflection)
171
+ end
172
+ association_instance_set(reflection.name, association)
173
+ association
174
+ end
175
+ end
176
+ end
177
+
178
+ def association_instance_get(name)
179
+ ivar = "@#{name}"
180
+ if instance_variable_defined?(ivar)
181
+ association = instance_variable_get(ivar)
182
+ association if association.respond_to?(:loaded?)
183
+ end
184
+ end
185
+
186
+ def association_instance_set(name, association)
187
+ instance_variable_set("@#{name}", association)
188
+ end
189
+
190
+ def method_missing(method, *args)
191
+ if self.class.read_inheritable_attribute(:proxy) && proxy.respond_to?(method)
192
+ proxy.send(method, *args)
193
+ else
194
+ super
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,134 @@
1
+ require 'active_support'
2
+ require 'active_model/attribute_methods'
3
+
4
+ module NinjaModel
5
+ class Attribute
6
+ attr_reader :name, :type, :default, :primary_key
7
+ alias :primary_key? :primary_key
8
+ alias :primary :primary_key
9
+
10
+ def initialize(name, type, default, owner_class, options)
11
+ @name, @type, @default = name.to_s, type, default
12
+ @owner_class = owner_class
13
+ @options = options
14
+ @primary_key = options.key?(:primary_key) && options[:primary_key]
15
+ end
16
+
17
+ def define_methods!
18
+ @owner_class.define_attribute_methods(true)
19
+ @owner_class.primary_key = name.to_sym if @primary_key
20
+ end
21
+
22
+ def convert(value)
23
+ case type
24
+ when :string then value
25
+ when :text then value
26
+ when :integer then value.to_i rescue value ? 1 : 0
27
+ when :float then value.to_f
28
+ when :date then ActiveRecord::ConnectionAdapters::Column.string_to_date(value)
29
+ else value
30
+ end
31
+ end
32
+ end
33
+
34
+ module Attributes
35
+ extend ActiveSupport::Concern
36
+ include ActiveModel::AttributeMethods
37
+
38
+ module ClassMethods
39
+ def attribute(name, data_type, *args)
40
+ name = name.to_s
41
+ opts = args.extract_options!
42
+ default = args.first unless args.blank?
43
+ new_attr = Attribute.new(name, data_type, default, self, opts)
44
+ self.model_attributes << new_attr
45
+ new_attr.define_methods!
46
+ end
47
+
48
+ def define_attribute_methods(force = false)
49
+ return unless self.model_attributes
50
+ undefine_attribute_methods if force
51
+ super(self.model_attributes.map { |attr| attr.name })
52
+ end
53
+
54
+ def columns
55
+ model_attributes
56
+ end
57
+
58
+ def model_attributes_hash
59
+ @attributes_hash ||= HashWithIndifferentAccess[model_attributes.map { |attribute| [attribute.name, attribute] }]
60
+ end
61
+
62
+ alias :columns_hash :model_attributes_hash
63
+
64
+ def attribute_names
65
+ @attribute_names ||= model_attributes.map { |attribute| attribute.name }
66
+ end
67
+
68
+ alias :column_names :attribute_names
69
+ end
70
+
71
+ included do
72
+ class_inheritable_accessor :model_attributes
73
+ self.model_attributes = []
74
+ attribute_method_suffix('', '=')
75
+ end
76
+
77
+ def attributes_from_model_attributes
78
+ self.class.model_attributes.inject({}) do |result, attr|
79
+ result[attr.name] = attr.default unless attr.name == self.class.primary_key
80
+ result
81
+ end
82
+ end
83
+
84
+ def write_attribute(name, value)
85
+ name = name.to_s
86
+ if a = self.class.model_attributes_hash[name]
87
+ @attributes[name] = value
88
+ else
89
+ raise NoMethodError, "Unknown attribute #{name.inspect}"
90
+ end
91
+ end
92
+
93
+ def read_attribute(name)
94
+ name = name.to_s
95
+ if !(value = @attributes[name]).nil?
96
+ self.class.model_attributes_hash[name].convert(@attributes[name])
97
+ else
98
+ nil
99
+ end
100
+ end
101
+
102
+ def [](attr_name)
103
+ read_attribute(attr_name)
104
+ end
105
+
106
+ def []=(attr_name, value)
107
+ write_attribute(attr_name, value)
108
+ end
109
+
110
+ def attributes=(new_attributes)
111
+ return unless new_attributes.is_a?(Hash)
112
+ attributes = new_attributes.stringify_keys
113
+
114
+ attributes.each do |k,v|
115
+ respond_to?("#{k}=".to_sym) ? send("#{k}=".to_sym, v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
116
+ end
117
+ end
118
+
119
+ def attribute_method?(name)
120
+ name = name.to_s
121
+ self.class.model_attributes_hash.key?(name)
122
+ end
123
+
124
+ private
125
+
126
+ def attribute(name)
127
+ read_attribute(name)
128
+ end
129
+
130
+ def attribute=(name, value)
131
+ write_attribute(name, value)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,106 @@
1
+ module NinjaModel
2
+ class Base
3
+ include Attributes
4
+ include Callbacks
5
+ include Identity
6
+ include Persistence
7
+ include Scoping
8
+ include Validation
9
+ include Adapters
10
+ include Associations
11
+ include Reflection
12
+ include ActiveRecord::Aggregations
13
+ extend ActiveModel::Translation
14
+ extend ActiveModel::Naming
15
+
16
+ class_inheritable_accessor :default_scoping, :instance_writer => false
17
+ self.default_scoping = []
18
+
19
+ class << self
20
+
21
+ delegate :find, :first, :last, :all, :exists?, :to => :scoped
22
+ delegate :where, :order, :limit, :to => :scoped
23
+
24
+ def configuration_path
25
+ @config_path ||= File.join(Rails.root, "config/ninja_model.yml")
26
+ end
27
+
28
+ def configuration_path=(new_path)
29
+ @config_path = new_path
30
+ end
31
+
32
+ def configuration
33
+ require 'erb'
34
+ require 'yaml'
35
+ @configuration ||= YAML::load(ERB.new(IO.read(configuration_path)).result).with_indifferent_access
36
+ end
37
+
38
+ def relation
39
+ @relation ||= Relation.new(self)
40
+ end
41
+
42
+ def logger
43
+ ::NinjaModel.logger
44
+ end
45
+
46
+ def unscoped
47
+ block_given? ? relation.scoping { yield } : relation
48
+ end
49
+
50
+ def scoped_methods
51
+ key = "#{self}_scoped_methods".to_sym
52
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
53
+ end
54
+
55
+ def default_scope(options = {})
56
+ reset_scoped_methods
57
+ self.default_scoping << build_finder_relation(options, default_scoping.pop)
58
+ end
59
+
60
+ def current_scoped_methods
61
+ last = scoped_methods.last
62
+ last.is_a?(Proc) ? unscoped(&last) : last
63
+ end
64
+
65
+ def reset_scoped_methods
66
+ Thread.current["#{self}_scoped_methods".to_sym] = nil
67
+ end
68
+
69
+ private
70
+
71
+ def build_finder_relation(options = {}, scope = nil)
72
+ relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options
73
+ relation = scope.merge(relation) if scope
74
+ relation
75
+ end
76
+ end
77
+
78
+ def attributes
79
+ attrs = {}
80
+ self.class.attribute_names.each { |name|
81
+ attrs[name] = read_attribute(name)
82
+ }
83
+ attrs
84
+ end
85
+
86
+ def initialize(attributes = nil)
87
+ @attributes = attributes_from_model_attributes
88
+ self.attributes = attributes unless attributes.nil?
89
+ @persisted = false
90
+ @readonly = true
91
+ @destroyed = false
92
+ result = yield self if block_given?
93
+ _run_initialize_callbacks
94
+ result
95
+ end
96
+
97
+ def instantiate(record)
98
+ @attributes = record.stringify_keys
99
+ @readonly = @destroyed = false
100
+ @persisted = true
101
+ self
102
+ end
103
+ end
104
+ end
105
+
106
+ require 'ninja_model/core_ext/symbol'
@@ -0,0 +1,14 @@
1
+ require 'active_support'
2
+ require 'active_model/callbacks'
3
+
4
+ module NinjaModel
5
+ module Callbacks
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ extend ActiveModel::Callbacks
10
+ define_model_callbacks :initialize, :find, :touch, :only => :after
11
+ define_model_callbacks :save, :create, :update, :destroy, :validation
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module NinjaModel
2
+
3
+ mattr_accessor :configuration
4
+
5
+ class Configuration
6
+ attr_accessor :config_file_path, :adapter_path, :specs
7
+
8
+ def self.create
9
+ NinjaModel.configuration ||= new
10
+ end
11
+
12
+ private
13
+
14
+ def initialize
15
+ @config_file_path = 'config/ninja_model.yml'
16
+ @adapter_path = 'ninja_model/adapters'
17
+ @specs = {}
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ class Symbol
2
+ NinjaModel::Predicate::PREDICATES.each do |predicate|
3
+ define_method(predicate) do |*args|
4
+ NinjaModel::Predicate.new(self, predicate, *args)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module NinjaModel
2
+ class NinjaModelError < StandardError; end
3
+ class AdapterNotSpecified < NinjaModelError; end
4
+ class ConnectionTimeoutError < NinjaModelError; end
5
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support'
2
+
3
+ module NinjaModel
4
+ module Identity
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::AttributeMethods
7
+
8
+ included do
9
+ class_inheritable_accessor :primary_key
10
+ self.primary_key = :id
11
+ undef_method(:id) if method_defined?(:id)
12
+ end
13
+
14
+ def to_param
15
+ send(self.class.primary_key).to_s if persisted?
16
+ end
17
+
18
+ def to_key
19
+ key = nil
20
+ key = send(self.class.primary_key) if persisted?
21
+ [key] if key
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module NinjaModel
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def xml(event)
4
+ return unless logger.debug?
5
+
6
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
7
+ xml = event.payload[:xml]
8
+
9
+ debug " #{name} #{xml}"
10
+ end
11
+
12
+ def logger
13
+ NinjaModel::Base.logger
14
+ end
15
+ end
16
+ end
17
+
18
+ ActiveRecord::LogSubscriber.attach_to :ninja_model
@@ -0,0 +1,76 @@
1
+ require 'active_support'
2
+
3
+ module NinjaModel
4
+ module Persistence
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::AttributeMethods
7
+
8
+ module ClassMethods
9
+ def get(rel)
10
+ end
11
+
12
+ def persist_with(adapter)
13
+
14
+ end
15
+ end
16
+
17
+ included do
18
+ class_inheritable_accessor :persistence_adapter
19
+ end
20
+
21
+ def save(*)
22
+ run_callbacks :save do
23
+ create_or_update
24
+ end
25
+ end
26
+
27
+ def create_or_update
28
+ if new_record?
29
+ create
30
+ else
31
+ update
32
+ end
33
+ end
34
+
35
+ def create
36
+ run_callbacks :create do
37
+ if self.class.adapter.create(self)
38
+ @persisted = true
39
+ end
40
+ @persisted
41
+ end
42
+ end
43
+
44
+ def update
45
+ self.class.adapter.update(self)
46
+ end
47
+
48
+ def new_record?
49
+ !@persisted
50
+ end
51
+
52
+ def destroyed?
53
+ @destroyed
54
+ end
55
+
56
+ def persisted?
57
+ @persisted && !destroyed?
58
+ end
59
+
60
+ def destroy
61
+ if self.class.adapter.destroy(self)
62
+ @destroyed = true
63
+ end
64
+ @destroyed
65
+ end
66
+
67
+ def reload
68
+ self.class.adapter.reload(self)
69
+ end
70
+
71
+ def update_attributes(attributes)
72
+ self.attributes = attributes
73
+ save
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,43 @@
1
+ module NinjaModel
2
+ class Predicate
3
+
4
+ PREDICATES = [:eq, :ne, :gt, :gte, :lt, :lte, :in]
5
+
6
+ attr_reader :attribute, :method, :value
7
+
8
+ def initialize(attribute, method, *args)
9
+ @attribute = attribute
10
+ @method = method
11
+ @valued = !args.blank?
12
+ @value = args.blank? ? nil : args.first
13
+ end
14
+
15
+ def value=(value)
16
+ @value = value
17
+ @valued = true
18
+ end
19
+
20
+ def has_value?
21
+ @valued
22
+ end
23
+
24
+ def test(suspect)
25
+ case method
26
+ when :eq
27
+ suspect.eql?(value)
28
+ when :ne
29
+ !suspect.eql?(value)
30
+ when :gt
31
+ suspect > value
32
+ when :gte
33
+ suspect >= value
34
+ when :lt
35
+ suspect < value
36
+ when :lte
37
+ suspect <= value
38
+ when :in
39
+ value.include?(suspect)
40
+ end
41
+ end
42
+ end
43
+ end