dm-reflection 0.0.5

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.
@@ -0,0 +1,326 @@
1
+ module DataMapper
2
+ module Reflection
3
+ module Builders
4
+ module Source
5
+
6
+ def to_ruby
7
+ Model.to_ruby(self)
8
+ end
9
+
10
+ class Model
11
+
12
+ attr_accessor :model
13
+
14
+ def self.to_ruby(model)
15
+ builder = new(model)
16
+ raise DataMapper::IncompleteModelError unless builder.complete_model?
17
+ builder.to_ruby
18
+ end
19
+
20
+
21
+ def to_ruby
22
+ ruby = "class #{model}\n"
23
+ ruby += "\n"
24
+ ruby += " include DataMapper::Resource\n"
25
+ property_definitions(key_properties).each do |key_definition|
26
+ ruby += "\n #{key_definition}"
27
+ end
28
+ ruby += "\n"
29
+ property_definitions(foreign_key_properties).each do |foreign_key_definition|
30
+ ruby += "\n #{foreign_key_definition}"
31
+ end
32
+ property_definitions(regular_properties).each do |property_definition|
33
+ ruby += "\n #{property_definition}"
34
+ end
35
+ ruby += "\n"
36
+ relationship_definitions(many_to_one_relationships).each do |relationship_definition|
37
+ ruby += "\n #{relationship_definition}"
38
+ end
39
+ relationship_definitions(one_to_one_relationships).each do |relationship_definition|
40
+ ruby += "\n #{relationship_definition}"
41
+ end
42
+ relationship_definitions(one_to_many_relationships).each do |relationship_definition|
43
+ ruby += "\n #{relationship_definition}"
44
+ end
45
+ relationship_definitions(many_to_many_relationships).each do |relationship_definition|
46
+ ruby += "\n #{relationship_definition}"
47
+ end
48
+ ruby += "\n"
49
+ ruby += "\nend\n"
50
+ end
51
+
52
+
53
+
54
+ def key_properties
55
+ model.key
56
+ end
57
+
58
+ def foreign_key_properties
59
+ []
60
+ end
61
+
62
+ def regular_properties
63
+ model.properties - model.key - foreign_key_properties
64
+ end
65
+
66
+
67
+ def many_to_one_relationships(repository = :default)
68
+ model.relationships(repository).select do |_, relationship|
69
+ relationship.is_a?(DataMapper::Associations::ManyToOne::Relationship)
70
+ end
71
+ end
72
+
73
+ def one_to_one_relationships(repository = :default)
74
+ model.relationships(repository).select do |_, relationship|
75
+ relationship.is_a?(DataMapper::Associations::OneToOne::Relationship)
76
+ end
77
+ end
78
+
79
+ def one_to_many_relationships(repository = :default)
80
+ model.relationships(repository).select do |_, relationship|
81
+ relationship.is_a?(DataMapper::Associations::OneToMany::Relationship) &&
82
+ !relationship.is_a?(DataMapper::Associations::ManyToMany::Relationship)
83
+ end
84
+ end
85
+
86
+ def many_to_many_relationships(repository = :default)
87
+ model.relationships(repository).select do |_, relationship|
88
+ relationship.is_a?(DataMapper::Associations::ManyToMany::Relationship)
89
+ end
90
+ end
91
+
92
+
93
+ def complete_model?
94
+ key_properties.any?
95
+ end
96
+
97
+
98
+ private
99
+
100
+ def initialize(model)
101
+ @model = model
102
+ end
103
+
104
+ def property_definitions(properties)
105
+ properties.map { |property| Property.for(property).to_ruby }
106
+ end
107
+
108
+ def relationship_definitions(relationships)
109
+ relationships.map { |_, relationship| Relationship.for(relationship).to_ruby }
110
+ end
111
+
112
+ end
113
+
114
+
115
+ module OptionBuilder
116
+
117
+ def options
118
+ option_string(prioritized_options + rest_options)
119
+ end
120
+
121
+ def option_priorities
122
+ []
123
+ end
124
+
125
+ def prioritized_options
126
+ []
127
+ end
128
+
129
+ def rest_options
130
+ backend_options.select { |k,v| !option_priorities.include?(k) && !irrelevant_options.include?(k) }
131
+ end
132
+
133
+ def irrelevant_options
134
+ []
135
+ end
136
+
137
+ def backend_options
138
+ @backend_options ||= backend.options.dup
139
+ end
140
+
141
+
142
+ private
143
+
144
+ def option_string(relevant_options)
145
+ relevant_options.map do |key, value|
146
+ ", #{key.inspect} => #{value == 'Resource' ? value : value.inspect}"
147
+ end.join
148
+ end
149
+
150
+ end
151
+
152
+
153
+ class Property
154
+
155
+ include OptionBuilder
156
+
157
+ attr_accessor :backend
158
+
159
+ def self.for(backend)
160
+ new(backend)
161
+ end
162
+
163
+ def to_ruby
164
+ "property :#{name}, #{type}#{options}"
165
+ end
166
+
167
+ def name
168
+ backend.name
169
+ end
170
+
171
+ def type
172
+ Extlib::Inflection.demodulize(backend.type)
173
+ end
174
+
175
+
176
+ def options
177
+ backend.type == DataMapper::Types::Serial ? '' : super
178
+ end
179
+
180
+ def option_priorities
181
+ [ :key, :required, :unique, :unique_index ]
182
+ end
183
+
184
+ def prioritized_options
185
+ option_priorities.inject([]) do |memo, name|
186
+ if name == :required
187
+ memo << [ name, backend_options[name] ] if backend_options[name] && !backend_options[:key]
188
+ else
189
+ memo << [ name, backend_options[name] ] if backend_options[name]
190
+ end
191
+ memo
192
+ end
193
+ end
194
+
195
+ def irrelevant_options
196
+ []
197
+ end
198
+
199
+ private
200
+
201
+ def initialize(backend)
202
+ @backend = backend
203
+ end
204
+
205
+ end
206
+
207
+ class Relationship
208
+
209
+ include OptionBuilder
210
+
211
+ attr_accessor :backend
212
+
213
+ def self.for(backend)
214
+ case backend
215
+ when DataMapper::Associations::ManyToOne::Relationship then ManyToOne. new(backend)
216
+ when DataMapper::Associations::OneToOne::Relationship then OneToOne. new(backend)
217
+ when DataMapper::Associations::OneToMany::Relationship then OneToMany. new(backend)
218
+ when DataMapper::Associations::ManyToMany::Relationship then ManyToMany.new(backend)
219
+ else
220
+ raise ArgumentError, "#{backend.class} is no valid datamapper relationship"
221
+ end
222
+ end
223
+
224
+ def to_ruby
225
+ "#{type} #{cardinality}, :#{name}#{options}"
226
+ end
227
+
228
+
229
+ def type
230
+ raise NotImplementedError
231
+ end
232
+
233
+ def name
234
+ backend.name
235
+ end
236
+
237
+
238
+ def cardinality
239
+ "#{min}..#{max}"
240
+ end
241
+
242
+ def min
243
+ backend.min
244
+ end
245
+
246
+ def max
247
+ backend.max
248
+ end
249
+
250
+
251
+ def option_priorities
252
+ [ :through, :constraint ]
253
+ end
254
+
255
+ def prioritized_options
256
+ option_priorities.inject([]) do |memo, name|
257
+ if name == :through && through = backend_options[:through]
258
+ value = through.is_a?(Symbol) ? through : Extlib::Inflection.demodulize(through)
259
+ memo << [ :through, value ]
260
+ end
261
+ memo
262
+ end
263
+ end
264
+
265
+ def irrelevant_options
266
+ [ :min, :max, :parent_repository_name, :child_repository_name ]
267
+ end
268
+
269
+ private
270
+
271
+ def initialize(backend)
272
+ @backend = backend
273
+ end
274
+
275
+ end
276
+
277
+ class ManyToOne < Relationship
278
+ def to_ruby
279
+ "#{type} :#{name}#{options}"
280
+ end
281
+ def type
282
+ :belongs_to
283
+ end
284
+ def cardinality
285
+ ''
286
+ end
287
+ end
288
+
289
+ class OneToOne < Relationship
290
+ def type
291
+ :has
292
+ end
293
+ def cardinality
294
+ min == 1 && max == 1 ? '1' : super
295
+ end
296
+ end
297
+
298
+ class OneToMany < Relationship
299
+ def type
300
+ :has
301
+ end
302
+ def cardinality
303
+ max_string = max == Infinity ? 'n' : max.to_s
304
+
305
+ if min == 0 || min == max
306
+ max_string
307
+ elsif max == Infinity
308
+ "#{min}..#{max_string}"
309
+ else
310
+ super
311
+ end
312
+ end
313
+ end
314
+
315
+ class ManyToMany < OneToMany
316
+ end
317
+
318
+
319
+ end
320
+ end
321
+ end
322
+
323
+ # Ensure activation when this file is required
324
+ Model.append_extensions(Reflection::Builders::Source)
325
+
326
+ end
@@ -0,0 +1,73 @@
1
+ module DataMapper
2
+ module Reflection
3
+ ##
4
+ # Main reflection method reflects models out of a repository.
5
+ # @param [Slug] repository is the key to the repository that will be reflected.
6
+ # @param [Constant] namespace is the namespace into which the reflected models will be added
7
+ # @param [Boolean] overwrite indicates the reflected models should replace existing models or not.
8
+ # @return [DataMapper::Model Array] the reflected models.
9
+ #
10
+ def self.reflect(repository, namespace = Object, overwrite = false)
11
+ adapter = DataMapper.repository(repository).adapter
12
+
13
+ models = []
14
+
15
+ adapter.get_storage_names.each do |storage_name|
16
+ namespace_parts = storage_name.split('__').map do |part|
17
+ Extlib::Inflection.classify(part)
18
+ end
19
+
20
+ model_name = namespace_parts.pop
21
+
22
+ namespace = if namespace_parts.any?
23
+ Object.make_module(namespace_parts.join('::'))
24
+ else
25
+ Object
26
+ end
27
+
28
+ next if namespace.const_defined?(model_name) && !overwrite
29
+
30
+ anonymous_model = DataMapper::Model.new do
31
+ class_eval <<-RUBY, __FILE__, __LINE__
32
+ storage_names[#{repository.inspect}]='#{storage_name}'
33
+ RUBY
34
+ unless repository == DataMapper::Repository.default_name
35
+ class_eval <<-RUBY, __FILE__, __LINE__
36
+ def self.default_repository_name
37
+ #{repository.inspect}
38
+ end
39
+ RUBY
40
+ end
41
+ end
42
+
43
+ model = namespace.const_set(model_name, anonymous_model)
44
+
45
+ adapter.get_properties(storage_name).each do |attribute|
46
+ attribute.delete_if { |k,v| v.nil? }
47
+ model.property(attribute.delete(:name).to_sym, attribute.delete(:type), attribute)
48
+ end
49
+
50
+ models << model
51
+ end
52
+ models
53
+ end
54
+ end # module Reflection
55
+
56
+ module Adapters
57
+ extendable do
58
+ ##
59
+ # Glue method that will register reflection extensions for adapters if the adapters are loaded.
60
+ #
61
+ # @param [Constant] const_name is the constant defined by the adapter.
62
+ #
63
+ # @api private
64
+ def const_added(const_name)
65
+ if DataMapper::Reflection.const_defined?(const_name)
66
+ adapter = const_get(const_name)
67
+ adapter.send(:include, DataMapper::Reflection.const_get(const_name))
68
+ end
69
+ super
70
+ end # const_added
71
+ end # extendable block
72
+ end # module Adapters
73
+ end # module DataMapper
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module Reflection
3
+ VERSION = '0.0.5'.freeze
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'dm-reflection/reflection'
2
+ require 'dm-reflection/adapters/sqlite3'
3
+ require 'dm-reflection/adapters/persevere'
4
+ require 'dm-reflection/adapters/mysql'
5
+ require 'dm-reflection/adapters/postgres'
6
+ require 'dm-reflection/builders/source_builder'
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ if ENV['ADAPTER'] == 'persevere'
4
+
5
+ POST = <<-RUBY
6
+ class Post
7
+
8
+ include DataMapper::Resource
9
+
10
+ property :id, Serial
11
+
12
+ property :created_at, DateTime
13
+ property :body, Text, :length => 65535, :lazy => true
14
+ property :updated_at, DateTime
15
+
16
+
17
+ end
18
+ RUBY
19
+
20
+ POST_COMMENT = <<-RUBY
21
+ class PostComment
22
+
23
+ include DataMapper::Resource
24
+
25
+ property :id, Serial
26
+
27
+ property :created_at, DateTime
28
+ property :body, Text, :length => 65535, :lazy => true
29
+ property :updated_at, DateTime
30
+ property :score, Integer
31
+ property :blog_post, DataMapper::Types::JsonReference, :reference => :Post
32
+
33
+
34
+ end
35
+ RUBY
36
+
37
+ PERSEVERE_REFLECTION_SOURCES = [ POST, POST_COMMENT ]
38
+
39
+ describe 'The Persevere DataMapper reflection module' do
40
+
41
+ before(:each) do
42
+ @adapter = repository(:default).adapter
43
+
44
+ PERSEVERE_REFLECTION_SOURCES.each { |source| eval(source) }
45
+
46
+ @models = {
47
+ :Post => POST,
48
+ :PostComment => POST_COMMENT,
49
+ }
50
+
51
+ @models.each_key { |model| Extlib::Inflection.constantize(model.to_s).auto_migrate! }
52
+ Post.send(:property, :comments, DataMapper::Types::JsonReferenceCollection, :reference => :PostComment)
53
+ Post.auto_upgrade!
54
+ @models.each_key { |model| remove_model_from_memory( Extlib::Inflection.constantize(model.to_s) ) }
55
+ end
56
+
57
+ after(:each) do
58
+ @models.each_key do |model_name|
59
+ next unless Object.const_defined?(model_name)
60
+ model = Extlib::Inflection.constantize(model_name.to_s)
61
+ remove_model_from_memory(model)
62
+ end
63
+ @models.each_key do |model_name|
64
+ next unless Object.const_defined?(model_name)
65
+ model = Extlib::Inflection.constantize(model_name.to_s)
66
+ remove_model_from_memory(model)
67
+ end
68
+ end
69
+
70
+ it "should return an array of tables" do
71
+ @adapter.get_storage_names.should be_kind_of(Array)
72
+ end
73
+
74
+ it "should return a table definition" do
75
+ result = @adapter.get_properties("post")
76
+ result.should be_kind_of(Array)
77
+ end
78
+
79
+ it "should reflect out JsonReference types" do
80
+ DataMapper::Reflection.reflect(:default)
81
+ PostComment.properties[:blog_post].type.should eql DataMapper::Types::JsonReference
82
+ Post.properties[:comments].type.should eql DataMapper::Types::JsonReferenceCollection
83
+ end
84
+
85
+ end
86
+ end
data/spec/rcov.opts ADDED
@@ -0,0 +1,6 @@
1
+ --exclude "spec"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
@@ -0,0 +1,108 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'dm-reflection/builders/source_builder'
3
+
4
+ BLOGPOST = <<-RUBY
5
+ class BlogPost
6
+
7
+ include DataMapper::Resource
8
+
9
+ property :id, Serial
10
+
11
+ property :created_at, DateTime
12
+ property :body, Text, :length => 65535, :lazy => true
13
+ property :updated_at, DateTime
14
+
15
+
16
+ end
17
+ RUBY
18
+
19
+ COMMENT = <<-RUBY
20
+ class Comment
21
+
22
+ include DataMapper::Resource
23
+
24
+ property :id, Serial
25
+
26
+ property :created_at, DateTime
27
+ property :body, Text, :length => 65535, :lazy => true
28
+ property :updated_at, DateTime
29
+ property :score, Integer
30
+
31
+
32
+ end
33
+ RUBY
34
+
35
+ REFLECTION_SOURCES = [ BLOGPOST, COMMENT ]
36
+
37
+ describe 'The DataMapper reflection module' do
38
+
39
+ before(:each) do
40
+ @adapter = repository(:default).adapter
41
+
42
+ REFLECTION_SOURCES.each { |source| eval(source) }
43
+
44
+ @models = {
45
+ :BlogPost => BLOGPOST,
46
+ :Comment => COMMENT,
47
+ }
48
+
49
+ @models.each_key { |model| Extlib::Inflection.constantize(model.to_s).auto_migrate! }
50
+ @models.each_key { |model| remove_model_from_memory( Extlib::Inflection.constantize(model.to_s) ) }
51
+ end
52
+
53
+ after(:each) do
54
+ @models.each_key do |model_name|
55
+ next unless Object.const_defined?(model_name)
56
+ model = Extlib::Inflection.constantize(model_name.to_s)
57
+ model.auto_migrate_down!
58
+ remove_model_from_memory(model)
59
+ end
60
+ end
61
+
62
+ describe 'repository(:name).reflect' do
63
+ it 'should reflect all the models in a repository' do
64
+ # Reflect the models back into memory.
65
+ DataMapper::Reflection.reflect(:default)
66
+
67
+ # Iterate through each model in memory and verify the source is the same as the original.
68
+ # using model.to_ruby
69
+ @models.each_pair do |model_name, source|
70
+ model = Extlib::Inflection.constantize(model_name.to_s)
71
+ reflected_source = model.to_ruby
72
+ model.to_ruby.should == source
73
+ end
74
+ end
75
+ end
76
+
77
+ describe 'reflected model instance' do
78
+ it 'should respond to default_repository_name and return the correct repo for a reflected model' do
79
+ # Reflect the models back into memory.
80
+ DataMapper::Reflection.reflect(:default)
81
+
82
+ @models.each_key do |model_name|
83
+ model = Extlib::Inflection.constantize(model_name.to_s)
84
+ model.should respond_to(:default_repository_name)
85
+ model.default_repository_name.should == :default
86
+ end
87
+ end
88
+ end
89
+
90
+ describe 'reflective adapter' do
91
+ it 'should respond to get_storage_names and return an array of models' do
92
+ @adapter.should respond_to(:get_storage_names)
93
+ @adapter.get_storage_names.should be_kind_of(Array)
94
+ end
95
+
96
+ it "should respond to get_properties and return an array of properties" do
97
+ @adapter.should respond_to(:get_properties)
98
+ tables = @adapter.get_storage_names
99
+ properties = @adapter.get_properties(tables[0])
100
+ properties.should be_kind_of(Array)
101
+ a_property = properties[0]
102
+ a_property.should be_kind_of(Hash)
103
+ a_property.keys.should include(:name)
104
+ a_property.keys.should include(:type)
105
+ end
106
+ end
107
+
108
+ end