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.
- data/CONTRIBUTING +51 -0
- data/FAQ +26 -8
- data/Manifest.txt +2 -0
- data/Rakefile +2 -1
- data/lib/dm-core.rb +8 -3
- data/lib/dm-core/adapters/abstract_adapter.rb +2 -2
- data/lib/dm-core/adapters/data_objects_adapter.rb +18 -4
- data/lib/dm-core/adapters/mysql_adapter.rb +8 -4
- data/lib/dm-core/adapters/postgres_adapter.rb +12 -3
- data/lib/dm-core/adapters/sqlite3_adapter.rb +1 -1
- data/lib/dm-core/associations.rb +1 -0
- data/lib/dm-core/associations/many_to_one.rb +7 -1
- data/lib/dm-core/associations/one_to_many.rb +1 -1
- data/lib/dm-core/associations/relationship.rb +14 -13
- data/lib/dm-core/auto_migrations.rb +57 -5
- data/lib/dm-core/collection.rb +2 -1
- data/lib/dm-core/logger.rb +5 -6
- data/lib/dm-core/model.rb +17 -2
- data/lib/dm-core/naming_conventions.rb +57 -25
- data/lib/dm-core/property.rb +6 -5
- data/lib/dm-core/query.rb +20 -16
- data/lib/dm-core/resource.rb +24 -7
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +2 -1
- data/script/profile.rb +1 -0
- data/spec/integration/association_spec.rb +131 -81
- data/spec/integration/association_through_spec.rb +87 -42
- data/spec/integration/associations/many_to_many_spec.rb +76 -16
- data/spec/integration/associations/many_to_one_spec.rb +6 -1
- data/spec/integration/associations/one_to_many_spec.rb +69 -0
- data/spec/integration/collection_spec.rb +7 -0
- data/spec/integration/query_spec.rb +24 -7
- data/spec/integration/resource_spec.rb +59 -10
- data/spec/integration/sti_spec.rb +23 -0
- data/spec/models/zoo.rb +2 -3
- data/spec/spec_helper.rb +9 -1
- data/spec/unit/adapters/postgres_adapter_spec.rb +9 -1
- data/spec/unit/associations/many_to_one_spec.rb +6 -0
- data/spec/unit/auto_migrations_spec.rb +2 -1
- data/spec/unit/naming_conventions_spec.rb +26 -18
- data/spec/unit/property_spec.rb +3 -4
- data/spec/unit/resource_spec.rb +19 -22
- data/tasks/gemspec.rb +23 -0
- data/tasks/hoe.rb +9 -1
- 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
|
-
|
64
|
+
An example of how to modify an existing logger:
|
65
65
|
|
66
|
-
DataMapper
|
66
|
+
DataMapper.logger.set_log(STDOUT, :debug)
|
67
67
|
|
68
|
-
|
68
|
+
An example of how to create new logger:
|
69
69
|
|
70
|
-
DataMapper.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
DataMapper.logger.
|
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
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",
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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(
|
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
|
-
|
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.
|
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`.`
|
43
|
-
WHERE `
|
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` = ?
|
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.
|
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" = ?
|
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" = ?
|
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
|
data/lib/dm-core/associations.rb
CHANGED
@@ -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
|
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)
|
113
|
-
|
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 =
|
143
|
-
query_values = bind_values.reject { |k| parent_identity_map[
|
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.
|
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.
|
152
|
-
|
153
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|