dm-persevere-adapter 0.71.4 → 0.72.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module Aggregates
4
+ def aggregate(query)
5
+ records = []
6
+ fields = query.fields
7
+ field_size = fields.size
8
+
9
+ connect if @persevere.nil?
10
+ resources = Array.new
11
+
12
+ query = Persevere.enhance(query)
13
+
14
+ json_query, headers = query.to_json_query
15
+ path = "/#{query.model.storage_name}/#{json_query}"
16
+ DataMapper.logger.debug("--> PATH/QUERY/HEADERS: #{path} #{headers.inspect}")
17
+
18
+ response = @persevere.retrieve(path, headers)
19
+
20
+ if response.code == "200"
21
+ results = [response.body]
22
+ results.each do |row_of_results|
23
+ row = query.fields.zip([row_of_results].flatten).map do |field, value|
24
+ if field.respond_to?(:operator)
25
+ send(field.operator, field.target, value)
26
+ else
27
+ field.typecast(value)
28
+ end
29
+ end
30
+ records << (field_size > 1 ? row : row[0])
31
+ end
32
+ end
33
+ records
34
+ end # aggregate method
35
+
36
+ private
37
+
38
+ def count(property, value)
39
+ value.to_i
40
+ end
41
+
42
+ def min(property, value)
43
+ values = JSON.parse("[#{value}]").flatten.compact
44
+ if values.is_a?(Array)
45
+ values.map! { |v| property.typecast(v) }
46
+ return values.sort[0].new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24)) if property.kind_of?(DataMapper::Property::DateTime)
47
+ return values.sort[0]
48
+ end
49
+ property.typecast(value)
50
+ end
51
+
52
+ def max(property, value)
53
+ values = JSON.parse("[#{value}]").flatten.compact
54
+ if values.is_a?(Array)
55
+ values.map! { |v| property.typecast(v) }
56
+ return values.sort[-1].new_offset(Rational(Time.now.getlocal.gmt_offset/3600, 24)) if property.kind_of?(DataMapper::Property::DateTime)
57
+ return values.sort[-1]
58
+ end
59
+ property.typecast(value)
60
+ end
61
+
62
+ def avg(property, value)
63
+ values = JSON.parse(value).compact
64
+ result = values.inject(0.0){|sum,i| sum+=i }/values.length
65
+ property.kind_of?(DataMapper::Property::Integer) ? result.to_f : property.typecast(result)
66
+ end
67
+
68
+ def sum(property, value)
69
+ property.typecast(value)
70
+ end
71
+ end # module Aggregates
72
+ end # module Persevere
73
+
74
+ DataMapper::Persevere::Adapter.send(:include, DataMapper::Persevere::Aggregates)
75
+ end # module DataMapper
@@ -0,0 +1,49 @@
1
+ module DataMapper
2
+ module Persevere
3
+ def self.enhance(object)
4
+ Persevere::Proxy[object]
5
+ end
6
+
7
+ class Proxy
8
+ instance_methods.each do |m|
9
+ undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$|^extend$)/
10
+ end
11
+
12
+ def raise(*args)
13
+ ::Object.send(:raise, *args)
14
+ end
15
+
16
+ def self.[](target)
17
+ proxy = self.new(target)
18
+
19
+ case target
20
+ when DataMapper::Resource
21
+ proxy.extend(JSONSupport::Core)
22
+ proxy.extend(JSONSupport::Resource)
23
+ when DataMapper::Model
24
+ proxy.extend(JSONSupport::Core)
25
+ proxy.extend(JSONSupport::Model)
26
+ proxy.extend(JSONSupport::Model::Properties)
27
+ when DataMapper::Property
28
+ proxy.extend(JSONSupport::Core)
29
+ proxy.extend(JSONSupport::Property)
30
+ when DataMapper::Query
31
+ proxy.extend(Persevere::Query)
32
+ else
33
+ return target
34
+ end
35
+ return proxy
36
+ end
37
+
38
+ def initialize(target)
39
+
40
+ @target = target
41
+ end
42
+
43
+ def method_missing(method, *args, &block)
44
+ @target.send(method, *args, &block)
45
+ end
46
+
47
+ end # Proxy
48
+ end # Persevere
49
+ end # DataMapper
@@ -0,0 +1,6 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), 'json_support'))
2
+ require File.join(dir, 'core')
3
+ require File.join(dir, 'model')
4
+ require File.join(dir, 'model', 'properties')
5
+ require File.join(dir, 'property')
6
+ require File.join(dir, 'resource')
@@ -0,0 +1,17 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module JSONSupport
4
+ module Core
5
+ def to_json(id = nil)
6
+ to_json_hash(id).to_json
7
+ end
8
+
9
+ def to_json_hash(id = nil)
10
+ return {
11
+ 'id' => id || self.to_s
12
+ }
13
+ end
14
+ end # Core
15
+ end # JSONSupport
16
+ end # Persevere
17
+ end # DataMapper
@@ -0,0 +1,22 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module JSONSupport
4
+ module Model
5
+ def to_json_hash(repository_name = default_repository_name)
6
+ schema_hash = super
7
+ schema_hash['id'] = self.storage_name(repository_name)
8
+ schema_hash['prototype'] ||= {}
9
+ return schema_hash
10
+ end
11
+
12
+ def to_json_schema(repository_name = default_repository_name)
13
+ to_json(repository_name)
14
+ end
15
+
16
+ def to_json_schema_hash(repository_name = default_repository_name)
17
+ to_json_hash(repository_name)
18
+ end
19
+ end # JSONSchema
20
+ end # Model
21
+ end # Persevere
22
+ end # DataMapper
@@ -0,0 +1,23 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module JSONSupport
4
+ module Model
5
+ module Properties
6
+ #TODO: Add various options in.
7
+ def to_json_hash(repository_name = default_repository_name)
8
+ schema_hash = super
9
+ schema_hash['properties'] ||= {}
10
+
11
+ # Handle properties
12
+ properties.select { |prop| prop.field != 'id' }.each do |prop|
13
+ prop = Persevere.enhance(prop)
14
+ schema_hash['properties'][prop.field] = prop.to_json_hash(repository_name)
15
+ end
16
+
17
+ return schema_hash
18
+ end
19
+ end # Properties
20
+ end # Model
21
+ end # JSON
22
+ end # Persevere
23
+ end # DataMapper
@@ -0,0 +1,23 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module JSONSupport
4
+ module Property
5
+ def to_json_hash(repo)
6
+ tm = repository(repo).adapter.type_map
7
+ type_information = tm[primitive]
8
+
9
+ json_hash = Hash.new
10
+ json_hash = { "type" => type_information[:primitive] }
11
+ json_hash.merge!({ "optional" => true }) unless required?
12
+ json_hash.merge!({ "unique" => true}) if unique?
13
+ json_hash.merge!({ "position" => @position }) unless @position.nil?
14
+ json_hash.merge!({ "prefix" => @prefix }) unless @prefix.nil?
15
+ json_hash.merge!({ "separator" => @separator }) unless @separator.nil?
16
+ json_hash.merge!( type_information.reject{ |key,value| key == :primitive } )
17
+
18
+ json_hash
19
+ end
20
+ end # Property
21
+ end # JSON
22
+ end # Persevere
23
+ end # DataMapper
@@ -0,0 +1,37 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module JSONSupport
4
+ module Resource
5
+
6
+ ##
7
+ # Convert a DataMapper Resource to a JSON.
8
+ #
9
+ # @param [Query] query
10
+ # The DataMapper query object passed in
11
+ #
12
+ # @api semipublic
13
+ def to_json_hash
14
+ json_rsrc = Hash.new
15
+
16
+
17
+ attributes(:property).each do |property, value|
18
+ next if value.nil? #|| (value.is_a?(Array) && value.empty?) || relations.include?(property.name.to_s)
19
+
20
+ json_rsrc[property.field] = case value
21
+ when DateTime then value.new_offset(0).strftime("%Y-%m-%dT%H:%M:%SZ")
22
+ when Date then value.to_s
23
+ when Time then value.strftime("%H:%M:%S")
24
+ when Float then value.to_f
25
+ when BigDecimal then value.to_f
26
+ when Integer then value.to_i
27
+ else # when String, TrueClass, FalseClass then
28
+ self[property.name]
29
+ end
30
+ end
31
+
32
+ json_rsrc
33
+ end
34
+ end # Resource
35
+ end # JSON
36
+ end # Persevere
37
+ end # DataMapper
@@ -0,0 +1,104 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module Migrations
4
+
5
+ # Returns whether the storage_name exists.
6
+ #
7
+ # @param [String] storage_name
8
+ # a String defining the name of a storage, for example a table name.
9
+ #
10
+ # @return [Boolean]
11
+ # true if the storage exists
12
+ #
13
+ # @api semipublic
14
+ def storage_exists?(storage_name)
15
+ class_names = JSON.parse(@persevere.retrieve('/Class/[=id]').body)
16
+ return true if class_names.include?("Class/"+storage_name)
17
+ false
18
+ end
19
+
20
+ ##
21
+ # Creates the persevere schema from the model.
22
+ #
23
+ # @param [DataMapper::Model] model
24
+ # The model that corresponds to the storage schema that needs to be created.
25
+ #
26
+ # @api semipublic
27
+ def create_model_storage(model)
28
+ model = Persevere.enhance(model)
29
+ name = self.name
30
+ properties = model.properties_with_subclasses(name)
31
+
32
+ return false if storage_exists?(model.storage_name(name))
33
+ return false if properties.empty?
34
+
35
+ # Make sure storage for referenced objects exists
36
+ model.relationships.each_pair do |n, r|
37
+ if ! storage_exists?(r.child_model.storage_name)
38
+ put_schema({'id' => r.child_model.storage_name, 'properties' => {}})
39
+ end
40
+ end
41
+ schema_hash = model.to_json_schema_hash()
42
+
43
+ return true unless put_schema(schema_hash) == false
44
+ false
45
+ end
46
+
47
+ ##
48
+ # Updates the persevere schema from the model.
49
+ #
50
+ # @param [DataMapper::Model] model
51
+ # The model that corresponds to the storage schema that needs to be updated.
52
+ #
53
+ # @api semipublic
54
+ def upgrade_model_storage(model)
55
+ model = Persevere.enhance(model)
56
+ name = self.name
57
+ properties = model.properties_with_subclasses(name)
58
+
59
+ DataMapper.logger.debug("Upgrading #{model.name}")
60
+
61
+ if success = create_model_storage(model)
62
+ return properties
63
+ end
64
+
65
+ new_schema_hash = model.to_json_schema_hash()
66
+ current_schema_hash = get_schema(new_schema_hash['id'])[0]
67
+ # TODO: Diff of what is there and what will be added.
68
+
69
+ new_properties = properties.map do |property|
70
+ prop_name = property.name.to_s
71
+ prop_type = property.type
72
+ next if prop_name == 'id' ||
73
+ (current_schema_hash['properties'].has_key?(prop_name) &&
74
+ new_schema_hash['properties'][prop_name]['type'] == current_schema_hash['properties'][prop_name]['type'] )
75
+ property
76
+ end.compact
77
+
78
+ return new_properties unless update_schema(new_schema_hash) == false
79
+ return nil
80
+ end
81
+
82
+ ##
83
+ # Destroys the persevere schema from the model.
84
+ #
85
+ # @param [DataMapper::Model] model
86
+ # The model that corresponds to the storage schema that needs to be destroyed.
87
+ #
88
+ # @api semipublic
89
+ def destroy_model_storage(model)
90
+ model = Persevere.enhance(model)
91
+ return true unless storage_exists?(model.storage_name(name))
92
+ schema_hash = model.to_json_schema_hash()
93
+ return true unless delete_schema(schema_hash) == false
94
+ false
95
+ end
96
+
97
+ end # module Migrations
98
+ end # module Persevere
99
+ end # module DataMapper
100
+
101
+ DataMapper::Migrations.include_migration_api
102
+ DataMapper::Persevere::Adapter.send(:include, DataMapper::Persevere::Migrations)
103
+
104
+
@@ -0,0 +1,208 @@
1
+ module DataMapper
2
+ module Persevere
3
+ module Query
4
+ ##
5
+ # TODO: Clean this mess up.
6
+ #
7
+ # @author lamb
8
+ def munge_condition(condition)
9
+ loaded_value = condition.loaded_value
10
+ return_value = ""
11
+ subject = condition.subject
12
+
13
+ if subject.is_a?(DataMapper::Property)
14
+ rhs = case loaded_value
15
+ when String then "\"#{loaded_value}\""
16
+ when DateTime then "date(%10.f)" % (Time.parse(loaded_value.to_s).to_f * 1000)
17
+ when nil then "undefined"
18
+ else loaded_value
19
+ end
20
+ return_value = "#{condition.subject.field}#{condition.__send__(:comparator_string)}#{rhs}"
21
+ elsif subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
22
+ # Join relationship, bury it down!
23
+ if self.model != subject.child_model
24
+ my_side_of_join = links.select{|relation|
25
+ relation.kind_of?(DataMapper::Associations::ManyToOne::Relationship) &&
26
+ relation.child_model == subject.child_model &&
27
+ # I would really like this to not look at the name,
28
+ # but sometimes they are different object of the same model
29
+ relation.parent_model.name == self.model.name }.first
30
+
31
+ # join_results = subject.child_model.all(subject.field.to_sym => loaded_value)
32
+ join_results = subject.child_model.all(subject.child_key.first.name => loaded_value[subject.parent_key.first.name])
33
+
34
+ return_value = join_results.map{|r| "#{self.model.key.first.name}=#{r[my_side_of_join.child_key.first.name]}"}.join('|')
35
+ else
36
+ comparator = loaded_value.nil? ? 'undefined' : loaded_value.key.first
37
+ return_value = "#{subject.child_key.first.name}#{condition.__send__(:comparator_string)}#{comparator}"
38
+ end
39
+ elsif subject.is_a?(DataMapper::Associations::Relationship)
40
+ if self.model != subject.child_model
41
+ return_value = "#{subject.child_key.first.name}#{condition.__send__(:comparator_string)}#{loaded_value.key.first}"
42
+ else
43
+ comparator = loaded_value.nil? ? 'undefined' : loaded_value.key.first
44
+ return_value = "#{subject.field}_id#{condition.__send__(:comparator_string)}#{comparator}"
45
+ end
46
+ end
47
+ return_value
48
+ end
49
+
50
+ ##
51
+ def process_condition(condition)
52
+ case condition
53
+ # Persevere 1.0 regular expressions are disable for security so we pass them back for DataMapper query filtering
54
+ # without regular expressions, the like operator is inordinately challenging hence we pass it back
55
+ # when :regexp then "RegExp(\"#{condition.value.source}\").test(#{condition.subject.name})"
56
+ when DataMapper::Query::Conditions::RegexpComparison then []
57
+ when DataMapper::Query::Conditions::LikeComparison then "#{condition.subject.field}='#{condition.loaded_value.gsub('%', '*')}'"
58
+ when DataMapper::Query::Conditions::AndOperation then
59
+ inside = condition.operands.map { |op| process_condition(op) }.flatten
60
+ inside.empty? ? [] : "(#{inside.join("&")})"
61
+ when DataMapper::Query::Conditions::OrOperation then "(#{condition.operands.map { |op| process_condition(op) }.join("|")})"
62
+ when DataMapper::Query::Conditions::NotOperation then
63
+ inside = process_condition(condition.operand)
64
+ inside.empty? ? [] : "!(%s)" % inside
65
+ when DataMapper::Query::Conditions::InclusionComparison then
66
+ result_string = Array.new
67
+ condition.value.to_a.each do |candidate|
68
+ if condition.subject.is_a?(DataMapper::Associations::Relationship)
69
+ result_string << "#{condition.subject.child_key.first.name}=#{candidate.key.first}" #munge_condition(condition)
70
+ else
71
+ result_string << "#{condition.subject.name}=#{candidate}"
72
+ end
73
+ end
74
+ if result_string.length > 0
75
+ "(#{result_string.join("|")})"
76
+ else
77
+ "#{condition.subject.name}=''"
78
+ end
79
+ when DataMapper::Query::Conditions::EqualToComparison,
80
+ DataMapper::Query::Conditions::GreaterThanComparison,
81
+ DataMapper::Query::Conditions::LessThanComparison,
82
+ DataMapper::Query::Conditions::GreaterThanOrEqualToComparison,
83
+ DataMapper::Query::Conditions::LessThanOrEqualToComparison then
84
+ munge_condition(condition)
85
+ when DataMapper::Query::Conditions::NullOperation then []
86
+ when Array then
87
+ old_statement, bind_values = condition
88
+ statement = old_statement.dup
89
+ bind_values.each{ |bind_value| statement.sub!('?', bind_value.to_s) }
90
+ statement.gsub(' ', '')
91
+ else condition.to_s.gsub(' ', '')
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Convert a DataMapper Query to a JSON Query.
97
+ #
98
+ # @param [Query] query
99
+ # The DataMapper query object passed in
100
+ #
101
+ # @api semipublic
102
+ def to_json_query
103
+ # Body of main function
104
+
105
+ json_query = ''
106
+ field_ops = Array.new
107
+ outfields = Array.new
108
+
109
+ json_query += self.to_json_query_filter
110
+
111
+ self.fields.each do |field|
112
+ if field.respond_to?(:operator)
113
+ field_ops << case field.operator
114
+ when :count then
115
+ if field.target.is_a?(DataMapper::Property)
116
+ "[?#{field.target.field}!=undefined].length"
117
+ else # field.target is all.
118
+ ".length"
119
+ end
120
+ when :min
121
+ if field.target.kind_of?(DataMapper::Property::DateTime) ||
122
+ field.target.kind_of?(DataMapper::Property::Time) ||
123
+ field.target.kind_of?(DataMapper::Property::Date)
124
+ "[=#{field.target.field}]"
125
+ else
126
+ ".min(?#{field.target.field})"
127
+ end
128
+ when :max
129
+ if field.target.kind_of?(DataMapper::Property::DateTime) ||
130
+ field.target.kind_of?(DataMapper::Property::Time) ||
131
+ field.target.kind_of?(DataMapper::Property::Date)
132
+ "[=#{field.target.field}]"
133
+ else
134
+ ".max(?#{field.target.field})"
135
+ end
136
+ when :sum
137
+ ".sum(?#{field.target.field})"
138
+ when :avg
139
+ "[=#{field.target.field}]"
140
+ end
141
+ else
142
+ outfields << "'#{field.field}':#{field.field}"
143
+ end
144
+ end
145
+
146
+ json_query += field_ops.join("")
147
+
148
+ json_query += self.to_json_query_ordering
149
+
150
+ json_query += "[={" + outfields.join(',') + "}]" unless outfields.empty?
151
+
152
+
153
+ # puts json_query, headers
154
+ return json_query, self.json_query_headers
155
+ end
156
+
157
+ ##
158
+ # The filter portion on a json query
159
+ #
160
+ # @author lamb
161
+ def to_json_query_filter
162
+ query_terms = []
163
+ query_terms << process_condition(conditions)
164
+
165
+ if query_terms.flatten.length != 0
166
+ return "[?#{query_terms.join("][?")}]"
167
+ else
168
+ return ''
169
+ end
170
+
171
+ end
172
+
173
+ ##
174
+ # The ordering portion of a json query
175
+ #
176
+ # @author lamb
177
+ def to_json_query_ordering
178
+ order_operations = []
179
+ if order && order.any?
180
+ order.map do |direction|
181
+ order_operations << case direction.operator
182
+ when :asc then "[\/#{direction.target.field}]"
183
+ when :desc then "[\\#{direction.target.field}]"
184
+ end
185
+ end
186
+ end
187
+
188
+ order_operations.join("")
189
+ end
190
+
191
+ ##
192
+ # The headers of a json query
193
+ #
194
+ # @author lamb
195
+ def json_query_headers
196
+ headers = Hash.new
197
+ offset = self.offset.to_i
198
+ limit = self.limit.nil? ? nil : self.limit.to_i + offset - 1
199
+
200
+ if offset != 0 || !limit.nil?
201
+ headers.merge!( {"Range" => "items=#{offset}-#{limit}"} )
202
+ end
203
+ return headers
204
+ end
205
+
206
+ end # Query
207
+ end # Persevere
208
+ end # DataMapper