dm-core 0.9.5 → 0.9.6

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 (54) hide show
  1. data/Manifest.txt +3 -0
  2. data/lib/dm-core.rb +14 -20
  3. data/lib/dm-core/adapters.rb +18 -0
  4. data/lib/dm-core/adapters/abstract_adapter.rb +17 -10
  5. data/lib/dm-core/adapters/data_objects_adapter.rb +17 -22
  6. data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  7. data/lib/dm-core/adapters/mysql_adapter.rb +1 -1
  8. data/lib/dm-core/adapters/postgres_adapter.rb +2 -2
  9. data/lib/dm-core/adapters/sqlite3_adapter.rb +1 -1
  10. data/lib/dm-core/associations.rb +3 -2
  11. data/lib/dm-core/associations/many_to_many.rb +3 -3
  12. data/lib/dm-core/associations/one_to_many.rb +10 -2
  13. data/lib/dm-core/associations/relationship.rb +20 -16
  14. data/lib/dm-core/auto_migrations.rb +5 -4
  15. data/lib/dm-core/collection.rb +10 -6
  16. data/lib/dm-core/dependency_queue.rb +2 -1
  17. data/lib/dm-core/identity_map.rb +3 -6
  18. data/lib/dm-core/model.rb +48 -27
  19. data/lib/dm-core/property.rb +57 -37
  20. data/lib/dm-core/property_set.rb +29 -22
  21. data/lib/dm-core/query.rb +57 -49
  22. data/lib/dm-core/repository.rb +3 -3
  23. data/lib/dm-core/resource.rb +17 -15
  24. data/lib/dm-core/scope.rb +7 -7
  25. data/lib/dm-core/support/kernel.rb +6 -2
  26. data/lib/dm-core/transaction.rb +7 -7
  27. data/lib/dm-core/version.rb +1 -1
  28. data/script/performance.rb +114 -22
  29. data/spec/integration/association_spec.rb +31 -2
  30. data/spec/integration/association_through_spec.rb +2 -0
  31. data/spec/integration/associations/many_to_many_spec.rb +152 -0
  32. data/spec/integration/associations/one_to_many_spec.rb +40 -3
  33. data/spec/integration/dependency_queue_spec.rb +0 -12
  34. data/spec/integration/postgres_adapter_spec.rb +1 -1
  35. data/spec/integration/property_spec.rb +3 -3
  36. data/spec/integration/query_spec.rb +39 -8
  37. data/spec/integration/resource_spec.rb +10 -6
  38. data/spec/integration/sti_spec.rb +22 -0
  39. data/spec/integration/strategic_eager_loading_spec.rb +21 -6
  40. data/spec/integration/type_spec.rb +1 -0
  41. data/spec/lib/model_loader.rb +10 -1
  42. data/spec/models/content.rb +16 -0
  43. data/spec/spec_helper.rb +4 -1
  44. data/spec/unit/adapters/data_objects_adapter_spec.rb +11 -11
  45. data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
  46. data/spec/unit/associations/many_to_many_spec.rb +16 -1
  47. data/spec/unit/model_spec.rb +0 -16
  48. data/spec/unit/property_set_spec.rb +8 -1
  49. data/spec/unit/property_spec.rb +476 -240
  50. data/spec/unit/query_spec.rb +41 -0
  51. data/spec/unit/resource_spec.rb +75 -56
  52. data/tasks/ci.rb +4 -36
  53. data/tasks/dm.rb +3 -3
  54. metadata +5 -2
data/Manifest.txt CHANGED
@@ -13,6 +13,7 @@ lib/dm-core.rb
13
13
  lib/dm-core/adapters.rb
14
14
  lib/dm-core/adapters/abstract_adapter.rb
15
15
  lib/dm-core/adapters/data_objects_adapter.rb
16
+ lib/dm-core/adapters/in_memory_adapter.rb
16
17
  lib/dm-core/adapters/mysql_adapter.rb
17
18
  lib/dm-core/adapters/postgres_adapter.rb
18
19
  lib/dm-core/adapters/sqlite3_adapter.rb
@@ -86,6 +87,7 @@ spec/lib/logging_helper.rb
86
87
  spec/lib/mock_adapter.rb
87
88
  spec/lib/model_loader.rb
88
89
  spec/lib/publicize_methods.rb
90
+ spec/models/content.rb
89
91
  spec/models/vehicles.rb
90
92
  spec/models/zoo.rb
91
93
  spec/spec.opts
@@ -93,6 +95,7 @@ spec/spec_helper.rb
93
95
  spec/unit/adapters/abstract_adapter_spec.rb
94
96
  spec/unit/adapters/adapter_shared_spec.rb
95
97
  spec/unit/adapters/data_objects_adapter_spec.rb
98
+ spec/unit/adapters/in_memory_adapter_spec.rb
96
99
  spec/unit/adapters/postgres_adapter_spec.rb
97
100
  spec/unit/associations/many_to_many_spec.rb
98
101
  spec/unit/associations/many_to_one_spec.rb
data/lib/dm-core.rb CHANGED
@@ -22,6 +22,7 @@ require 'addressable/uri'
22
22
 
23
23
  gem 'extlib', '>=0.9.5'
24
24
  require 'extlib'
25
+ require "extlib/inflection"
25
26
 
26
27
  begin
27
28
  require 'fastthread'
@@ -132,8 +133,8 @@ module DataMapper
132
133
  case uri_or_options
133
134
  when Hash
134
135
  adapter_name = uri_or_options[:adapter].to_s
135
- when String, Addressable::URI
136
- uri_or_options = Addressable::URI.parse(uri_or_options) if uri_or_options.kind_of?(String)
136
+ when String, DataObjects::URI, Addressable::URI
137
+ uri_or_options = DataObjects::URI.parse(uri_or_options) if uri_or_options.kind_of?(String)
137
138
  adapter_name = uri_or_options.scheme
138
139
  end
139
140
 
@@ -168,26 +169,19 @@ module DataMapper
168
169
  # @param [Symbol] args the name of a repository to act within or return, :default is default
169
170
  # @yield [Proc] (optional) block to execute within the context of the named repository
170
171
  # @demo spec/integration/repository_spec.rb
171
- def self.repository(*args, &block) # :yields: current_context
172
- if args.size > 1
173
- raise ArgumentError, "Can only pass in one optional argument, but passed in #{args.size} arguments", caller
174
- end
175
-
176
- if args.any? && !args.first.kind_of?(Symbol)
177
- raise ArgumentError, "First optional argument must be a Symbol, but was #{args.first.inspect}", caller
178
- end
179
-
180
- name = args.first
181
-
172
+ def self.repository(name = nil) # :yields: current_context
182
173
  current_repository = if name
174
+ raise ArgumentError, "First optional argument must be a Symbol, but was #{args.first.inspect}" unless name.is_a?(Symbol)
183
175
  Repository.context.detect { |r| r.name == name } || Repository.new(name)
184
176
  else
185
177
  Repository.context.last || Repository.new(Repository.default_name)
186
178
  end
187
179
 
188
- return current_repository unless block_given?
189
-
190
- current_repository.scope(&block)
180
+ if block_given?
181
+ current_repository.scope { |*block_args| yield(*block_args) }
182
+ else
183
+ current_repository
184
+ end
191
185
  end
192
186
 
193
187
  # A logger should always be present. Lets be consistent with DO
@@ -205,12 +199,12 @@ module DataMapper
205
199
  # drops and recreates the repository upwards to match model definitions
206
200
  #
207
201
  # @param [Symbol] name repository to act on, :default is the default
208
- def self.auto_migrate!(name = Repository.default_name)
209
- repository(name).auto_migrate!
202
+ def self.auto_migrate!(repository_name = nil)
203
+ AutoMigrator.auto_migrate(repository_name)
210
204
  end
211
205
 
212
- def self.auto_upgrade!(name = Repository.default_name)
213
- repository(name).auto_upgrade!
206
+ def self.auto_upgrade!(repository_name = nil)
207
+ AutoMigrator.auto_upgrade(repository_name)
214
208
  end
215
209
 
216
210
  def self.prepare(*args, &blk)
@@ -1,4 +1,22 @@
1
1
  dir = Pathname(__FILE__).dirname.expand_path / 'adapters'
2
2
 
3
3
  require dir / 'abstract_adapter'
4
+ require dir / 'in_memory_adapter'
5
+
6
+ # TODO Factor these out into dm-more
4
7
  require dir / 'data_objects_adapter'
8
+ begin
9
+ require dir / 'sqlite3_adapter'
10
+ rescue LoadError
11
+ # ignore it
12
+ end
13
+ begin
14
+ require dir / 'mysql_adapter'
15
+ rescue LoadError
16
+ # ignore it
17
+ end
18
+ begin
19
+ require dir / 'postgres_adapter'
20
+ rescue LoadError
21
+ # ignore it
22
+ end
@@ -38,7 +38,7 @@ module DataMapper
38
38
  # connection string for configuration.
39
39
  def initialize(name, uri_or_options)
40
40
  assert_kind_of 'name', name, Symbol
41
- assert_kind_of 'uri_or_options', uri_or_options, Addressable::URI, Hash, String
41
+ assert_kind_of 'uri_or_options', uri_or_options, Addressable::URI, DataObjects::URI, Hash, String
42
42
 
43
43
  @name = name
44
44
  @uri = normalize_uri(uri_or_options)
@@ -46,12 +46,7 @@ module DataMapper
46
46
  @resource_naming_convention = NamingConventions::Resource::UnderscoredAndPluralized
47
47
  @field_naming_convention = NamingConventions::Field::Underscored
48
48
 
49
- @transactions = Hash.new do |hash, key|
50
- hash.delete_if do |k, v|
51
- !k.respond_to?(:alive?) || !k.alive?
52
- end
53
- hash[key] = []
54
- end
49
+ @transactions = {}
55
50
  end
56
51
 
57
52
  # TODO: move to dm-more/dm-migrations
@@ -144,7 +139,7 @@ module DataMapper
144
139
  #
145
140
  # TODO: move to dm-more/dm-transaction
146
141
  def push_transaction(transaction)
147
- @transactions[Thread.current] << transaction
142
+ transactions(Thread.current) << transaction
148
143
  end
149
144
 
150
145
  #
@@ -156,7 +151,7 @@ module DataMapper
156
151
  #
157
152
  # TODO: move to dm-more/dm-transaction
158
153
  def pop_transaction
159
- @transactions[Thread.current].pop
154
+ transactions(Thread.current).pop
160
155
  end
161
156
 
162
157
  #
@@ -169,7 +164,7 @@ module DataMapper
169
164
  #
170
165
  # TODO: move to dm-more/dm-transaction
171
166
  def current_transaction
172
- @transactions[Thread.current].last
167
+ transactions(Thread.current).last
173
168
  end
174
169
 
175
170
  #
@@ -194,6 +189,18 @@ module DataMapper
194
189
  def transaction_primitive
195
190
  raise NotImplementedError
196
191
  end
192
+
193
+ private
194
+ def transactions(thread)
195
+ unless @transactions[thread]
196
+ @transactions.delete_if do |key, value|
197
+ !key.respond_to?(:alive?) || !key.alive?
198
+ end
199
+ @transactions[thread] = []
200
+ end
201
+ @transactions[thread]
202
+ end
203
+
197
204
  end
198
205
 
199
206
  include Transaction
@@ -1,4 +1,4 @@
1
- gem 'data_objects', '=0.9.5'
1
+ gem 'data_objects', '>=0.9.5'
2
2
  require 'data_objects'
3
3
 
4
4
  module DataMapper
@@ -42,7 +42,10 @@ module DataMapper
42
42
  command.set_types(query.fields.map { |p| p.primitive })
43
43
 
44
44
  begin
45
- reader = command.execute_reader(*query.bind_values)
45
+ bind_values = query.bind_values.map do |v|
46
+ v == [] ? [nil] : v
47
+ end
48
+ reader = command.execute_reader(*bind_values)
46
49
 
47
50
  while(reader.next!)
48
51
  collection.load(reader.values)
@@ -114,12 +117,12 @@ module DataMapper
114
117
  protected
115
118
 
116
119
  def normalize_uri(uri_or_options)
117
- if uri_or_options.kind_of?(String)
118
- uri_or_options = Addressable::URI.parse(uri_or_options)
120
+ if uri_or_options.kind_of?(String) || uri_or_options.kind_of?(Addressable::URI)
121
+ uri_or_options = DataObjects::URI.parse(uri_or_options)
119
122
  end
120
123
 
121
- if uri_or_options.kind_of?(Addressable::URI)
122
- return uri_or_options.normalize
124
+ if uri_or_options.kind_of?(DataObjects::URI)
125
+ return uri_or_options
123
126
  end
124
127
 
125
128
  adapter = uri_or_options.delete(:adapter).to_s
@@ -131,7 +134,7 @@ module DataMapper
131
134
  query = uri_or_options.to_a.map { |pair| pair * '=' } * '&'
132
135
  query = nil if query == ''
133
136
 
134
- return Addressable::URI.new(adapter, user, password, host, port, database, query, nil)
137
+ return DataObjects::URI.parse(Addressable::URI.new(adapter, user, password, host, port, database, query, nil))
135
138
  end
136
139
 
137
140
  # TODO: clean up once transaction related methods move to dm-more/dm-transactions
@@ -161,7 +164,7 @@ module DataMapper
161
164
  end
162
165
  end
163
166
 
164
- def with_connection(&block)
167
+ def with_connection
165
168
  connection = nil
166
169
  begin
167
170
  connection = create_connection
@@ -174,7 +177,7 @@ module DataMapper
174
177
  end
175
178
  end
176
179
 
177
- def with_reader(statement, bind_values = [], &block)
180
+ def with_reader(statement, bind_values = [])
178
181
  with_connection do |connection|
179
182
  reader = nil
180
183
  begin
@@ -264,8 +267,7 @@ module DataMapper
264
267
  table_name = query.model.storage_name(query.repository.name)
265
268
 
266
269
  statement = ''
267
-
268
- query.links.reverse.each do |relationship|
270
+ query.links.each do |relationship|
269
271
  parent_table_name = relationship.parent_model.storage_name(query.repository.name)
270
272
  child_table_name = relationship.child_model.storage_name(query.repository.name)
271
273
 
@@ -326,17 +328,10 @@ module DataMapper
326
328
  opposite = condition == left_condition ? right_condition : left_condition
327
329
  query.merge_subquery(operator, opposite, condition)
328
330
  "(#{read_statement(condition)})"
329
- elsif condition.kind_of?(Array) && condition.all? { |p| p.kind_of?(Property) }
331
+
332
+ # [].all? is always true
333
+ elsif condition.kind_of?(Array) && condition.any? && condition.all? { |p| p.kind_of?(Property) }
330
334
  property_values = condition.map { |p| property_to_column_name(query.repository, p, qualify) }
331
- # An IN() clause with an empty set is the same as a false condition
332
- # IN() with an empty set is not supported on all RDMS, but this is.
333
- if property_values.empty?
334
- if [:eql, :in, :not].include? operator
335
- return operator == :not ? "1=1" : "0=1"
336
- else
337
- raise "Invalid query operator #{operator.inspect} for #{property_values.inspect}"
338
- end
339
- end
340
335
  "(#{property_values * ', '})"
341
336
  else
342
337
  '?'
@@ -354,7 +349,7 @@ module DataMapper
354
349
  else raise "Invalid query operator: #{operator.inspect}"
355
350
  end
356
351
 
357
- conditions * " #{comparison} "
352
+ "(" + (conditions * " #{comparison} ") + ")"
358
353
  end
359
354
 
360
355
  def equality_operator(operand)
@@ -0,0 +1,87 @@
1
+ module DataMapper
2
+ module Adapters
3
+ class InMemoryAdapter < AbstractAdapter
4
+ def initialize(name, uri_or_options)
5
+ @records = Hash.new { |hash,model| hash[model] = Array.new }
6
+ end
7
+
8
+ def create(resources)
9
+ resources.each do |resource|
10
+ @records[resource.model] << resource
11
+ end.size # just return the number of records
12
+ end
13
+
14
+ def update(attributes, query)
15
+ read_many(query).each do |resource|
16
+ attributes.each do |property,value|
17
+ property.set!(resource, value)
18
+ end
19
+ end.size
20
+ end
21
+
22
+ def read_one(query)
23
+ read(query, query.model, false)
24
+ end
25
+
26
+ def read_many(query)
27
+ Collection.new(query) do |set|
28
+ read(query, set, true)
29
+ end
30
+ end
31
+
32
+ def delete(query)
33
+ records = @records[query.model]
34
+
35
+ read_many(query).each do |resource|
36
+ records.delete(resource)
37
+ end.size
38
+ end
39
+
40
+ private
41
+
42
+ def read(query, set, many = true)
43
+ model = query.model
44
+ conditions = query.conditions
45
+
46
+ match_with = many ? :select : :detect
47
+
48
+ # Iterate over the records for this model, and return
49
+ # the ones that match the conditions
50
+ result = @records[model].send(match_with) do |resource|
51
+ conditions.all? do |tuple|
52
+ operator, property, bind_value = *tuple
53
+
54
+ value = property.get!(resource)
55
+
56
+ case operator
57
+ when :eql, :in then equality_comparison(bind_value, value)
58
+ when :not then !equality_comparison(bind_value, value)
59
+ when :like then Regexp.new(bind_value) =~ value
60
+ when :gt then !value.nil? && value > bind_value
61
+ when :gte then !value.nil? && value >= bind_value
62
+ when :lt then !value.nil? && value < bind_value
63
+ when :lte then !value.nil? && value <= bind_value
64
+ else raise "Invalid query operator: #{operator.inspect}"
65
+ end
66
+ end
67
+ end
68
+
69
+ return result unless many
70
+
71
+ # TODO Sort
72
+
73
+ # TODO Limit
74
+
75
+ set.replace(result)
76
+ end
77
+
78
+ def equality_comparison(bind_value, value)
79
+ case bind_value
80
+ when Array, Range then bind_value.include?(value)
81
+ when NilClass then value.nil?
82
+ else bind_value == value
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,4 +1,4 @@
1
- gem 'do_mysql', '=0.9.5'
1
+ gem 'do_mysql', '>=0.9.5'
2
2
  require 'do_mysql'
3
3
 
4
4
  module DataMapper
@@ -1,4 +1,4 @@
1
- gem 'do_postgres', '=0.9.5'
1
+ gem 'do_postgres', '>=0.9.5'
2
2
  require 'do_postgres'
3
3
 
4
4
  module DataMapper
@@ -86,7 +86,7 @@ module DataMapper
86
86
  end
87
87
 
88
88
  # TODO: move to dm-more/dm-migrations
89
- def without_notices(&block)
89
+ def without_notices
90
90
  # execute the block with NOTICE messages disabled
91
91
  begin
92
92
  execute('SET client_min_messages = warning')
@@ -1,4 +1,4 @@
1
- gem 'do_sqlite3', '=0.9.5'
1
+ gem 'do_sqlite3', '>=0.9.5'
2
2
  require 'do_sqlite3'
3
3
 
4
4
  module DataMapper
@@ -45,8 +45,8 @@ module DataMapper
45
45
  end
46
46
 
47
47
  def relationships(repository_name = default_repository_name)
48
- @relationships ||= Hash.new { |h,k| h[k] = k == Repository.default_name ? {} : h[Repository.default_name].dup }
49
- @relationships[repository_name]
48
+ @relationships ||= {}
49
+ @relationships[repository_name] ||= repository_name == Repository.default_name ? {} : relationships(Repository.default_name).dup
50
50
  end
51
51
 
52
52
  def n
@@ -145,6 +145,7 @@ module DataMapper
145
145
  #
146
146
  # @api public
147
147
  def belongs_to(name, options={})
148
+ @_valid_relations = false
148
149
  relationship = ManyToOne.setup(name, self, options)
149
150
  # Please leave this in - I will release contextual serialization soon
150
151
  # which requires this -- guyvdb
@@ -42,13 +42,13 @@ module DataMapper
42
42
  opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
43
43
  opts[:parent_model] = model
44
44
  opts[:repository_name] = repository_name
45
- opts[:remote_relationship_name] ||= opts.delete(:remote_name) || name
45
+ opts[:remote_relationship_name] ||= opts.delete(:remote_name) || Extlib::Inflection.tableize(opts[:child_model])
46
46
  opts[:parent_key] = opts[:parent_key]
47
47
  opts[:child_key] = opts[:child_key]
48
48
  opts[:mutable] = true
49
49
 
50
50
  names = [ opts[:child_model], opts[:parent_model].name ].sort
51
- model_name = names.join
51
+ model_name = names.join.gsub("::", "")
52
52
  storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
53
53
 
54
54
  opts[:near_relationship_name] = Extlib::Inflection.tableize(model_name).to_sym
@@ -67,7 +67,7 @@ module DataMapper
67
67
  EOS
68
68
 
69
69
  names.each do |n|
70
- model.belongs_to(Extlib::Inflection.underscore(n).to_sym)
70
+ model.belongs_to(Extlib::Inflection.underscore(n).gsub("/", "_").to_sym, :class_name => n)
71
71
  end
72
72
 
73
73
  Object.const_set(model_name, model)
@@ -132,7 +132,7 @@ module DataMapper
132
132
  orphan_resource(super)
133
133
  end
134
134
 
135
- def delete(resource, &block)
135
+ def delete(resource)
136
136
  assert_mutable
137
137
  orphan_resource(super)
138
138
  end
@@ -153,6 +153,14 @@ module DataMapper
153
153
  assert_mutable
154
154
  attributes = default_attributes.merge(attributes)
155
155
  resource = children.respond_to?(:build) ? super(attributes) : new_child(attributes)
156
+ resource
157
+ end
158
+
159
+ def new(attributes = {})
160
+ assert_mutable
161
+ raise UnsavedParentError, 'You cannot intialize until the parent is saved' if @parent.new_record?
162
+ attributes = default_attributes.merge(attributes)
163
+ resource = children.respond_to?(:new) ? super(attributes) : @relationship.child_model.new(attributes)
156
164
  self << resource
157
165
  resource
158
166
  end
@@ -196,7 +204,7 @@ module DataMapper
196
204
  end
197
205
 
198
206
  def save
199
- assert_mutable
207
+ return true if children.frozen?
200
208
 
201
209
  # save every resource in the collection
202
210
  each { |resource| save_resource(resource) }