sam-dm-core 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 (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,80 @@
1
+ # TODO: move to dm-more/dm-migrations
2
+
3
+ module DataMapper
4
+ class TypeMap
5
+
6
+ attr_accessor :parent, :chains
7
+
8
+ def initialize(parent = nil, &blk)
9
+ @parent, @chains = parent, {}
10
+
11
+ blk.call(self) unless blk.nil?
12
+ end
13
+
14
+ def map(type)
15
+ @chains[type] ||= TypeChain.new
16
+ end
17
+
18
+ def lookup(type)
19
+ if type_mapped?(type)
20
+ lookup_from_map(type)
21
+ else
22
+ lookup_by_type(type)
23
+ end
24
+ end
25
+
26
+ def lookup_from_map(type)
27
+ lookup_from_parent(type).merge(map(type).translate)
28
+ end
29
+
30
+ def lookup_from_parent(type)
31
+ if !@parent.nil? && @parent.type_mapped?(type)
32
+ @parent[type]
33
+ else
34
+ {}
35
+ end
36
+ end
37
+
38
+ # @raise <DataMapper::TypeMap::Error> if the type is not a default primitive or has a type map entry.
39
+ def lookup_by_type(type)
40
+ raise DataMapper::TypeMap::Error.new(type) unless type.respond_to?(:primitive) && !type.primitive.nil?
41
+
42
+ lookup(type.primitive).merge(Type::PROPERTY_OPTIONS.inject({}) {|h, k| h[k] = type.send(k); h})
43
+ end
44
+
45
+ alias [] lookup
46
+
47
+ def type_mapped?(type)
48
+ @chains.has_key?(type) || (@parent.nil? ? false : @parent.type_mapped?(type))
49
+ end
50
+
51
+ class TypeChain
52
+ attr_accessor :primitive, :attributes
53
+
54
+ def initialize
55
+ @attributes = {}
56
+ end
57
+
58
+ def to(primitive)
59
+ @primitive = primitive
60
+ self
61
+ end
62
+
63
+ def with(attributes)
64
+ raise "method 'with' expects a hash" unless attributes.kind_of?(Hash)
65
+ @attributes.merge!(attributes)
66
+ self
67
+ end
68
+
69
+ def translate
70
+ @attributes.merge((@primitive.nil? ? {} : {:primitive => @primitive}))
71
+ end
72
+ end # class TypeChain
73
+
74
+ class Error < StandardError
75
+ def initialize(type)
76
+ super("Type #{type} must have a default primitive or type map entry")
77
+ end
78
+ end
79
+ end # class TypeMap
80
+ end # module DataMapper
@@ -0,0 +1,19 @@
1
+ dir = Pathname(__FILE__).dirname.expand_path / 'types'
2
+
3
+ require dir / 'boolean'
4
+ require dir / 'discriminator'
5
+ require dir / 'text'
6
+ require dir / 'paranoid_datetime'
7
+ require dir / 'paranoid_boolean'
8
+ require dir / 'object'
9
+ require dir / 'serial'
10
+
11
+ unless defined?(DM)
12
+ DM = DataMapper::Types
13
+ end
14
+
15
+ module DataMapper
16
+ module Resource
17
+ include Types
18
+ end # module Resource
19
+ end # module DataMapper
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module Types
3
+ class Boolean < DataMapper::Type
4
+ primitive TrueClass
5
+ end # class Boolean
6
+ end # module Types
7
+ end # module DataMapper
@@ -0,0 +1,34 @@
1
+ module DataMapper
2
+ module Types
3
+ class Discriminator < DataMapper::Type
4
+ primitive Class
5
+ track :set
6
+ default lambda { |r,p| p.model }
7
+ nullable false
8
+
9
+ def self.bind(property)
10
+ model = property.model
11
+
12
+ model.class_eval <<-EOS, __FILE__, __LINE__
13
+ def self.descendants
14
+ (@descendants ||= []).uniq!
15
+ @descendants
16
+ end
17
+
18
+ after_class_method :inherited, :add_scope_for_discriminator
19
+
20
+ def self.add_scope_for_discriminator(retval, target)
21
+ target.descendants << target
22
+ target.default_scope.update(#{property.name.inspect} => target.descendants)
23
+ propagate_descendants(target)
24
+ end
25
+
26
+ def self.propagate_descendants(target)
27
+ descendants << target
28
+ superclass.propagate_descendants(target) if superclass.respond_to?(:propagate_descendants)
29
+ end
30
+ EOS
31
+ end
32
+ end # class Discriminator
33
+ end # module Types
34
+ end # module DataMapper
@@ -0,0 +1,24 @@
1
+ require "base64"
2
+
3
+ module DataMapper
4
+ module Types
5
+ class Object < DataMapper::Type
6
+ primitive String
7
+ size 65535
8
+ lazy true
9
+ track :hash
10
+
11
+ def self.typecast(value, property)
12
+ value
13
+ end
14
+
15
+ def self.dump(value, property)
16
+ Base64.encode64(Marshal.dump(value))
17
+ end
18
+
19
+ def self.load(value, property)
20
+ value.nil? ? nil : Marshal.load(Base64.decode64(value))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ module DataMapper
2
+ module Types
3
+ class ParanoidBoolean < DataMapper::Type(Boolean)
4
+ primitive TrueClass
5
+ default false
6
+ lazy true
7
+
8
+ def self.bind(property)
9
+ model = property.model
10
+ repository = property.repository
11
+
12
+ model.send(:set_paranoid_property, property.name){true}
13
+
14
+ model.class_eval <<-EOS, __FILE__, __LINE__
15
+
16
+ def self.with_deleted
17
+ with_exclusive_scope(#{property.name.inspect} => true) do
18
+ yield
19
+ end
20
+ end
21
+
22
+ def destroy
23
+ self.class.paranoid_properties.each do |name, blk|
24
+ attribute_set(name, blk.call(self))
25
+ end
26
+ save
27
+ end
28
+ EOS
29
+
30
+ model.default_scope(repository.name).update(property.name => false)
31
+ end
32
+ end # class ParanoidBoolean
33
+ end # module Types
34
+ end # module DataMapper
@@ -0,0 +1,33 @@
1
+ module DataMapper
2
+ module Types
3
+ class ParanoidDateTime < DataMapper::Type(DateTime)
4
+ primitive DateTime
5
+ lazy true
6
+
7
+ def self.bind(property)
8
+ model = property.model
9
+ repository = property.repository
10
+
11
+ model.send(:set_paranoid_property, property.name){DateTime.now}
12
+
13
+ model.class_eval <<-EOS, __FILE__, __LINE__
14
+
15
+ def self.with_deleted
16
+ with_exclusive_scope(#{property.name.inspect}.not => nil) do
17
+ yield
18
+ end
19
+ end
20
+
21
+ def destroy
22
+ self.class.paranoid_properties.each do |name, blk|
23
+ attribute_set(name, blk.call(self))
24
+ end
25
+ save
26
+ end
27
+ EOS
28
+
29
+ model.default_scope(repository.name).update(property.name => nil)
30
+ end
31
+ end # class ParanoidDateTime
32
+ end # module Types
33
+ end # module DataMapper
@@ -0,0 +1,9 @@
1
+ # FIXME: can we alias this to the class Text if it isn't already defined?
2
+ module DataMapper
3
+ module Types
4
+ class Serial < DataMapper::Type
5
+ primitive Integer
6
+ serial true
7
+ end # class Text
8
+ end # module Types
9
+ end # module DataMapper
@@ -0,0 +1,10 @@
1
+ # FIXME: can we alias this to the class Text if it isn't already defined?
2
+ module DataMapper
3
+ module Types
4
+ class Text < DataMapper::Type
5
+ primitive String
6
+ size 65535
7
+ lazy true
8
+ end # class Text
9
+ end # module Types
10
+ end # module DataMapper
@@ -0,0 +1,3 @@
1
+ module DataMapper
2
+ VERSION = '0.9.6' unless defined?(DataMapper::VERSION)
3
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ rake spec:unit
3
+ ADAPTER=sqlite3 rake spec:integration
4
+ ADAPTER=mysql rake spec:integration
5
+ ADAPTER=postgres rake spec:integration
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'dm-core')
4
+
5
+ require 'rubygems'
6
+ require 'ftools'
7
+
8
+ # sudo gem install rbench
9
+ # OR git clone git://github.com/somebee/rbench.git , rake install
10
+ gem 'rbench', '>=0.2.2'
11
+ require 'rbench'
12
+
13
+ gem 'faker', '>=0.3.1'
14
+ require 'faker'
15
+
16
+ gem 'activerecord', '>=2.1.0'
17
+ require 'active_record'
18
+
19
+ socket_file = Pathname.glob(%w[
20
+ /opt/local/var/run/mysql5/mysqld.sock
21
+ tmp/mysqld.sock
22
+ /tmp/mysqld.sock
23
+ tmp/mysql.sock
24
+ /tmp/mysql.sock
25
+ /var/mysql/mysql.sock
26
+ /var/run/mysqld/mysqld.sock
27
+ ]).find { |path| path.socket? }
28
+
29
+ configuration_options = {
30
+ :adapter => 'mysql',
31
+ :username => 'root',
32
+ :password => '',
33
+ :database => 'data_mapper_1',
34
+ }
35
+
36
+ configuration_options[:socket] = socket_file unless socket_file.nil?
37
+
38
+ log_dir = DataMapper.root / 'log'
39
+ log_dir.mkdir unless log_dir.directory?
40
+
41
+ DataMapper::Logger.new(log_dir / 'dm.log', :debug)
42
+ adapter = DataMapper.setup(:default, "mysql://root@localhost/data_mapper_1?socket=#{socket_file}")
43
+
44
+ if configuration_options[:adapter]
45
+ sqlfile = File.join(File.dirname(__FILE__),'..','tmp','perf.sql')
46
+ mysql_bin = %w[mysql mysql5].select{|bin| `which #{bin}`.length > 0 }
47
+ mysqldump_bin = %w[mysqldump mysqldump5].select{|bin| `which #{bin}`.length > 0 }
48
+ end
49
+
50
+ ActiveRecord::Base.logger = Logger.new(log_dir / 'ar.log')
51
+ ActiveRecord::Base.logger.level = 0
52
+
53
+ ActiveRecord::Base.establish_connection(configuration_options)
54
+
55
+ class ARExhibit < ActiveRecord::Base #:nodoc:
56
+ set_table_name 'exhibits'
57
+ end
58
+
59
+ ARExhibit.find_by_sql('SELECT 1')
60
+
61
+ class Exhibit
62
+ include DataMapper::Resource
63
+
64
+ property :id, Serial
65
+ property :name, String
66
+ property :zoo_id, Integer
67
+ property :notes, Text, :lazy => true
68
+ property :created_on, Date
69
+ # property :updated_at, DateTime
70
+ end
71
+
72
+ touch_attributes = lambda do |exhibits|
73
+ [*exhibits].each do |exhibit|
74
+ exhibit.id
75
+ exhibit.name
76
+ exhibit.created_on
77
+ # exhibit.updated_at
78
+ end
79
+ end
80
+
81
+
82
+ c = configuration_options
83
+
84
+ if sqlfile && File.exists?(sqlfile)
85
+ puts "Found data-file. Importing from #{sqlfile}"
86
+ #adapter.execute("LOAD DATA LOCAL INFILE '#{sqlfile}' INTO TABLE exhibits")
87
+ `#{mysql_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} < #{sqlfile}`
88
+ else
89
+
90
+ Exhibit.auto_migrate!
91
+
92
+ exhibits = []
93
+ # pre-compute the insert statements and fake data compilation,
94
+ # so the benchmarks below show the actual runtime for the execute
95
+ # method, minus the setup steps
96
+ 10_000.times do
97
+ exhibits << [
98
+ 'INSERT INTO `exhibits` (`name`, `zoo_id`, `notes`, `created_on`) VALUES (?, ?, ?, ?)',
99
+ Faker::Company.name,
100
+ rand(10).ceil,
101
+ Faker::Lorem.paragraphs.join($/),
102
+ Date.today
103
+ ]
104
+ end
105
+ 10_000.times { |i| adapter.execute(*exhibits.at(i)) }
106
+
107
+ if sqlfile
108
+ answer = nil
109
+ until answer && answer[/^$|y|yes|n|no/]
110
+ print("Would you like to dump data into tmp/perf.sql (for faster setup)? [Yn]");
111
+ STDOUT.flush
112
+ answer = gets
113
+ end
114
+
115
+ if answer[/^$|y|yes/]
116
+ File.makedirs(File.dirname(sqlfile))
117
+ #adapter.execute("SELECT * INTO OUTFILE '#{sqlfile}' FROM exhibits;")
118
+ `#{mysqldump_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} exhibits > #{sqlfile}`
119
+ puts "File saved\n"
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ TIMES = ENV['x'] ? ENV['x'].to_i : 10_000
126
+
127
+ puts "You can specify how many times you want to run the benchmarks with rake:perf x=(number)"
128
+ puts "Some tasks will be run 10 and 1000 times less than (number)"
129
+ puts "Benchmarks will now run #{TIMES} times"
130
+
131
+ RBench.run(TIMES) do
132
+
133
+ column :times
134
+ column :dm, :title => "DM 0.9.4"
135
+ column :ar, :title => "AR 2.1"
136
+ column :diff, :compare => [:dm,:ar]
137
+
138
+ report "Model.new (instantiation)" do
139
+ dm { Exhibit.new }
140
+ ar { ARExhibit.new }
141
+ end
142
+
143
+ report "Model.new (setting attributes)" do
144
+ attrs = {:name => 'sam', :zoo_id => 1}
145
+ dm { Exhibit.new(attrs) }
146
+ ar { ARExhibit.new(attrs) }
147
+ end
148
+
149
+ report "Model.get specific (not cached)" do
150
+ dm { touch_attributes[Exhibit.get(1)] }
151
+ ActiveRecord::Base.uncached { ar { touch_attributes[ARExhibit.find(1)] } }
152
+ end
153
+
154
+ report "Model.get specific (cached)" do
155
+ Exhibit.repository(:default) { dm { touch_attributes[Exhibit.get(1)] } }
156
+ ActiveRecord::Base.cache { ar { touch_attributes[ARExhibit.find(1)] } }
157
+ end
158
+
159
+ report "Model.first" do
160
+ dm { touch_attributes[Exhibit.first] }
161
+ ar { touch_attributes[ARExhibit.first] }
162
+ end
163
+
164
+ report "Model.all limit(100)", TIMES / 10 do
165
+ dm { touch_attributes[Exhibit.all(:limit => 100)] }
166
+ ar { touch_attributes[ARExhibit.find(:all, :limit => 100)] }
167
+ end
168
+
169
+ report "Model.all limit(10,000)", TIMES / 1000 do
170
+ dm { touch_attributes[Exhibit.all(:limit => 10_000)] }
171
+ ar { touch_attributes[ARExhibit.find(:all, :limit => 10_000)] }
172
+ end
173
+
174
+ create_exhibit = {
175
+ :name => Faker::Company.name,
176
+ :zoo_id => rand(10).ceil,
177
+ :notes => Faker::Lorem.paragraphs.join($/),
178
+ :created_on => Date.today
179
+ }
180
+
181
+ report "Model.create" do
182
+ dm { Exhibit.create(create_exhibit) }
183
+ ar { ARExhibit.create(create_exhibit) }
184
+ end
185
+
186
+ report "Resource#update" do
187
+ dm { e = Exhibit.get(1); e.name = 'bob'; e.save }
188
+ ar { e = ARExhibit.find(1); e.name = 'bob'; e.save }
189
+ end
190
+
191
+ report "Resource#destroy" do
192
+ dm { Exhibit.first.destroy }
193
+ ar { ARExhibit.first.destroy }
194
+ end
195
+
196
+ summary "Total"
197
+
198
+ end
199
+
200
+ connection = adapter.send(:create_connection)
201
+ command = connection.create_command("DROP TABLE exhibits")
202
+ command.execute_non_query rescue nil
203
+ connection.close