dm-core 0.9.3 → 0.9.4

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 (45) hide show
  1. data/CONTRIBUTING +51 -0
  2. data/FAQ +26 -8
  3. data/Manifest.txt +2 -0
  4. data/Rakefile +2 -1
  5. data/lib/dm-core.rb +8 -3
  6. data/lib/dm-core/adapters/abstract_adapter.rb +2 -2
  7. data/lib/dm-core/adapters/data_objects_adapter.rb +18 -4
  8. data/lib/dm-core/adapters/mysql_adapter.rb +8 -4
  9. data/lib/dm-core/adapters/postgres_adapter.rb +12 -3
  10. data/lib/dm-core/adapters/sqlite3_adapter.rb +1 -1
  11. data/lib/dm-core/associations.rb +1 -0
  12. data/lib/dm-core/associations/many_to_one.rb +7 -1
  13. data/lib/dm-core/associations/one_to_many.rb +1 -1
  14. data/lib/dm-core/associations/relationship.rb +14 -13
  15. data/lib/dm-core/auto_migrations.rb +57 -5
  16. data/lib/dm-core/collection.rb +2 -1
  17. data/lib/dm-core/logger.rb +5 -6
  18. data/lib/dm-core/model.rb +17 -2
  19. data/lib/dm-core/naming_conventions.rb +57 -25
  20. data/lib/dm-core/property.rb +6 -5
  21. data/lib/dm-core/query.rb +20 -16
  22. data/lib/dm-core/resource.rb +24 -7
  23. data/lib/dm-core/version.rb +1 -1
  24. data/script/performance.rb +2 -1
  25. data/script/profile.rb +1 -0
  26. data/spec/integration/association_spec.rb +131 -81
  27. data/spec/integration/association_through_spec.rb +87 -42
  28. data/spec/integration/associations/many_to_many_spec.rb +76 -16
  29. data/spec/integration/associations/many_to_one_spec.rb +6 -1
  30. data/spec/integration/associations/one_to_many_spec.rb +69 -0
  31. data/spec/integration/collection_spec.rb +7 -0
  32. data/spec/integration/query_spec.rb +24 -7
  33. data/spec/integration/resource_spec.rb +59 -10
  34. data/spec/integration/sti_spec.rb +23 -0
  35. data/spec/models/zoo.rb +2 -3
  36. data/spec/spec_helper.rb +9 -1
  37. data/spec/unit/adapters/postgres_adapter_spec.rb +9 -1
  38. data/spec/unit/associations/many_to_one_spec.rb +6 -0
  39. data/spec/unit/auto_migrations_spec.rb +2 -1
  40. data/spec/unit/naming_conventions_spec.rb +26 -18
  41. data/spec/unit/property_spec.rb +3 -4
  42. data/spec/unit/resource_spec.rb +19 -22
  43. data/tasks/gemspec.rb +23 -0
  44. data/tasks/hoe.rb +9 -1
  45. metadata +6 -14
data/CONTRIBUTING ADDED
@@ -0,0 +1,51 @@
1
+ # NOTE: This is a work in progress. As of July 24, it applies only to dm-core.
2
+
3
+ # Contributing to Edge DataMapper
4
+
5
+ We have now implemented Hoe throughout the DataMapper suite, so there will be a
6
+ handful of new procedures for contributing to our git repositories. I'll give
7
+ you a run through of how to set up your machine, and then provide a few
8
+ commands that should be run before committing or pushing changes.
9
+
10
+ ## Installing and configuring Hoe
11
+
12
+ The first step is to install hoe. You'll need at least version 1.7.0.
13
+
14
+ (sudo) gem install hoe --include-dependencies
15
+
16
+ Now you'll need to configure hoe. You'll need to run this from inside of
17
+ dm-core, or one of the other DataMapper projects.
18
+
19
+ rake config_hoe
20
+
21
+ The only thing you should need to change is the exclude regular expression,
22
+ which needs to look like this:
23
+
24
+ exclude: !ruby/regexp /tmp$|CVS|\.svn|\.git|.+\.gemspec/
25
+
26
+ Now you have the correct setup for contributing.
27
+
28
+ ## Before committing changes
29
+
30
+ Before you commit changes, you must verify that `Manifest.txt` (the file which
31
+ contains the names of every file to be included in a gem release) and
32
+ `[project-name].gemspec` are up to date. We have create a rake task to make
33
+ this easy:
34
+
35
+ rake gemspec
36
+
37
+ This will check `Manifest.txt` (using Hoe's `rake check_manifest`) to ensure
38
+ there are no differences between the files in the project, and those listed in
39
+ the manifest. If there is a difference, it will display a warning and a list of
40
+ the differences in `diff` format.
41
+
42
+ If the changes in the diff are correct, then you can run the following command
43
+ to update the manifest.
44
+
45
+ rake check_manifest | patch
46
+
47
+ If there are files you do not want added to the manifest, then you should
48
+ remove the files from the project, and then run `rake gemspec` again.
49
+
50
+ If `rake gemspec` says it was successful, then you can proceed with committing
51
+ and pushing your changes.
data/FAQ CHANGED
@@ -61,14 +61,32 @@ back. If you want to just execute something against the database, use
61
61
 
62
62
  === Can I get an query log of what DataMapper is issuing?
63
63
 
64
- Yup, to set this up, do:
64
+ An example of how to modify an existing logger:
65
65
 
66
- DataMapper::Logger.new(STDOUT, 0)
66
+ DataMapper.logger.set_log(STDOUT, :debug)
67
67
 
68
- Incidentally, if you'd like to send a message into the DataMapper logger, do:
68
+ An example of how to create new logger:
69
69
 
70
- DataMapper.logger.debug { "something" }
71
- DataMapper.logger.info { "something" }
72
- DataMapper.logger.warn { "something" }
73
- DataMapper.logger.error { "something" }
74
- DataMapper.logger.fatal { "something" }
70
+ DataMapper::Logger.new(STDOUT, :info)
71
+
72
+ To send a message to the DataMapper logger:
73
+
74
+ DataMapper.logger.debug("something")
75
+ DataMapper.logger.info ("something")
76
+ DataMapper.logger.warn ("something")
77
+ DataMapper.logger.error("something")
78
+ DataMapper.logger.fatal("something")
79
+
80
+
81
+ === I want to run the specs, but I have a custom database setup
82
+
83
+ For example, if you installed MySQL using MacPorts, your socket may be located
84
+ at /opt/local/var/run/mysql5/mysqld.sock instead of /tmp/mysql.sock
85
+
86
+ In that case, setup an environment variable in your shell before running the
87
+ specs:
88
+ export MYSQL_SPEC_URI="mysql://localhost/dm_core_test?socket=/opt/local/var/run/mysql5/mysqld.sock"
89
+ rake spec
90
+
91
+ Using another kind of database? Note that spec_helper.rb will also look for
92
+ SQLITE3_SPEC_URI AND POSTGRES_SPEC_URI.
data/Manifest.txt CHANGED
@@ -1,4 +1,5 @@
1
1
  .autotest
2
+ CONTRIBUTING
2
3
  FAQ
3
4
  History.txt
4
5
  MIT-LICENSE
@@ -119,5 +120,6 @@ spec/unit/type_spec.rb
119
120
  tasks/ci.rb
120
121
  tasks/dm.rb
121
122
  tasks/doc.rb
123
+ tasks/gemspec.rb
122
124
  tasks/hoe.rb
123
125
  tasks/install.rb
data/Rakefile CHANGED
@@ -13,7 +13,7 @@ AUTHOR = "Sam Smoot"
13
13
  EMAIL = "ssmoot@gmail.com"
14
14
  GEM_NAME = "dm-core"
15
15
  GEM_VERSION = DataMapper::VERSION
16
- GEM_DEPENDENCIES = ["data_objects", GEM_VERSION], ["extlib", GEM_VERSION],
16
+ GEM_DEPENDENCIES = ["data_objects", GEM_VERSION], ["extlib", "=0.9.4"],
17
17
  ["rspec", ">=1.1.3"], ["addressable", ">=1.0.4"]
18
18
 
19
19
 
@@ -23,6 +23,7 @@ PROJECT_SUMMARY = "An Object/Relational Mapper for Ruby"
23
23
  PROJECT_URL = "http://datamapper.org"
24
24
 
25
25
  require ROOT + 'tasks/hoe'
26
+ require ROOT + 'tasks/gemspec'
26
27
  require ROOT + 'tasks/install'
27
28
  require ROOT + 'tasks/dm'
28
29
  require ROOT + 'tasks/doc'
data/lib/dm-core.rb CHANGED
@@ -20,7 +20,7 @@ require 'rubygems'
20
20
  gem 'addressable', '>=1.0.4'
21
21
  require 'addressable/uri'
22
22
 
23
- gem 'extlib', '=0.9.3'
23
+ gem 'extlib', '=0.9.4'
24
24
  require 'extlib'
25
25
 
26
26
  begin
@@ -143,8 +143,13 @@ module DataMapper
143
143
  lib_name = "#{Extlib::Inflection.underscore(adapter_name)}_adapter"
144
144
  begin
145
145
  require root / 'lib' / 'dm-core' / 'adapters' / lib_name
146
- rescue LoadError
147
- require lib_name
146
+ rescue LoadError => e
147
+ begin
148
+ require lib_name
149
+ rescue Exception
150
+ # library not found, raise the original error
151
+ raise e
152
+ end
148
153
  end
149
154
  end
150
155
 
@@ -43,8 +43,8 @@ module DataMapper
43
43
  @name = name
44
44
  @uri = normalize_uri(uri_or_options)
45
45
 
46
- @resource_naming_convention = NamingConventions::UnderscoredAndPluralized
47
- @field_naming_convention = NamingConventions::Underscored
46
+ @resource_naming_convention = NamingConventions::Resource::UnderscoredAndPluralized
47
+ @field_naming_convention = NamingConventions::Field::Underscored
48
48
 
49
49
  @transactions = Hash.new do |hash, key|
50
50
  hash.delete_if do |k, v|
@@ -1,4 +1,4 @@
1
- gem 'data_objects', '=0.9.3'
1
+ gem 'data_objects', '=0.9.4'
2
2
  require 'data_objects'
3
3
 
4
4
  module DataMapper
@@ -327,7 +327,17 @@ module DataMapper
327
327
  query.merge_subquery(operator, opposite, condition)
328
328
  "(#{read_statement(condition)})"
329
329
  elsif condition.kind_of?(Array) && condition.all? { |p| p.kind_of?(Property) }
330
- "(#{condition.map { |p| property_to_column_name(query.repository, p, qualify) } * ', '})"
330
+ 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
+ "(#{property_values * ', '})"
331
341
  else
332
342
  '?'
333
343
  end
@@ -500,7 +510,7 @@ module DataMapper
500
510
  end
501
511
 
502
512
  # TODO: move to dm-more/dm-migrations
503
- def drop_table_statement(repisitory, model)
513
+ def drop_table_statement(repository, model)
504
514
  "DROP TABLE IF EXISTS #{quote_table_name(model.storage_name(repository.name))}"
505
515
  end
506
516
 
@@ -546,7 +556,11 @@ module DataMapper
546
556
  # remove the default if the property is not nullable
547
557
  schema.delete(:default) unless property.nullable?
548
558
  else
549
- schema[:default] = property.default
559
+ if property.type.respond_to?(:dump)
560
+ schema[:default] = property.type.dump(property.default, property)
561
+ else
562
+ schema[:default] = property.default
563
+ end
550
564
  end
551
565
 
552
566
  schema
@@ -1,4 +1,4 @@
1
- gem 'do_mysql', '=0.9.3'
1
+ gem 'do_mysql', '=0.9.4'
2
2
  require 'do_mysql'
3
3
 
4
4
  module DataMapper
@@ -39,8 +39,10 @@ module DataMapper
39
39
  def storage_exists?(storage_name)
40
40
  statement = <<-EOS.compress_lines
41
41
  SELECT COUNT(*)
42
- FROM `information_schema`.`columns`
43
- WHERE `table_schema` = ? AND `table_name` = ?
42
+ FROM `information_schema`.`tables`
43
+ WHERE `table_type` = 'BASE TABLE'
44
+ AND `table_schema` = ?
45
+ AND `table_name` = ?
44
46
  EOS
45
47
 
46
48
  query(statement, db_name, storage_name).first > 0
@@ -51,7 +53,9 @@ module DataMapper
51
53
  statement = <<-EOS.compress_lines
52
54
  SELECT COUNT(*)
53
55
  FROM `information_schema`.`columns`
54
- WHERE `table_schema` = ? AND `table_name` = ? AND `column_name` = ?
56
+ WHERE `table_schema` = ?
57
+ AND `table_name` = ?
58
+ AND `column_name` = ?
55
59
  EOS
56
60
 
57
61
  query(statement, db_name, storage_name, field_name).first > 0
@@ -1,4 +1,4 @@
1
- gem 'do_postgres', '=0.9.3'
1
+ gem 'do_postgres', '=0.9.4'
2
2
  require 'do_postgres'
3
3
 
4
4
  module DataMapper
@@ -21,7 +21,8 @@ module DataMapper
21
21
  statement = <<-EOS.compress_lines
22
22
  SELECT COUNT(*)
23
23
  FROM "information_schema"."columns"
24
- WHERE "table_name" = ? AND "table_schema" = current_schema()
24
+ WHERE "table_name" = ?
25
+ AND "table_schema" = current_schema()
25
26
  EOS
26
27
 
27
28
  query(statement, storage_name).first > 0
@@ -33,7 +34,9 @@ module DataMapper
33
34
  SELECT COUNT(*)
34
35
  FROM "pg_class"
35
36
  JOIN "pg_attribute" ON "pg_class"."oid" = "pg_attribute"."attrelid"
36
- WHERE "pg_attribute"."attname" = ? AND "pg_class"."relname" = ? AND "pg_attribute"."attnum" >= 0
37
+ WHERE "pg_attribute"."attname" = ?
38
+ AND "pg_class"."relname" = ?
39
+ AND "pg_attribute"."attnum" >= 0
37
40
  EOS
38
41
 
39
42
  query(statement, column_name, storage_name).first > 0
@@ -53,6 +56,7 @@ module DataMapper
53
56
 
54
57
  # TODO: move to dm-more/dm-migrations
55
58
  def destroy_model_storage(repository, model)
59
+ return true unless storage_exists?(model.storage_name(repository.name))
56
60
  success = without_notices { super }
57
61
  model.properties(repository.name).each do |property|
58
62
  drop_sequence(repository, property) if property.serial?
@@ -76,6 +80,11 @@ module DataMapper
76
80
  module SQL
77
81
  private
78
82
 
83
+ # TODO: move to dm-more/dm-migrations
84
+ def drop_table_statement(repository, model)
85
+ "DROP TABLE #{quote_table_name(model.storage_name(repository.name))}"
86
+ end
87
+
79
88
  # TODO: move to dm-more/dm-migrations
80
89
  def without_notices(&block)
81
90
  # execute the block with NOTICE messages disabled
@@ -1,4 +1,4 @@
1
- gem 'do_sqlite3', '=0.9.3'
1
+ gem 'do_sqlite3', '=0.9.4'
2
2
  require 'do_sqlite3'
3
3
 
4
4
  module DataMapper
@@ -40,6 +40,7 @@ module DataMapper
40
40
  #
41
41
  # @api private
42
42
  def many_to_one_relationships
43
+ relationships unless @relationships # needs to be initialized!
43
44
  @relationships.values.collect do |rels| rels.values end.flatten.select do |relationship| relationship.child_model == self end
44
45
  end
45
46
 
@@ -61,7 +61,9 @@ module DataMapper
61
61
  return true unless parent.new_record?
62
62
 
63
63
  @relationship.with_repository(parent) do
64
- parent.save
64
+ result = parent.save
65
+ @relationship.child_key.set(@child, @relationship.parent_key.get(parent)) if result
66
+ result
65
67
  end
66
68
  end
67
69
 
@@ -78,6 +80,10 @@ module DataMapper
78
80
  super || parent.respond_to?(method, include_private)
79
81
  end
80
82
 
83
+ def instance_variable_get(variable)
84
+ super || parent.instance_variable_get(variable)
85
+ end
86
+
81
87
  private
82
88
 
83
89
  def initialize(relationship, child)
@@ -244,7 +244,7 @@ module DataMapper
244
244
  end
245
245
 
246
246
  def assert_mutable
247
- raise ImmutableAssociationError, 'You can not modify this assocation' if children.frozen?
247
+ raise ImmutableAssociationError, 'You can not modify this association' if children.frozen?
248
248
  end
249
249
 
250
250
  def default_attributes
@@ -103,15 +103,14 @@ module DataMapper
103
103
 
104
104
  query = collection.query.dup
105
105
  query.conditions.map! do |operator, property, bind_value|
106
- if child_key.has_property?(property.name)
106
+ if operator != :raw && child_key.has_property?(property.name)
107
107
  bind_value = *children.map { |child| property.get(child) }.uniq
108
108
  end
109
109
  [ operator, property, bind_value ]
110
110
  end
111
111
 
112
- parents_children = Collection.new(query) do |collection|
113
- children.each { |child| collection.send(:add, child) }
114
- end
112
+ parents_children = Collection.new(query)
113
+ children.each { |child| parents_children.send(:add, child) }
115
114
 
116
115
  if parent_key.get(parent) == parent_values
117
116
  ret = parents_children
@@ -137,22 +136,24 @@ module DataMapper
137
136
  return parent
138
137
  end
139
138
 
140
- children = child_identity_map.values
139
+ children = child_identity_map.values | [child]
141
140
 
142
- bind_values = *children.map { |c| child_key.get(c) }.uniq
143
- query_values = bind_values.reject { |k| parent_identity_map[[k]] }
141
+ bind_values = children.map { |c| child_key.get(c) }.uniq
142
+ query_values = bind_values.reject { |k| parent_identity_map[k] }
144
143
 
145
144
  bind_values = query_values unless query_values.empty?
146
- query = parent_key.map { |k| [ k, bind_values ] }.to_hash
147
-
145
+ query = parent_key.zip(bind_values.transpose).to_hash
148
146
  association_accessor = "#{self.name}_association"
149
147
 
150
148
  collection = parent_model.send(:all, query)
151
- collection.send(:lazy_load)
152
- children.each do |c|
153
- c.send(association_accessor).instance_variable_set(:@parent, collection.get(*child_key.get(c)))
149
+ unless collection.empty?
150
+ collection.send(:lazy_load)
151
+ children.each do |c|
152
+ c.send(association_accessor).instance_variable_set(:@parent, collection.get(*child_key.get(c)))
153
+ end
154
+
155
+ child.send(association_accessor).instance_variable_get(:@parent)
154
156
  end
155
- child.send(association_accessor).instance_variable_get(:@parent)
156
157
  end
157
158
  end
158
159
 
@@ -3,14 +3,40 @@
3
3
  module DataMapper
4
4
  class AutoMigrator
5
5
  ##
6
- # Destructively automigrates the data-store to match the model
6
+ # Destructively automigrates the data-store to match the model.
7
+ # First migrates all models down and then up.
7
8
  # REPEAT: THIS IS DESTRUCTIVE
8
9
  #
9
10
  # @param Symbol repository_name the repository to be migrated
10
- # @calls DataMapper::Resource#auto_migrate!
11
- def self.auto_migrate(repository_name = nil)
12
- DataMapper::Resource.descendants.each do |model|
13
- model.auto_migrate!(repository_name)
11
+ def self.auto_migrate(repository_name = nil, *descendants)
12
+ auto_migrate_down(repository_name, *descendants)
13
+ auto_migrate_up(repository_name, *descendants)
14
+ end
15
+
16
+ ##
17
+ # Destructively automigrates the data-store down
18
+ # REPEAT: THIS IS DESTRUCTIVE
19
+ #
20
+ # @param Symbol repository_name the repository to be migrated
21
+ # @calls DataMapper::Resource#auto_migrate_down!
22
+ # @api private
23
+ def self.auto_migrate_down(repository_name = nil, *descendants)
24
+ descendants = DataMapper::Resource.descendants.to_a if descendants.empty?
25
+ descendants.reverse.each do |model|
26
+ model.auto_migrate_down!(repository_name)
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Automigrates the data-store up
32
+ #
33
+ # @param Symbol repository_name the repository to be migrated
34
+ # @calls DataMapper::Resource#auto_migrate_up!
35
+ # @api private
36
+ def self.auto_migrate_up(repository_name = nil, *descendants)
37
+ descendants = DataMapper::Resource.descendants.to_a if descendants.empty?
38
+ descendants.each do |model|
39
+ model.auto_migrate_up!(repository_name)
14
40
  end
15
41
  end
16
42
 
@@ -34,11 +60,37 @@ module DataMapper
34
60
  #
35
61
  # @param Symbol repository_name the repository to be migrated
36
62
  def auto_migrate!(repository_name = nil)
63
+ auto_migrate_down!(repository_name)
64
+ auto_migrate_up!(repository_name)
65
+ end
66
+
67
+ ##
68
+ # Destructively migrates the data-store down, which basically
69
+ # deletes all the models.
70
+ # REPEAT: THIS IS DESTRUCTIVE
71
+ #
72
+ # @param Symbol repository_name the repository to be migrated
73
+ # @api private
74
+ def auto_migrate_down!(repository_name = nil)
37
75
  if self.superclass != Object
38
76
  self.superclass.auto_migrate!(repository_name)
39
77
  else
40
78
  repository(repository_name) do |r|
41
79
  r.adapter.destroy_model_storage(r, self)
80
+ end
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Auto migrates the data-store to match the model
86
+ #
87
+ # @param Symbol repository_name the repository to be migrated
88
+ # @api private
89
+ def auto_migrate_up!(repository_name = nil)
90
+ if self.superclass != Object
91
+ self.superclass.auto_migrate!(repository_name)
92
+ else
93
+ repository(repository_name) do |r|
42
94
  r.adapter.create_model_storage(r, self)
43
95
  end
44
96
  end