ardm 0.0.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 (179) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +35 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +21 -0
  5. data/README.md +70 -0
  6. data/Rakefile +4 -0
  7. data/ardm.gemspec +29 -0
  8. data/db/.gitignore +1 -0
  9. data/lib/ardm/active_record/associations.rb +80 -0
  10. data/lib/ardm/active_record/base.rb +49 -0
  11. data/lib/ardm/active_record/dirty.rb +25 -0
  12. data/lib/ardm/active_record/hooks.rb +31 -0
  13. data/lib/ardm/active_record/inheritance.rb +37 -0
  14. data/lib/ardm/active_record/is/state_machine.rb +21 -0
  15. data/lib/ardm/active_record/is.rb +22 -0
  16. data/lib/ardm/active_record/not_found.rb +7 -0
  17. data/lib/ardm/active_record/predicate_builder/array_handler.rb +31 -0
  18. data/lib/ardm/active_record/predicate_builder/rails3.rb +147 -0
  19. data/lib/ardm/active_record/predicate_builder/rails4.rb +139 -0
  20. data/lib/ardm/active_record/predicate_builder/relation_handler.rb +15 -0
  21. data/lib/ardm/active_record/predicate_builder.rb +19 -0
  22. data/lib/ardm/active_record/property.rb +357 -0
  23. data/lib/ardm/active_record/query.rb +108 -0
  24. data/lib/ardm/active_record/record.rb +70 -0
  25. data/lib/ardm/active_record/relation.rb +83 -0
  26. data/lib/ardm/active_record/repository.rb +38 -0
  27. data/lib/ardm/active_record/serialization.rb +164 -0
  28. data/lib/ardm/active_record/storage_names.rb +28 -0
  29. data/lib/ardm/active_record/validations.rb +111 -0
  30. data/lib/ardm/active_record.rb +43 -0
  31. data/lib/ardm/data_mapper/not_found.rb +5 -0
  32. data/lib/ardm/data_mapper/record.rb +41 -0
  33. data/lib/ardm/data_mapper.rb +5 -0
  34. data/lib/ardm/env.rb +5 -0
  35. data/lib/ardm/property/api_key.rb +30 -0
  36. data/lib/ardm/property/bcrypt_hash.rb +31 -0
  37. data/lib/ardm/property/binary.rb +23 -0
  38. data/lib/ardm/property/boolean.rb +29 -0
  39. data/lib/ardm/property/class.rb +19 -0
  40. data/lib/ardm/property/comma_separated_list.rb +28 -0
  41. data/lib/ardm/property/csv.rb +35 -0
  42. data/lib/ardm/property/date.rb +12 -0
  43. data/lib/ardm/property/datetime.rb +12 -0
  44. data/lib/ardm/property/decimal.rb +38 -0
  45. data/lib/ardm/property/discriminator.rb +65 -0
  46. data/lib/ardm/property/enum.rb +51 -0
  47. data/lib/ardm/property/epoch_time.rb +38 -0
  48. data/lib/ardm/property/file_path.rb +25 -0
  49. data/lib/ardm/property/flag.rb +65 -0
  50. data/lib/ardm/property/float.rb +18 -0
  51. data/lib/ardm/property/integer.rb +24 -0
  52. data/lib/ardm/property/invalid_value_error.rb +22 -0
  53. data/lib/ardm/property/ip_address.rb +35 -0
  54. data/lib/ardm/property/json.rb +49 -0
  55. data/lib/ardm/property/lookup.rb +29 -0
  56. data/lib/ardm/property/numeric.rb +40 -0
  57. data/lib/ardm/property/object.rb +36 -0
  58. data/lib/ardm/property/paranoid_boolean.rb +18 -0
  59. data/lib/ardm/property/paranoid_datetime.rb +17 -0
  60. data/lib/ardm/property/regexp.rb +22 -0
  61. data/lib/ardm/property/serial.rb +16 -0
  62. data/lib/ardm/property/slug.rb +29 -0
  63. data/lib/ardm/property/string.rb +40 -0
  64. data/lib/ardm/property/support/dirty_minder.rb +169 -0
  65. data/lib/ardm/property/support/flags.rb +41 -0
  66. data/lib/ardm/property/support/paranoid_base.rb +78 -0
  67. data/lib/ardm/property/text.rb +11 -0
  68. data/lib/ardm/property/time.rb +12 -0
  69. data/lib/ardm/property/uri.rb +27 -0
  70. data/lib/ardm/property/uuid.rb +65 -0
  71. data/lib/ardm/property/validation.rb +208 -0
  72. data/lib/ardm/property/yaml.rb +38 -0
  73. data/lib/ardm/property.rb +891 -0
  74. data/lib/ardm/property_set.rb +152 -0
  75. data/lib/ardm/query/expression.rb +85 -0
  76. data/lib/ardm/query/ext/symbol.rb +37 -0
  77. data/lib/ardm/query/operator.rb +64 -0
  78. data/lib/ardm/record.rb +1 -0
  79. data/lib/ardm/support/assertions.rb +8 -0
  80. data/lib/ardm/support/deprecate.rb +12 -0
  81. data/lib/ardm/support/descendant_set.rb +89 -0
  82. data/lib/ardm/support/equalizer.rb +48 -0
  83. data/lib/ardm/support/ext/array.rb +22 -0
  84. data/lib/ardm/support/ext/blank.rb +25 -0
  85. data/lib/ardm/support/ext/hash.rb +67 -0
  86. data/lib/ardm/support/ext/module.rb +47 -0
  87. data/lib/ardm/support/ext/object.rb +57 -0
  88. data/lib/ardm/support/ext/string.rb +24 -0
  89. data/lib/ardm/support/ext/try_dup.rb +12 -0
  90. data/lib/ardm/support/hook.rb +405 -0
  91. data/lib/ardm/support/lazy_array.rb +451 -0
  92. data/lib/ardm/support/local_object_space.rb +13 -0
  93. data/lib/ardm/support/logger.rb +201 -0
  94. data/lib/ardm/support/mash.rb +176 -0
  95. data/lib/ardm/support/naming_conventions.rb +90 -0
  96. data/lib/ardm/support/ordered_set.rb +380 -0
  97. data/lib/ardm/support/subject.rb +33 -0
  98. data/lib/ardm/support/subject_set.rb +250 -0
  99. data/lib/ardm/version.rb +3 -0
  100. data/lib/ardm.rb +56 -0
  101. data/spec/fixtures/api_user.rb +11 -0
  102. data/spec/fixtures/article.rb +22 -0
  103. data/spec/fixtures/bookmark.rb +14 -0
  104. data/spec/fixtures/invention.rb +5 -0
  105. data/spec/fixtures/network_node.rb +23 -0
  106. data/spec/fixtures/person.rb +17 -0
  107. data/spec/fixtures/software_package.rb +22 -0
  108. data/spec/fixtures/ticket.rb +12 -0
  109. data/spec/fixtures/tshirt.rb +15 -0
  110. data/spec/integration/api_key_spec.rb +25 -0
  111. data/spec/integration/bcrypt_hash_spec.rb +45 -0
  112. data/spec/integration/comma_separated_list_spec.rb +85 -0
  113. data/spec/integration/dirty_minder_spec.rb +197 -0
  114. data/spec/integration/enum_spec.rb +79 -0
  115. data/spec/integration/epoch_time_spec.rb +59 -0
  116. data/spec/integration/file_path_spec.rb +158 -0
  117. data/spec/integration/flag_spec.rb +72 -0
  118. data/spec/integration/ip_address_spec.rb +151 -0
  119. data/spec/integration/json_spec.rb +70 -0
  120. data/spec/integration/slug_spec.rb +65 -0
  121. data/spec/integration/uri_spec.rb +136 -0
  122. data/spec/integration/uuid_spec.rb +102 -0
  123. data/spec/integration/yaml_spec.rb +85 -0
  124. data/spec/public/property/binary_spec.rb +41 -0
  125. data/spec/public/property/boolean_spec.rb +30 -0
  126. data/spec/public/property/class_spec.rb +28 -0
  127. data/spec/public/property/date_spec.rb +22 -0
  128. data/spec/public/property/date_time_spec.rb +22 -0
  129. data/spec/public/property/decimal_spec.rb +23 -0
  130. data/spec/public/property/discriminator_spec.rb +133 -0
  131. data/spec/public/property/float_spec.rb +22 -0
  132. data/spec/public/property/integer_spec.rb +22 -0
  133. data/spec/public/property/object_spec.rb +103 -0
  134. data/spec/public/property/serial_spec.rb +22 -0
  135. data/spec/public/property/string_spec.rb +22 -0
  136. data/spec/public/property/text_spec.rb +23 -0
  137. data/spec/public/property/time_spec.rb +22 -0
  138. data/spec/public/property_spec.rb +316 -0
  139. data/spec/rcov.opts +6 -0
  140. data/spec/schema.rb +86 -0
  141. data/spec/semipublic/property/binary_spec.rb +14 -0
  142. data/spec/semipublic/property/boolean_spec.rb +48 -0
  143. data/spec/semipublic/property/class_spec.rb +36 -0
  144. data/spec/semipublic/property/date_spec.rb +44 -0
  145. data/spec/semipublic/property/date_time_spec.rb +47 -0
  146. data/spec/semipublic/property/decimal_spec.rb +83 -0
  147. data/spec/semipublic/property/discriminator_spec.rb +22 -0
  148. data/spec/semipublic/property/float_spec.rb +83 -0
  149. data/spec/semipublic/property/integer_spec.rb +83 -0
  150. data/spec/semipublic/property/lookup_spec.rb +27 -0
  151. data/spec/semipublic/property/serial_spec.rb +14 -0
  152. data/spec/semipublic/property/string_spec.rb +14 -0
  153. data/spec/semipublic/property/text_spec.rb +30 -0
  154. data/spec/semipublic/property/time_spec.rb +49 -0
  155. data/spec/semipublic/property_spec.rb +51 -0
  156. data/spec/shared/flags_shared_spec.rb +36 -0
  157. data/spec/shared/identity_function_group.rb +5 -0
  158. data/spec/shared/public_property_spec.rb +229 -0
  159. data/spec/shared/semipublic_property_spec.rb +159 -0
  160. data/spec/spec.opts +4 -0
  161. data/spec/spec_helper.rb +58 -0
  162. data/spec/unit/bcrypt_hash_spec.rb +154 -0
  163. data/spec/unit/csv_spec.rb +139 -0
  164. data/spec/unit/dirty_minder_spec.rb +64 -0
  165. data/spec/unit/enum_spec.rb +125 -0
  166. data/spec/unit/epoch_time_spec.rb +72 -0
  167. data/spec/unit/file_path_spec.rb +75 -0
  168. data/spec/unit/flag_spec.rb +114 -0
  169. data/spec/unit/ip_address_spec.rb +109 -0
  170. data/spec/unit/json_spec.rb +127 -0
  171. data/spec/unit/paranoid_boolean_spec.rb +142 -0
  172. data/spec/unit/paranoid_datetime_spec.rb +149 -0
  173. data/spec/unit/regexp_spec.rb +62 -0
  174. data/spec/unit/uri_spec.rb +64 -0
  175. data/spec/unit/yaml_spec.rb +111 -0
  176. data/tasks/spec.rake +40 -0
  177. data/tasks/yard.rake +9 -0
  178. data/tasks/yardstick.rake +19 -0
  179. metadata +350 -0
@@ -0,0 +1,139 @@
1
+ require 'active_support/concern'
2
+
3
+ module Ardm
4
+ module ActiveRecord
5
+ module PredicateBuilder
6
+ module Rails4
7
+ extend ActiveSupport::Concern
8
+
9
+ Base = ::ActiveRecord::Base
10
+ Relation = ::ActiveRecord::Relation
11
+
12
+ included do
13
+ class << self
14
+ alias_method :original_build_from_hash, :build_from_hash
15
+ end
16
+
17
+ class_attribute :handlers
18
+ self.handlers = []
19
+
20
+ register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
21
+ register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
22
+ register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
23
+ register_handler(Range, ->(attribute, value) { attribute.in(value) })
24
+ register_handler(Relation, RelationHandler.new)
25
+ register_handler(Array, ArrayHandler.new)
26
+ end
27
+
28
+ module ClassMethods
29
+ def resolve_column_aliases(klass, hash)
30
+ hash = hash.dup
31
+ hash.keys.grep(Symbol) do |key|
32
+ if klass.attribute_alias? key
33
+ hash[klass.attribute_alias(key)] = hash.delete key
34
+ end
35
+ end
36
+ hash
37
+ end
38
+
39
+ def build_from_hash(klass, attributes, default_table)
40
+ queries = []
41
+
42
+ #
43
+ # attributes {
44
+ # Ardm::Query::Operator(target: :attr, operator: :not) =>
45
+ attributes.each do |column, value|
46
+ table = default_table
47
+
48
+ if value.is_a?(Hash)
49
+ if value.empty?
50
+ queries << '1=0'
51
+ else
52
+ table = Arel::Table.new(column, default_table.engine)
53
+ association = klass.reflect_on_association(column.to_sym)
54
+
55
+ value.each do |k, v|
56
+ queries.concat expand(association && association.klass, table, k, v)
57
+ end
58
+ end
59
+ else
60
+ if Ardm::Query::Operator === column
61
+ column = column.target.to_s
62
+ operator = column.operator
63
+ else
64
+ column = column.to_s
65
+ operator = nil
66
+ end
67
+
68
+ if column.include?('.')
69
+ table_name, column = column.split('.', 2)
70
+ table = Arel::Table.new(table_name, default_table.engine)
71
+ end
72
+
73
+ queries.concat expand(klass, table, column, value)
74
+ end
75
+ end
76
+
77
+ queries
78
+ end
79
+
80
+ def expand(klass, table, column, value)
81
+ queries = []
82
+
83
+ # Find the foreign key when using queries such as:
84
+ # Post.where(author: author)
85
+ #
86
+ # For polymorphic relationships, find the foreign key and type:
87
+ # PriceEstimate.where(estimate_of: treasure)
88
+ if klass && value.is_a?(Base) && reflection = klass.reflect_on_association(column.to_sym)
89
+ if reflection.polymorphic?
90
+ queries << build(table[reflection.foreign_type], value.class.base_class)
91
+ end
92
+
93
+ column = reflection.foreign_key
94
+ end
95
+
96
+ queries << build(table[column], value)
97
+ queries
98
+ end
99
+
100
+ def references(attributes)
101
+ attributes.map do |key, value|
102
+ if value.is_a?(Hash)
103
+ key
104
+ else
105
+ key = key.to_s
106
+ key.split('.').first if key.include?('.')
107
+ end
108
+ end.compact
109
+ end
110
+
111
+ # Define how a class is converted to Arel nodes when passed to +where+.
112
+ # The handler can be any object that responds to +call+, and will be used
113
+ # for any value that +===+ the class given. For example:
114
+ #
115
+ # MyCustomDateRange = Struct.new(:start, :end)
116
+ # handler = proc do |column, range|
117
+ # Arel::Nodes::Between.new(column,
118
+ # Arel::Nodes::And.new([range.start, range.end])
119
+ # )
120
+ # end
121
+ # ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
122
+ def register_handler(klass, handler)
123
+ handlers.unshift([klass, handler])
124
+ end
125
+
126
+ private
127
+
128
+ def build(attribute, value)
129
+ handler_for(value).call(attribute, value)
130
+ end
131
+
132
+ def handler_for(object)
133
+ handlers.detect { |klass, _| klass === object }.last
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,15 @@
1
+ module Ardm
2
+ module ActiveRecord
3
+ module PredicateBuilder
4
+ class RelationHandler # :nodoc:
5
+ def call(attribute, value)
6
+ if value.select_values.empty?
7
+ value = value.select(value.klass.arel_table[value.klass.primary_key])
8
+ end
9
+
10
+ attribute.in(value.arel.ast)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_record/relation/predicate_builder' # force it to load
2
+
3
+ require 'ardm/active_record/predicate_builder/relation_handler'
4
+ require 'ardm/active_record/predicate_builder/array_handler'
5
+
6
+ if ::ActiveRecord::PredicateBuilder.respond_to? :expand
7
+ require 'ardm/active_record/predicate_builder/rails4'
8
+ ::ActiveRecord::PredicateBuilder.send(:include, Ardm::ActiveRecord::PredicateBuilder::Rails4)
9
+ else
10
+ require 'ardm/active_record/predicate_builder/rails3'
11
+ ::ActiveRecord::PredicateBuilder.send(:include, Ardm::ActiveRecord::PredicateBuilder::Rails3)
12
+ end
13
+
14
+ ::ActiveRecord::PredicateBuilder.class_eval do
15
+ # calls super instead of calling the method on the class
16
+ class << self
17
+ remove_method :build_from_hash
18
+ end
19
+ end
@@ -0,0 +1,357 @@
1
+ require 'active_support/concern'
2
+ require 'ardm/property/lookup'
3
+
4
+ module Ardm
5
+ module ActiveRecord
6
+ module Property
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ extend Ardm::Property::Lookup
11
+ extend Ardm::ActiveRecord::Property::ClassMethods
12
+
13
+ instance_variable_set(:@properties, Ardm::PropertySet.new)
14
+ instance_variable_set(:@field_naming_convention, nil)
15
+ send :before_validation, :initialize_ardm_property_defaults
16
+ end
17
+
18
+ def self.extended(model)
19
+ raise "Please include #{self} instead of extend."
20
+ end
21
+
22
+ module ClassMethods
23
+
24
+ def inherited(model)
25
+ model.instance_variable_set(:@properties, Ardm::PropertySet.new)
26
+ model.instance_variable_set(:@field_naming_convention, @field_naming_convention)
27
+
28
+ model_properties = model.properties
29
+ @properties.each { |property| model_properties << property }
30
+
31
+ super
32
+ end
33
+
34
+ # Defines a Property on the Resource
35
+ #
36
+ # @param [Symbol] name
37
+ # the name for which to call this property
38
+ # @param [Class] type
39
+ # the ruby type to define this property as
40
+ # @param [Hash(Symbol => String)] options
41
+ # a hash of available options
42
+ #
43
+ # @return [Property]
44
+ # the created Property
45
+ #
46
+ # @see Property
47
+ #
48
+ # @api public
49
+ def property(name, type, options = {})
50
+ # if the type can be found within Property then
51
+ # use that class rather than the primitive
52
+ klass = Ardm::Property.determine_class(type)
53
+
54
+ unless klass
55
+ raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type"
56
+ end
57
+
58
+ property = klass.new(self, name, options)
59
+
60
+ self.properties << property
61
+
62
+ # add the property to the child classes only if the property was
63
+ # added after the child classes' properties have been copied from
64
+ # the parent
65
+ descendants.each do |descendant|
66
+ descendant.properties << property
67
+ end
68
+
69
+ serialize(property.field, property)
70
+
71
+ set_primary_key_for(property)
72
+ create_reader_for(property)
73
+ create_writer_for(property)
74
+ add_validations_for(property)
75
+
76
+ # FIXME: explicit return needed for YARD to parse this properly
77
+ return property
78
+ end
79
+
80
+ # Gets a list of all properties that have been defined on this Model
81
+ #
82
+ # @return [PropertySet]
83
+ # A list of Properties defined on this Model in the given Repository
84
+ #
85
+ # @api public
86
+ def properties
87
+ @properties ||= PropertySet.new
88
+ end
89
+
90
+ def initialize_attributes(attributes, options = {})
91
+ super(attributes, options)
92
+
93
+ properties.each do |property|
94
+ if attributes.key?(property.name)
95
+ attributes[property.field] = attributes[property.name]
96
+ end
97
+ end
98
+
99
+ attributes
100
+ end
101
+
102
+ def columns
103
+ @columns ||= properties.map do |property|
104
+ sql_type = connection.type_to_sql(
105
+ property.dump_as.name.to_sym,
106
+ property.options[:limit],
107
+ property.options[:precision],
108
+ property.options[:scale]
109
+ )
110
+
111
+ column = ::ActiveRecord::ConnectionAdapters::Column.new(
112
+ #property.name.to_s,
113
+ property.field.to_s,
114
+ nil,#property.dump(property.default),
115
+ sql_type,
116
+ property.allow_nil?
117
+ )
118
+
119
+ column.primary = property.key?
120
+ column
121
+ end
122
+ end
123
+
124
+ # Hook into the query system when we would be finding composed_of
125
+ # fields in active record. This lets us mangle the query as needed.
126
+ #
127
+ # Every DM property needs to be dumped when it's being sent to a query.
128
+ # This also gives us a chance to handle aliased fields
129
+ def expand_hash_conditions_for_aggregates(*args)
130
+ dump_properties_hash(super)
131
+ end
132
+
133
+ def dump_properties_hash(options)
134
+ options.inject({}) do |new_attrs, (key, value)|
135
+ if property = properties[key]
136
+ new_attrs[property.field] = property.dump(value)
137
+ else
138
+ new_attrs[key] = value
139
+ end
140
+ new_attrs
141
+ end
142
+ end
143
+
144
+ # Gets the list of key fields for this Model
145
+ #
146
+ # @return [Array]
147
+ # The list of key fields for this Model
148
+ #
149
+ # @api public
150
+ def key
151
+ properties.key
152
+ end
153
+
154
+ # @api public
155
+ def serial
156
+ key.detect { |property| property.serial? }
157
+ end
158
+
159
+ # Gets the field naming conventions for this resource in the given Repository
160
+ #
161
+ # @return [#call]
162
+ # The naming convention for the given Repository
163
+ #
164
+ # @api public
165
+ def field_naming_convention
166
+ @field_naming_convention ||= lambda { |property| property.name.to_s.underscore }
167
+ end
168
+
169
+ # @api private
170
+ def properties_with_subclasses
171
+ props = properties.dup
172
+
173
+ descendants.each do |model|
174
+ model.properties.each do |property|
175
+ props << property
176
+ end
177
+ end
178
+
179
+ props
180
+ end
181
+
182
+ # @api private
183
+ def key_conditions(key)
184
+ Hash[ self.key.zip(key.nil? ? [] : key) ]
185
+ end
186
+
187
+ private
188
+
189
+ # Defines the anonymous module that is used to add properties.
190
+ # Using a single module here prevents having a very large number
191
+ # of anonymous modules, where each property has their own module.
192
+ # @api private
193
+ def property_module
194
+ @property_module ||= begin
195
+ mod = Module.new
196
+ class_eval do
197
+ include mod
198
+ end
199
+ mod
200
+ end
201
+ end
202
+
203
+ def set_primary_key_for(property)
204
+ if property.key? || property.serial?
205
+ self.primary_key = property.name
206
+ end
207
+ end
208
+
209
+ # defines the reader method for the property
210
+ #
211
+ # @api private
212
+ def create_reader_for(property)
213
+ return if property.key? || property.serial? # let AR do it
214
+ name = property.name.to_s
215
+ reader_visibility = property.reader_visibility
216
+ instance_variable_name = property.instance_variable_name
217
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
218
+ #{reader_visibility}
219
+ def #{name}
220
+ attribute_get(#{name.inspect})
221
+ end
222
+ RUBY
223
+
224
+ if property.kind_of?(Ardm::Property::Boolean)
225
+ boolean_reader_name = "#{name}?"
226
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
227
+ #{reader_visibility}
228
+ def #{boolean_reader_name}
229
+ #{name}
230
+ end
231
+ RUBY
232
+ end
233
+ end
234
+
235
+ # defines the setter for the property
236
+ #
237
+ # @api private
238
+ def create_writer_for(property)
239
+ return if property.key? || property.serial? # let AR do it
240
+ name = property.name
241
+ writer_visibility = property.writer_visibility
242
+
243
+ writer_name = "#{name}="
244
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
245
+ #{writer_visibility}
246
+ def #{writer_name}(value)
247
+ attribute_set(#{name.inspect}, value)
248
+ end
249
+ RUBY
250
+ end
251
+
252
+ def add_validations_for(property)
253
+ return if property.key? || property.serial?
254
+ rules = Ardm::Property::Validation.rules_for_property(property)
255
+ rules.each do |options|
256
+ validates(property.name, options)
257
+ end
258
+ end
259
+
260
+ # @api public
261
+ # This confuses the rails
262
+ #
263
+ #def method_missing(method, *args, &block)
264
+ # if property = properties[method]
265
+ # return property
266
+ # end
267
+
268
+ # super
269
+ #end
270
+ end # module ClassMethods
271
+
272
+ # when exactly does a datamapper default property get set?
273
+ def initialize_ardm_property_defaults
274
+ return unless new_record?
275
+ self.class.properties.each do |property|
276
+ attribute_get(property.name) # assigns default on fetch
277
+ end
278
+ true
279
+ end
280
+
281
+ # This not the same as read_attribute in AR
282
+ def attribute_get(name)
283
+ property = self.class.properties[name]
284
+ val = read_attribute property.field
285
+ if new_record? && val.nil? && property.default?
286
+ write_attribute property.field, property.typecast(property.default_for(self))
287
+ end
288
+ read_attribute property.field
289
+ end
290
+
291
+ # This not the same as write_attribute in AR
292
+ def attribute_set(name, value)
293
+ property = self.class.properties[name]
294
+ write_attribute property.field, property.typecast(value)
295
+ read_attribute property.field
296
+ end
297
+
298
+ # Retrieve the key(s) for this resource.
299
+ #
300
+ # This always returns the persisted key value,
301
+ # even if the key is changed and not yet persisted.
302
+ # This is done so all relations still work.
303
+ #
304
+ # @return [Array(Key)]
305
+ # the key(s) identifying this resource
306
+ #
307
+ # @api public
308
+ def key
309
+ return @_key if defined?(@_key)
310
+
311
+ model_key = self.class.key
312
+
313
+ key = model_key.map do |property|
314
+ changed_attributes[property.name] || (property.loaded?(self) ? property.get!(self) : nil)
315
+ end
316
+
317
+ # only memoize a valid key
318
+ @_key = key if model_key.valid?(key)
319
+ end
320
+
321
+ # Gets this instance's Model's properties
322
+ #
323
+ # @return [PropertySet]
324
+ # List of this Resource's Model's properties
325
+ #
326
+ # @api private
327
+ def properties
328
+ self.class.properties
329
+ end
330
+
331
+ # Fetches all the names of the attributes that have been loaded,
332
+ # even if they are lazy but have been called
333
+ #
334
+ # @return [Array<Property>]
335
+ # names of attributes that have been loaded
336
+ #
337
+ # @api private
338
+ def fields
339
+ properties.select do |property|
340
+ property.loaded?(self) || (new_record? && property.default?)
341
+ end
342
+ end
343
+
344
+ # Reset the key to the original value
345
+ #
346
+ # @return [undefined]
347
+ #
348
+ # @api private
349
+ def reset_key
350
+ properties.key.zip(key) do |property, value|
351
+ property.set!(self, value)
352
+ end
353
+ end
354
+
355
+ end # module Property
356
+ end # module Rails
357
+ end # module Ardm
@@ -0,0 +1,108 @@
1
+ require 'active_support/concern'
2
+
3
+ require 'ardm/query/expression'
4
+ require 'ardm/query/operator'
5
+ require 'ardm/query/ext/symbol'
6
+ require 'ardm/active_record/predicate_builder'
7
+
8
+ module Ardm
9
+ module ActiveRecord
10
+ module Query
11
+ extend ActiveSupport::Concern
12
+
13
+ def self.order(model, ord)
14
+ case ord
15
+ when Array
16
+ ord.map {|o| order(model, o) }.join(", ")
17
+ when Ardm::Query::Operator
18
+ field = ord.target
19
+ if property = model.properties[field]
20
+ field = property.field
21
+ end
22
+ "#{field} #{ord.operator.upcase}"
23
+ else
24
+ ord
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ # hook into query engine in the most general way possible
30
+ def expand_hash_conditions_for_aggregates(options)
31
+ complex, simple = options.partition {|k,v| Ardm::Query::Operator === k }
32
+ result = super(Hash[simple]) # send simple all at once to save computation
33
+ complex.each do |(operator, value)|
34
+ expanded_opts = super(operator.target => value)
35
+
36
+ if expanded_opts.size > 1
37
+ $stderr.puts "WARNING: Operator #{operator.target.inspect} on multiple attribute aggregate #{expanded_opts.inspect} might be totally crazyballs."
38
+ end
39
+
40
+ expanded_opts.each do |new_key, new_val|
41
+ new_operator = Ardm::Query::Operator.new(new_key, operator.operator)
42
+ result[new_operator] = new_val
43
+ end
44
+ end
45
+
46
+ # This hack allows access to the original class from within the PredicateBuilder (so hax)
47
+ class << result
48
+ attr_accessor :klass
49
+ end
50
+ result.klass = self
51
+ result
52
+ end
53
+
54
+ def get(id)
55
+ all(id: id).first
56
+ end
57
+
58
+ def get!(id)
59
+ find(id)
60
+ end
61
+
62
+ def first_or_create(find_params)
63
+ all(find_params).first_or_create
64
+ end
65
+
66
+ def first_or_create!(find_params)
67
+ all(find_params).first_or_create!
68
+ end
69
+
70
+ def first_or_initialize(find_params)
71
+ all(find_params).first_or_initialize
72
+ end
73
+
74
+ #def first(args=nil)
75
+ # if Hash === args
76
+ # all(args).first
77
+ # else
78
+ # puts "#{self}.first(#{args.inspect})"
79
+ # puts caller[0..1]
80
+ # super(args)
81
+ # end
82
+ #end
83
+
84
+ #def all(options={})
85
+ # puts "#{self}.all(#{options.inspect})"
86
+ # puts caller[0..1]
87
+ # binding.pry if options[:account]
88
+ # Ardm::Query.all(self, options)
89
+ #end
90
+
91
+ #def exist?(options={})
92
+ # puts "#{self}.exist?(#{options.inspect})"
93
+ # puts caller[0..10]
94
+ # options.empty? ? super : all(options).exist?
95
+ #end
96
+
97
+ #def count(options={})
98
+ # puts "#{self}.count(#{options.inspect})"
99
+ # puts caller[0..10]
100
+ # $visited ||= 0
101
+ # puts $visited += 1
102
+ # raise if $visited > 10
103
+ # options.empty? ? super : all(options).count
104
+ #end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,70 @@
1
+ require 'active_record'
2
+ require 'ardm'
3
+ require 'ardm/active_record/base'
4
+
5
+ module Ardm
6
+ module ActiveRecord
7
+ class Record < ::ActiveRecord::Base
8
+ include Ardm::ActiveRecord::Base
9
+
10
+ self.abstract_class = true
11
+
12
+ JSON = Json
13
+
14
+ def self.finalize
15
+ end
16
+
17
+ def self.execute_sql(sql)
18
+ connection.execute(sql)
19
+ end
20
+
21
+ def self.property(property_name, property_type, options={})
22
+ property = super
23
+ begin
24
+ attr_accessible property.name
25
+ attr_accessible property.field
26
+ rescue => e
27
+ puts "WARNING: Error silenced. FIXME before release.\n#{e}"
28
+ end
29
+ property
30
+ end
31
+
32
+ # no-op in active record
33
+ def self.timestamps(*a)
34
+ end
35
+
36
+ def key
37
+ id
38
+ end
39
+
40
+ def new?
41
+ new_record?
42
+ end
43
+
44
+ def save_self(*)
45
+ save
46
+ end
47
+
48
+ def update(*a)
49
+ if a.size == 1
50
+ update_attributes(*a)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def update!(*a)
57
+ update_attributes!(*a)
58
+ end
59
+
60
+ #active record internals for detecting if this method should exist as an attribute
61
+ #if attribute_method_matcher(:open)
62
+ # def open
63
+ # read_attribute(:open)
64
+ # #attribute_missing(match, *args, &block)
65
+ # end
66
+ #end
67
+
68
+ end
69
+ end
70
+ end