dm-core 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
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) }