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.
- checksums.yaml +15 -0
- data/.gitignore +35 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +70 -0
- data/Rakefile +4 -0
- data/ardm.gemspec +29 -0
- data/db/.gitignore +1 -0
- data/lib/ardm/active_record/associations.rb +80 -0
- data/lib/ardm/active_record/base.rb +49 -0
- data/lib/ardm/active_record/dirty.rb +25 -0
- data/lib/ardm/active_record/hooks.rb +31 -0
- data/lib/ardm/active_record/inheritance.rb +37 -0
- data/lib/ardm/active_record/is/state_machine.rb +21 -0
- data/lib/ardm/active_record/is.rb +22 -0
- data/lib/ardm/active_record/not_found.rb +7 -0
- data/lib/ardm/active_record/predicate_builder/array_handler.rb +31 -0
- data/lib/ardm/active_record/predicate_builder/rails3.rb +147 -0
- data/lib/ardm/active_record/predicate_builder/rails4.rb +139 -0
- data/lib/ardm/active_record/predicate_builder/relation_handler.rb +15 -0
- data/lib/ardm/active_record/predicate_builder.rb +19 -0
- data/lib/ardm/active_record/property.rb +357 -0
- data/lib/ardm/active_record/query.rb +108 -0
- data/lib/ardm/active_record/record.rb +70 -0
- data/lib/ardm/active_record/relation.rb +83 -0
- data/lib/ardm/active_record/repository.rb +38 -0
- data/lib/ardm/active_record/serialization.rb +164 -0
- data/lib/ardm/active_record/storage_names.rb +28 -0
- data/lib/ardm/active_record/validations.rb +111 -0
- data/lib/ardm/active_record.rb +43 -0
- data/lib/ardm/data_mapper/not_found.rb +5 -0
- data/lib/ardm/data_mapper/record.rb +41 -0
- data/lib/ardm/data_mapper.rb +5 -0
- data/lib/ardm/env.rb +5 -0
- data/lib/ardm/property/api_key.rb +30 -0
- data/lib/ardm/property/bcrypt_hash.rb +31 -0
- data/lib/ardm/property/binary.rb +23 -0
- data/lib/ardm/property/boolean.rb +29 -0
- data/lib/ardm/property/class.rb +19 -0
- data/lib/ardm/property/comma_separated_list.rb +28 -0
- data/lib/ardm/property/csv.rb +35 -0
- data/lib/ardm/property/date.rb +12 -0
- data/lib/ardm/property/datetime.rb +12 -0
- data/lib/ardm/property/decimal.rb +38 -0
- data/lib/ardm/property/discriminator.rb +65 -0
- data/lib/ardm/property/enum.rb +51 -0
- data/lib/ardm/property/epoch_time.rb +38 -0
- data/lib/ardm/property/file_path.rb +25 -0
- data/lib/ardm/property/flag.rb +65 -0
- data/lib/ardm/property/float.rb +18 -0
- data/lib/ardm/property/integer.rb +24 -0
- data/lib/ardm/property/invalid_value_error.rb +22 -0
- data/lib/ardm/property/ip_address.rb +35 -0
- data/lib/ardm/property/json.rb +49 -0
- data/lib/ardm/property/lookup.rb +29 -0
- data/lib/ardm/property/numeric.rb +40 -0
- data/lib/ardm/property/object.rb +36 -0
- data/lib/ardm/property/paranoid_boolean.rb +18 -0
- data/lib/ardm/property/paranoid_datetime.rb +17 -0
- data/lib/ardm/property/regexp.rb +22 -0
- data/lib/ardm/property/serial.rb +16 -0
- data/lib/ardm/property/slug.rb +29 -0
- data/lib/ardm/property/string.rb +40 -0
- data/lib/ardm/property/support/dirty_minder.rb +169 -0
- data/lib/ardm/property/support/flags.rb +41 -0
- data/lib/ardm/property/support/paranoid_base.rb +78 -0
- data/lib/ardm/property/text.rb +11 -0
- data/lib/ardm/property/time.rb +12 -0
- data/lib/ardm/property/uri.rb +27 -0
- data/lib/ardm/property/uuid.rb +65 -0
- data/lib/ardm/property/validation.rb +208 -0
- data/lib/ardm/property/yaml.rb +38 -0
- data/lib/ardm/property.rb +891 -0
- data/lib/ardm/property_set.rb +152 -0
- data/lib/ardm/query/expression.rb +85 -0
- data/lib/ardm/query/ext/symbol.rb +37 -0
- data/lib/ardm/query/operator.rb +64 -0
- data/lib/ardm/record.rb +1 -0
- data/lib/ardm/support/assertions.rb +8 -0
- data/lib/ardm/support/deprecate.rb +12 -0
- data/lib/ardm/support/descendant_set.rb +89 -0
- data/lib/ardm/support/equalizer.rb +48 -0
- data/lib/ardm/support/ext/array.rb +22 -0
- data/lib/ardm/support/ext/blank.rb +25 -0
- data/lib/ardm/support/ext/hash.rb +67 -0
- data/lib/ardm/support/ext/module.rb +47 -0
- data/lib/ardm/support/ext/object.rb +57 -0
- data/lib/ardm/support/ext/string.rb +24 -0
- data/lib/ardm/support/ext/try_dup.rb +12 -0
- data/lib/ardm/support/hook.rb +405 -0
- data/lib/ardm/support/lazy_array.rb +451 -0
- data/lib/ardm/support/local_object_space.rb +13 -0
- data/lib/ardm/support/logger.rb +201 -0
- data/lib/ardm/support/mash.rb +176 -0
- data/lib/ardm/support/naming_conventions.rb +90 -0
- data/lib/ardm/support/ordered_set.rb +380 -0
- data/lib/ardm/support/subject.rb +33 -0
- data/lib/ardm/support/subject_set.rb +250 -0
- data/lib/ardm/version.rb +3 -0
- data/lib/ardm.rb +56 -0
- data/spec/fixtures/api_user.rb +11 -0
- data/spec/fixtures/article.rb +22 -0
- data/spec/fixtures/bookmark.rb +14 -0
- data/spec/fixtures/invention.rb +5 -0
- data/spec/fixtures/network_node.rb +23 -0
- data/spec/fixtures/person.rb +17 -0
- data/spec/fixtures/software_package.rb +22 -0
- data/spec/fixtures/ticket.rb +12 -0
- data/spec/fixtures/tshirt.rb +15 -0
- data/spec/integration/api_key_spec.rb +25 -0
- data/spec/integration/bcrypt_hash_spec.rb +45 -0
- data/spec/integration/comma_separated_list_spec.rb +85 -0
- data/spec/integration/dirty_minder_spec.rb +197 -0
- data/spec/integration/enum_spec.rb +79 -0
- data/spec/integration/epoch_time_spec.rb +59 -0
- data/spec/integration/file_path_spec.rb +158 -0
- data/spec/integration/flag_spec.rb +72 -0
- data/spec/integration/ip_address_spec.rb +151 -0
- data/spec/integration/json_spec.rb +70 -0
- data/spec/integration/slug_spec.rb +65 -0
- data/spec/integration/uri_spec.rb +136 -0
- data/spec/integration/uuid_spec.rb +102 -0
- data/spec/integration/yaml_spec.rb +85 -0
- data/spec/public/property/binary_spec.rb +41 -0
- data/spec/public/property/boolean_spec.rb +30 -0
- data/spec/public/property/class_spec.rb +28 -0
- data/spec/public/property/date_spec.rb +22 -0
- data/spec/public/property/date_time_spec.rb +22 -0
- data/spec/public/property/decimal_spec.rb +23 -0
- data/spec/public/property/discriminator_spec.rb +133 -0
- data/spec/public/property/float_spec.rb +22 -0
- data/spec/public/property/integer_spec.rb +22 -0
- data/spec/public/property/object_spec.rb +103 -0
- data/spec/public/property/serial_spec.rb +22 -0
- data/spec/public/property/string_spec.rb +22 -0
- data/spec/public/property/text_spec.rb +23 -0
- data/spec/public/property/time_spec.rb +22 -0
- data/spec/public/property_spec.rb +316 -0
- data/spec/rcov.opts +6 -0
- data/spec/schema.rb +86 -0
- data/spec/semipublic/property/binary_spec.rb +14 -0
- data/spec/semipublic/property/boolean_spec.rb +48 -0
- data/spec/semipublic/property/class_spec.rb +36 -0
- data/spec/semipublic/property/date_spec.rb +44 -0
- data/spec/semipublic/property/date_time_spec.rb +47 -0
- data/spec/semipublic/property/decimal_spec.rb +83 -0
- data/spec/semipublic/property/discriminator_spec.rb +22 -0
- data/spec/semipublic/property/float_spec.rb +83 -0
- data/spec/semipublic/property/integer_spec.rb +83 -0
- data/spec/semipublic/property/lookup_spec.rb +27 -0
- data/spec/semipublic/property/serial_spec.rb +14 -0
- data/spec/semipublic/property/string_spec.rb +14 -0
- data/spec/semipublic/property/text_spec.rb +30 -0
- data/spec/semipublic/property/time_spec.rb +49 -0
- data/spec/semipublic/property_spec.rb +51 -0
- data/spec/shared/flags_shared_spec.rb +36 -0
- data/spec/shared/identity_function_group.rb +5 -0
- data/spec/shared/public_property_spec.rb +229 -0
- data/spec/shared/semipublic_property_spec.rb +159 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/unit/bcrypt_hash_spec.rb +154 -0
- data/spec/unit/csv_spec.rb +139 -0
- data/spec/unit/dirty_minder_spec.rb +64 -0
- data/spec/unit/enum_spec.rb +125 -0
- data/spec/unit/epoch_time_spec.rb +72 -0
- data/spec/unit/file_path_spec.rb +75 -0
- data/spec/unit/flag_spec.rb +114 -0
- data/spec/unit/ip_address_spec.rb +109 -0
- data/spec/unit/json_spec.rb +127 -0
- data/spec/unit/paranoid_boolean_spec.rb +142 -0
- data/spec/unit/paranoid_datetime_spec.rb +149 -0
- data/spec/unit/regexp_spec.rb +62 -0
- data/spec/unit/uri_spec.rb +64 -0
- data/spec/unit/yaml_spec.rb +111 -0
- data/tasks/spec.rake +40 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- 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
|