dm-persevere-adapter 0.52.1 → 0.60.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,4 @@ pkg
2
2
  tmp
3
3
  coverage
4
4
  *.gemspec
5
+ .rvmrc
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'rake'
2
3
  require 'pathname'
3
4
 
4
5
  begin
@@ -14,18 +15,9 @@ begin
14
15
  gemspec.add_dependency(%q<dm-core>, [">= 0.10.1"])
15
16
  gemspec.add_dependency(%q<extlib>)
16
17
  end
17
- # Jeweler::Tasks.new do |gemspec|
18
- # gemspec.name = %q{persevere}
19
- # gemspec.summary = %q{A ruby wrapper for persevere}
20
- # gemspec.description = %q{A ruby wrapper for persevere}
21
- # gemspec.email = ["irjudson [a] gmail [d] com"]
22
- # gemspec.homepage = %q{http://github.com/yogo/persevere}
23
- # gemspec.authors = ["Ivan R. Judson", "The Yogo Data Management Development Team" ]
24
- # gemspec.rdoc_options = ["--main", "persevere/README.txt"]
25
- # gemspec.files = ["LICENSE.txt", "persevere/History.txt", "persevere/README.txt", "Rakefile", "lib/persevere.rb"]
26
- # gemspec.test_files = ["spec/persevere_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
27
- # end
18
+
28
19
  Jeweler::GemcutterTasks.new
20
+ FileList['tasks/**/*.rake'].each { |task| import task }
29
21
  rescue LoadError
30
22
  puts "Jeweler not available. Install it with: gem install jeweler"
31
23
  end
@@ -33,6 +25,4 @@ end
33
25
  ROOT = Pathname(__FILE__).dirname.expand_path
34
26
  JRUBY = RUBY_PLATFORM =~ /java/
35
27
  WINDOWS = Gem.win_platform?
36
- SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
37
-
38
- Pathname.glob(ROOT.join('tasks/**/*.rb').to_s).each { |f| require f }
28
+ SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.52.1
1
+ 0.60.0
@@ -0,0 +1,270 @@
1
+ module DataMapper
2
+ module Associations
3
+ module ManyToMany #:nodoc:
4
+ class Relationship < Associations::OneToMany::Relationship
5
+
6
+ OPTIONS.delete(:via)
7
+ OPTIONS.delete(:through)
8
+
9
+ remove_method :through
10
+ remove_method :via
11
+ remove_method :lazy_load
12
+ remove_method :source_scope
13
+ remove_method :inverted_options
14
+ remove_method :valid_target?
15
+ remove_method :valid_source?
16
+
17
+ #
18
+ # @api private
19
+ def query
20
+ @query
21
+ end
22
+
23
+ def set(source, target)
24
+ assert_kind_of 'source', source, source_model
25
+ assert_kind_of 'target', target, target_model
26
+ lazy_load(source) unless loaded?(source)
27
+ get!(source).replace([target])
28
+ end
29
+
30
+ # Loads association targets and sets resulting value on
31
+ # given source resource
32
+ #
33
+ # @param [Resource] source
34
+ # the source resource for the association
35
+ #
36
+ # @return [undefined]
37
+ #
38
+ # @api private
39
+ def lazy_load(source)
40
+
41
+ # SEL: load all related resources in the source collection
42
+ collection = source.collection
43
+ # if source.saved? && collection.size > 1 #OLD LINE --IRJ
44
+ if source.saved?
45
+ eager_load(collection)
46
+ end
47
+
48
+ unless loaded?(source)
49
+ set!(source, collection_for(source))
50
+ end
51
+ end
52
+
53
+ # Eager load the collection using the source as a base
54
+ #
55
+ # @param [Collection] source
56
+ # the source collection to query with
57
+ # @param [Query, Hash] query
58
+ # optional query to restrict the collection
59
+ #
60
+ # @return [Collection]
61
+ # the loaded collection for the source
62
+ #
63
+ # @api private
64
+ def eager_load(source, query = nil)
65
+
66
+ targets = source.model.all(query_for(source, query))
67
+
68
+ # FIXME: cannot associate targets to m:m collection yet, maybe we fixed it.
69
+ # if source.loaded? && !source.kind_of?(ManyToMany::Collection) WE CHANGED THIS: IRJ/RL
70
+ if source.loaded?
71
+ associate_targets(source, targets)
72
+ end
73
+
74
+ targets
75
+ end
76
+
77
+ def associate_targets(source, targets)
78
+ # TODO: create an object that wraps this logic, and when the first
79
+ # kicker is fired, then it'll load up the collection, and then
80
+ # populate all the other methods
81
+ target_maps = Hash.new { |hash, key| hash[key] = [] }
82
+
83
+ targets.each do |target|
84
+ target_maps[target_key.get(target)] << target
85
+ end
86
+
87
+ Array(source).each do |source|
88
+ key = source_key.get(source)
89
+ # eager_load_targets(source, target_maps[key], query)
90
+
91
+ set!(source, collection_for(source, query).set(targets))
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ # Returns the inverse relationship class
98
+ #
99
+ # @api private
100
+ def inverse_class
101
+ self.class
102
+ end
103
+
104
+ def inverse_name
105
+ Extlib::Inflection.underscore(Extlib::Inflection.demodulize(source_model.name)).pluralize.to_sym
106
+ end
107
+
108
+ # @api private
109
+ def invert
110
+ inverse_class.new(inverse_name, parent_model, child_model, inverted_options)
111
+ end
112
+
113
+ # @api semipublic
114
+ def initialize(name, target_model, source_model, options = {})
115
+ options.delete(:through)
116
+ super
117
+ end
118
+
119
+ # Returns collection class used by this type of
120
+ # relationship
121
+ #
122
+ # @api private
123
+ def collection_class
124
+ ManyToMany::Collection
125
+ end
126
+ end # class Relationship
127
+
128
+ class Collection < Associations::OneToMany::Collection
129
+ remove_method :_save
130
+ remove_method :_create
131
+
132
+ def inverse_add(*resources)
133
+ resources.each do |r|
134
+ r.send(relationship.inverse.name)._original_add(source)
135
+ end
136
+ end
137
+
138
+ alias :_original_add :"<<"
139
+ def <<(resource)
140
+ resource.send(relationship.inverse.name)._original_add(source)
141
+ _original_add(resource)
142
+ end
143
+
144
+ alias :_original_concat :concat
145
+ def concat(resources)
146
+ inverse_add(*resources)
147
+ _original_concat(resources)
148
+ end
149
+
150
+ alias :_original_push :push
151
+ def push(*resources)
152
+ inverse_add(*resources)
153
+ _original_push(*resources)
154
+ end
155
+
156
+
157
+ alias :_original_unshift :unshift
158
+ def unshift(*resources)
159
+ inverse_add(*resources)
160
+ _original_unshift(*resources)
161
+ end
162
+
163
+ alias :_original_insert :insert
164
+ def insert(offset, *resources)
165
+ inverse_add(*resources)
166
+ _original_insert(offset, *resources)
167
+ end
168
+
169
+ alias :_original_delete :delete
170
+ def delete(resource)
171
+ result = _original_delete(resource)
172
+ resource.send(relationship.inverse.name)._original_delete(source)
173
+ result
174
+ end
175
+
176
+ alias :_original_pop :pop
177
+ def pop(*)
178
+ removed = _original_pop
179
+ removed._original_delete(source) unless removed.nil?
180
+ removed
181
+ end
182
+
183
+ alias :_original_shift :shift
184
+ def shift(*)
185
+ removed = _original_pop
186
+ removed._original_delete(source) unless removed.nil?
187
+ removed
188
+ end
189
+
190
+ alias :_original_delete_at :delete_at
191
+ def delete_at(offset)
192
+ resource = _original_delete_at(offset)
193
+ resource._original_delete(source) unless removed.nil?
194
+ resource
195
+ end
196
+
197
+ # alias :_original_delete_if :delete_if
198
+ def delete_if
199
+ results = super { |resource| yield(resource) && resource_removed(resource) }
200
+ results.each{|r| r._original_delete(source) }
201
+ results
202
+ end
203
+
204
+ def reject!
205
+ results = super { |resource| yield(resource) && resource_removed(resource) }
206
+ results.each{|r| r._original_delete(source) }
207
+ results
208
+ end
209
+
210
+ def replace(other)
211
+ other = resources_added(other)
212
+ removed = entries - other
213
+ new_resources = other - removed
214
+ resources_removed(removed)
215
+ removed.each{|r| r._original_delete(source) }
216
+ new_resources.each{|r| r._original_add(source) }
217
+ super(other)
218
+ end
219
+
220
+ alias :_original_clear :clear
221
+ def clear
222
+ self.each{|r| r._original_delete(source) }
223
+ _original_clear
224
+ end
225
+
226
+ # TODO: Add these
227
+ # slice!, splice, collect!
228
+
229
+ def _save(safe)
230
+ loaded_entries = self.loaded_entries
231
+ @removed.clear
232
+ loaded_entries.all? { |resource| resource.__send__(safe ? :save : :save!) }
233
+ end
234
+
235
+ private
236
+
237
+ # Track the added resource
238
+ #
239
+ # @param [Resource] resource
240
+ # the resource that was added
241
+ #
242
+ # @return [Resource]
243
+ # the resource that was added
244
+ #
245
+ # @api private
246
+ def resource_added(resource)
247
+ resource = initialize_resource(resource)
248
+
249
+ if resource.saved?
250
+ @identity_map[resource.key] = resource
251
+ @removed.delete(resource)
252
+ else
253
+ resource.save
254
+ end
255
+ resource
256
+ end
257
+
258
+ # @api private
259
+ def resource_removed(resource)
260
+ if resource.saved?
261
+ @identity_map.delete(resource.key)
262
+ @removed << resource
263
+ end
264
+
265
+ resource
266
+ end
267
+ end # class Collection
268
+ end # module ManyToMany
269
+ end # module Associations
270
+ end # module DataMapper
@@ -0,0 +1,48 @@
1
+ module DataMapper
2
+ module Model
3
+ # module Json
4
+ def to_json_schema(repository_name = default_repository_name)
5
+ to_json_schema_hash(repository_name).to_json
6
+ end
7
+
8
+ #TODO: Add various options in.
9
+ def to_json_schema_hash(repository_name = default_repository_name)
10
+ schema_hash = {
11
+ 'id' => self.storage_name(repository_name),
12
+ 'prototype' => Hash.new,
13
+ 'properties' => Hash.new
14
+ }
15
+
16
+ # Handle properties
17
+ properties.select { |prop| prop.field != 'id' }.each do |prop|
18
+ schema_hash['properties'][prop.field] = prop.to_json_schema_hash(repository_name)
19
+ end
20
+
21
+ # Handle relationships
22
+ relationships.each_pair do |nom,relation|
23
+ next if self.name.downcase == nom
24
+ child = relation.child_model
25
+ parent = relation.parent_model
26
+
27
+ case relation
28
+ when DataMapper::Associations::OneToMany::Relationship, DataMapper::Associations::ManyToMany::Relationship
29
+ schema_hash['properties'][nom] = { "type" => "array",
30
+ "optional" => true,
31
+ "items" => {"$ref" => "../#{child.storage_name}"},
32
+ "minItems" => relation.min,
33
+ }
34
+
35
+ schema_hash['properties'][nom]["maxItems"] = relation.max if relation.max != Infinity
36
+ when DataMapper::Associations::ManyToOne::Relationship, DataMapper::Associations::OneToOne::Relationship
37
+ if self == relation.child_model
38
+ ref = "../#{parent.storage_name}"
39
+ else
40
+ ref = "../#{child.storage_name}"
41
+ end
42
+ schema_hash['properties'][nom] = { "type" => { "$ref" => ref }, "optional" => true }
43
+ end
44
+ end
45
+ return schema_hash
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ module DataMapper
2
+ class Property
3
+
4
+ def to_json_schema_hash(repo)
5
+ tm = repository(repo).adapter.type_map
6
+ json_hash = Hash.new
7
+ json_hash = { "type" => tm[type][:primitive] }
8
+ json_hash.merge!({ "optional" => true }) unless required?
9
+ json_hash.merge!({ "unique" => true}) if unique?
10
+ json_hash.merge!({ "position" => @position }) unless @position.nil?
11
+ json_hash.merge!({ "prefix" => @prefix }) unless @prefix.nil?
12
+ json_hash.merge!({ "separator" => @separator }) unless @separator.nil?
13
+ json_hash.merge!( tm[type].reject{ |key,value| key == :primitive } )
14
+
15
+ json_hash
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,166 @@
1
+ module DataMapper
2
+ class Query
3
+ ##
4
+ def munge_condition(condition)
5
+ loaded_value = condition.loaded_value
6
+ return_value = ""
7
+
8
+ if condition.subject.is_a?(DataMapper::Property)
9
+ rhs = case loaded_value
10
+ when String then "\"#{loaded_value}\""
11
+ when DateTime then "date(%10.f)" % (Time.parse(loaded_value.to_s).to_f * 1000)
12
+ when nil then "undefined"
13
+ else loaded_value
14
+ end
15
+ return_value = "#{condition.subject.field}#{condition.__send__(:comparator_string)}#{rhs}"
16
+ end
17
+
18
+ return_value = _fugly_munger(condition, loaded_value) if condition.subject.is_a?(DataMapper::Associations::Relationship)
19
+ return_value
20
+ end
21
+
22
+ def _fugly_munger(condition, loaded_value)
23
+ subject = condition.subject
24
+ case subject
25
+ when DataMapper::Associations::ManyToMany::Relationship then
26
+ return_value = "#{condition.subject.field}.contains(/#{subject.child_model.storage_name}/#{loaded_value.key.first})"
27
+ when DataMapper::Associations::OneToMany::Relationship then
28
+ return_value = "#{condition.subject.field}.contains(/#{subject.parent_model.storage_name}/#{loaded_value.key.first})"
29
+ when DataMapper::Associations::OneToOne::Relationship then
30
+ return_value = "#{condition.subject.field}#{condition.__send__(:comparator_string)}/#{subject.parent_model.storage_name}/#{loaded_value.key.first}"
31
+ when DataMapper::Associations::ManyToOne::Relationship then
32
+ if self.model != subject.child_model
33
+ return_value = "#{condition.subject.field}.contains(/#{subject.parent_model.storage_name}/#{loaded_value.key.first})"
34
+ else
35
+ return_value = "#{condition.subject.field}#{condition.__send__(:comparator_string)}/#{subject.parent_model.storage_name}/#{loaded_value.key.first}"
36
+ end
37
+ end
38
+ end
39
+
40
+ ##
41
+ def process_condition(condition)
42
+ case condition
43
+ # Persevere 1.0 regular expressions are disable for security so we pass them back for DataMapper query filtering
44
+ # without regular expressions, the like operator is inordinately challenging hence we pass it back
45
+ # when :regexp then "RegExp(\"#{condition.value.source}\").test(#{condition.subject.name})"
46
+ when DataMapper::Query::Conditions::RegexpComparison then []
47
+ when DataMapper::Query::Conditions::LikeComparison then "#{condition.subject.field}='#{condition.loaded_value.gsub('%', '*')}'"
48
+ when DataMapper::Query::Conditions::AndOperation then
49
+ inside = condition.operands.map { |op| process_condition(op) }.flatten
50
+ inside.empty? ? [] : "(#{inside.join("&")})"
51
+ when DataMapper::Query::Conditions::OrOperation then "(#{condition.operands.map { |op| process_condition(op) }.join("|")})"
52
+ when DataMapper::Query::Conditions::NotOperation then
53
+ inside = process_condition(condition.operand)
54
+ inside.empty? ? [] : "!(%s)" % inside
55
+ when DataMapper::Query::Conditions::InclusionComparison then
56
+ result_string = Array.new
57
+ condition.value.to_a.each do |candidate|
58
+ if condition.subject.is_a?(DataMapper::Associations::Relationship)
59
+ # debugger
60
+ result_string << _fugly_munger(condition, candidate)
61
+ else
62
+ result_string << "#{condition.subject.name}=#{candidate}"
63
+ end
64
+ end
65
+ if result_string.length > 0
66
+ "(#{result_string.join("|")})"
67
+ else
68
+ "#{condition.subject.name}=''"
69
+ end
70
+ when DataMapper::Query::Conditions::EqualToComparison,
71
+ DataMapper::Query::Conditions::GreaterThanComparison,
72
+ DataMapper::Query::Conditions::LessThanComparison,
73
+ DataMapper::Query::Conditions::GreaterThanOrEqualToComparison,
74
+ DataMapper::Query::Conditions::LessThanOrEqualToComparison then
75
+ munge_condition(condition)
76
+ when DataMapper::Query::Conditions::NullOperation then []
77
+ when Array then
78
+ old_statement, bind_values = condition
79
+ statement = old_statement.dup
80
+ bind_values.each{ |bind_value| statement.sub!('?', bind_value.to_s) }
81
+ statement.gsub(' ', '')
82
+ else condition.to_s.gsub(' ', '')
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Convert a DataMapper Query to a JSON Query.
88
+ #
89
+ # @param [Query] query
90
+ # The DataMapper query object passed in
91
+ #
92
+ # @api semipublic
93
+ def to_json_query
94
+
95
+ # Body of main function
96
+ json_query = ""
97
+ query_terms = Array.new
98
+ order_operations = Array.new
99
+ field_ops = Array.new
100
+ outfields = Array.new
101
+ headers = Hash.new
102
+
103
+ query_terms << process_condition(conditions)
104
+
105
+ if query_terms.flatten.length != 0
106
+ json_query += "[?#{query_terms.join("][?")}]"
107
+ end
108
+
109
+ self.fields.each do |field|
110
+ if field.respond_to?(:operator)
111
+ field_ops << case field.operator
112
+ when :count then
113
+ if field.target.is_a?(DataMapper::Property)
114
+ "[?#{field.target.field}!=undefined].length"
115
+ else # field.target is all.
116
+ ".length"
117
+ end
118
+ when :min
119
+ if field.target.type == DateTime || field.target.type == Time || field.target.type == Date
120
+ "[=#{field.target.field}]"
121
+ else
122
+ ".min(?#{field.target.field})"
123
+ end
124
+ when :max
125
+ if field.target.type == DateTime || field.target.type == Time || field.target.type == Date
126
+ "[=#{field.target.field}]"
127
+ else
128
+ ".max(?#{field.target.field})"
129
+ end
130
+ when :sum
131
+ ".sum(?#{field.target.field})"
132
+ when :avg
133
+ "[=#{field.target.field}]"
134
+ end
135
+ else
136
+ outfields << "'#{field.field}':#{field.field}"
137
+ end
138
+ end
139
+
140
+ json_query += field_ops.join("")
141
+
142
+ if order && order.any?
143
+ order.map do |direction|
144
+ order_operations << case direction.operator
145
+ when :asc then "[\/#{direction.target.field}]"
146
+ when :desc then "[\\#{direction.target.field}]"
147
+ end
148
+ end
149
+ end
150
+
151
+ json_query += order_operations.join("")
152
+
153
+ json_query += "[={" + outfields.join(',') + "}]" unless outfields.empty?
154
+
155
+ offset = self.offset.to_i
156
+ limit = self.limit.nil? ? nil : self.limit.to_i + offset - 1
157
+
158
+ if offset != 0 || !limit.nil?
159
+ headers.merge!({"Range", "items=#{offset}-#{limit}"})
160
+ end
161
+ # puts "#{inspect}"
162
+ # puts json_query, headers
163
+ return json_query, headers
164
+ end
165
+ end
166
+ end