codders-dataset 1.3.2.1

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.

Potentially problematic release.


This version of codders-dataset might be problematic. Click here for more details.

Files changed (47) hide show
  1. data/CHANGELOG +67 -0
  2. data/LICENSE +19 -0
  3. data/README +111 -0
  4. data/Rakefile +31 -0
  5. data/TODO +15 -0
  6. data/VERSION.yml +4 -0
  7. data/lib/dataset.rb +130 -0
  8. data/lib/dataset/base.rb +157 -0
  9. data/lib/dataset/collection.rb +19 -0
  10. data/lib/dataset/database/base.rb +43 -0
  11. data/lib/dataset/database/mysql.rb +34 -0
  12. data/lib/dataset/database/postgresql.rb +34 -0
  13. data/lib/dataset/database/sqlite3.rb +32 -0
  14. data/lib/dataset/extensions/cucumber.rb +20 -0
  15. data/lib/dataset/extensions/rspec.rb +21 -0
  16. data/lib/dataset/extensions/test_unit.rb +60 -0
  17. data/lib/dataset/instance_methods.rb +10 -0
  18. data/lib/dataset/load.rb +47 -0
  19. data/lib/dataset/record/fixture.rb +73 -0
  20. data/lib/dataset/record/heirarchy.rb +65 -0
  21. data/lib/dataset/record/meta.rb +30 -0
  22. data/lib/dataset/record/model.rb +50 -0
  23. data/lib/dataset/resolver.rb +110 -0
  24. data/lib/dataset/session.rb +51 -0
  25. data/lib/dataset/session_binding.rb +319 -0
  26. data/lib/dataset/version.rb +9 -0
  27. data/plugit/descriptor.rb +25 -0
  28. data/spec/dataset/cucumber_spec.rb +54 -0
  29. data/spec/dataset/database/base_spec.rb +21 -0
  30. data/spec/dataset/record/heirarchy_spec.rb +14 -0
  31. data/spec/dataset/resolver_spec.rb +110 -0
  32. data/spec/dataset/rspec_spec.rb +133 -0
  33. data/spec/dataset/session_binding_spec.rb +203 -0
  34. data/spec/dataset/session_spec.rb +299 -0
  35. data/spec/dataset/test_unit_spec.rb +210 -0
  36. data/spec/fixtures/datasets/constant_not_defined.rb +0 -0
  37. data/spec/fixtures/datasets/ending_with_dataset.rb +2 -0
  38. data/spec/fixtures/datasets/exact_name.rb +2 -0
  39. data/spec/fixtures/datasets/not_a_dataset_base.rb +2 -0
  40. data/spec/fixtures/more_datasets/in_another_directory.rb +2 -0
  41. data/spec/models.rb +18 -0
  42. data/spec/schema.rb +26 -0
  43. data/spec/spec_helper.rb +47 -0
  44. data/spec/stubs/mini_rails.rb +18 -0
  45. data/spec/stubs/test_help.rb +1 -0
  46. data/tasks/dataset.rake +19 -0
  47. metadata +164 -0
@@ -0,0 +1,30 @@
1
+ module Dataset
2
+ module Record # :nodoc:
3
+
4
+ # A mechanism to cache information about an ActiveRecord class to speed
5
+ # things up a bit for insertions, finds, and method generation.
6
+ class Meta # :nodoc:
7
+ attr_reader :heirarchy, :class_name, :record_class
8
+
9
+ # Provides information necessary to insert STI classes correctly for
10
+ # later reading.
11
+ delegate :name, :sti_name, :to => :record_class
12
+ delegate :inheritance_column, :table_name, :timestamp_columns, :to => :heirarchy
13
+
14
+ def initialize(heirarchy, record_class)
15
+ @heirarchy = heirarchy
16
+ @record_class = record_class
17
+ @class_name = record_class.name
18
+ end
19
+
20
+ def inheriting_record?
21
+ !record_class.descends_from_active_record?
22
+ end
23
+
24
+ def to_s
25
+ "#<RecordMeta: #{table_name}>"
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ module Dataset
2
+ module Record # :nodoc:
3
+
4
+ class Model # :nodoc:
5
+ attr_reader :attributes, :model, :meta, :symbolic_name, :session_binding
6
+
7
+ def initialize(meta, attributes, symbolic_name, session_binding)
8
+ @meta = meta
9
+ @attributes = attributes.stringify_keys
10
+ @symbolic_name = symbolic_name || object_id
11
+ @session_binding = session_binding
12
+ end
13
+
14
+ def record_class
15
+ meta.record_class
16
+ end
17
+
18
+ def id
19
+ model.id
20
+ end
21
+
22
+ def create
23
+ model = to_model
24
+ model.save!
25
+ model
26
+ end
27
+
28
+ def to_hash
29
+ to_model.attributes
30
+ end
31
+
32
+ def to_model
33
+ @model ||= begin
34
+ m = meta.record_class.new
35
+ attributes.each do |k,v|
36
+ if reflection = record_class.reflect_on_association(k.to_sym)
37
+ case v
38
+ when Symbol
39
+ v = session_binding.find_model(reflection.klass, v)
40
+ end
41
+ end
42
+ m.send "#{k}=", v
43
+ end
44
+ m
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,110 @@
1
+ module Dataset
2
+ # An error raised when a dataset class cannot be found.
3
+ #
4
+ class DatasetNotFound < StandardError
5
+ end
6
+
7
+ # A dataset may be referenced as a class or as a name. A Dataset::Resolver
8
+ # will take an identifier, whether a class or a name, and return the class.
9
+ #
10
+ class Resolver
11
+ cattr_accessor :default
12
+
13
+ def identifiers
14
+ @identifiers ||= {}
15
+ end
16
+
17
+ # Attempt to convert a name to a constant. With the identifier :people, it
18
+ # will search for 'PeopleDataset', then 'People'.
19
+ #
20
+ def resolve(identifier)
21
+ return identifier if identifier.is_a?(Class)
22
+ if constant = identifiers[identifier]
23
+ return constant
24
+ end
25
+
26
+ constant = resolve_class(identifier)
27
+ unless constant
28
+ constant = resolve_identifier(identifier)
29
+ end
30
+ identifiers[identifier] = constant
31
+ end
32
+
33
+ protected
34
+ def resolve_identifier(identifier) # :nodoc:
35
+ constant = resolve_class(identifier)
36
+ unless constant
37
+ raise Dataset::DatasetNotFound, "Could not find a dataset '#{identifier.to_s.camelize}' or '#{identifier.to_s.camelize + suffix}'."
38
+ end
39
+ constant
40
+ end
41
+
42
+ def resolve_class(identifier)
43
+ names = [identifier.to_s.camelize, identifier.to_s.camelize + suffix]
44
+ constant = resolve_these(names.reverse)
45
+ if constant && constant.superclass != ::Dataset::Base
46
+ raise Dataset::DatasetNotFound, "Found a class '#{constant.name}', but it does not subclass 'Dataset::Base'."
47
+ end
48
+ constant
49
+ end
50
+
51
+ def resolve_these(names) # :nodoc:
52
+ names.each do |name|
53
+ constant = name.constantize rescue nil
54
+ return constant if constant && constant.is_a?(Class)
55
+ end
56
+ nil
57
+ end
58
+
59
+ def suffix # :nodoc:
60
+ @suffix ||= 'Dataset'
61
+ end
62
+ end
63
+
64
+ # Resolves a dataset by looking for a file in the provided directory path
65
+ # that has a name matching the identifier. Of course, should the identifier
66
+ # be a class already, it is simply returned.
67
+ #
68
+ class DirectoryResolver < Resolver
69
+ def initialize(*paths)
70
+ @paths = paths
71
+ end
72
+
73
+ def <<(path)
74
+ @paths << path
75
+ end
76
+
77
+ protected
78
+ def resolve_identifier(identifier) # :nodoc:
79
+ @paths.each do |path|
80
+ file = File.join(path, identifier.to_s)
81
+ unless File.exists?(file + '.rb')
82
+ file = file + '_' + file_suffix
83
+ next unless File.exists?(file + '.rb')
84
+ end
85
+ require file
86
+ begin
87
+ return super
88
+ rescue Dataset::DatasetNotFound => dnf
89
+ if dnf.message =~ /\ACould not find/
90
+ raise Dataset::DatasetNotFound, "Found the dataset file '#{file + '.rb'}', but it did not define #{dnf.message.sub('Could not find ', '')}"
91
+ else
92
+ raise Dataset::DatasetNotFound, "Found the dataset file '#{file + '.rb'}' and a class #{dnf.message.sub('Found a class ', '')}"
93
+ end
94
+ end
95
+ end
96
+ raise DatasetNotFound, "Could not find a dataset file in #{@paths.inspect} having the name '#{identifier}.rb' or '#{identifier}_#{file_suffix}.rb'."
97
+ end
98
+
99
+ def file_suffix # :nodoc:
100
+ @file_suffix ||= suffix.downcase
101
+ end
102
+ end
103
+
104
+ # The default resolver, used by the Dataset::Sessions that aren't given a
105
+ # different instance. You can set this to something else in your
106
+ # test/spec_helper.
107
+ #
108
+ Resolver.default = Resolver.new
109
+
110
+ end
@@ -0,0 +1,51 @@
1
+ module Dataset
2
+ class Session # :nodoc:
3
+ attr_accessor :dataset_resolver
4
+
5
+ def initialize(database, dataset_resolver = Resolver.default)
6
+ @database = database
7
+ @dataset_resolver = dataset_resolver
8
+ @datasets = Hash.new
9
+ @load_stack = []
10
+ end
11
+
12
+ def add_dataset(test_class, dataset_identifier)
13
+ dataset = dataset_resolver.resolve(dataset_identifier)
14
+ if dataset.used_datasets
15
+ dataset.used_datasets.each { |used_dataset| self.add_dataset(test_class, used_dataset) }
16
+ end
17
+ datasets_for(test_class) << dataset
18
+ end
19
+
20
+ def datasets_for(test_class)
21
+ if test_class.superclass
22
+ @datasets[test_class] ||= Collection.new(datasets_for(test_class.superclass) || [])
23
+ end
24
+ end
25
+
26
+ def load_datasets_for(test_class)
27
+ datasets = datasets_for(test_class)
28
+ if last_load = @load_stack.last
29
+ if last_load.datasets == datasets
30
+ current_load = Reload.new(last_load)
31
+ elsif last_load.datasets.subset?(datasets)
32
+ @database.capture(last_load.datasets)
33
+ current_load = Load.new(datasets, last_load.dataset_binding)
34
+ current_load.execute(last_load.datasets, @dataset_resolver)
35
+ @load_stack.push(current_load)
36
+ else
37
+ @load_stack.pop
38
+ last_load = @load_stack.last
39
+ @database.restore(last_load.datasets) if last_load
40
+ current_load = load_datasets_for(test_class)
41
+ end
42
+ else
43
+ @database.clear
44
+ current_load = Load.new(datasets, @database)
45
+ current_load.execute([], @dataset_resolver)
46
+ @load_stack.push(current_load)
47
+ end
48
+ current_load
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,319 @@
1
+ module Dataset
2
+ # An error that will be raised when an attempt is made to load a named model
3
+ # that doesn't exist. For example, if you do people(:jenny), and yet no
4
+ # record was ever created with the symbolic name :jenny, this error will be
5
+ # raised.
6
+ #
7
+ class RecordNotFound < StandardError
8
+ def initialize(record_heirarchy, symbolic_name)
9
+ super "There is no '#{record_heirarchy.base_class.name}' found for the symbolic name ':#{symbolic_name}'."
10
+ end
11
+ end
12
+
13
+ # Whenever you use Dataset::RecordMethods, you will get finder methods in
14
+ # your tests that help you load instances of the records you have created
15
+ # (or named models).
16
+ #
17
+ # create_record :person, :jimmy, :name => 'Jimmy'
18
+ # person_id(:jimmy) => The id was captured from create_record
19
+ # people(:jimmy) => The same as Jimmy.find(person_id(:jimmy))
20
+ #
21
+ # The methods will not exist in a test unless it utilizes a dataset (or
22
+ # defines one itself through the block technique) that creates a record for
23
+ # the type.
24
+ #
25
+ # You may also pass multiple names to these methods, which will have them
26
+ # return an array of values.
27
+ #
28
+ # people(:jimmy, :jane, :jeff) => [<# Person :name => 'Jimmy'>, <# Person :name => 'Jane'>, <# Person :name => 'Jeff'>]
29
+ # person_id(:jimmy, :jane, :jeff) => [1, 2, 3]
30
+ #
31
+ # NOTE the plurality of the instance finder, versus the singularity of the
32
+ # id finder.
33
+ #
34
+ # == Single Table Inheritence
35
+ #
36
+ # class Person < ActiveRecord::Base; end
37
+ # class User < Person; end
38
+ #
39
+ # create_record :user, :bobby, :name => 'Bobby'
40
+ #
41
+ # people(:bobby) OR users(:bobby)
42
+ #
43
+ module ModelFinders
44
+ def create_finders(record_meta) # :nodoc:
45
+ @finders_generated ||= []
46
+ heirarchy_finders_hash = record_meta.heirarchy.model_finder_names.join('').hash
47
+ return if @finders_generated.include?(heirarchy_finders_hash)
48
+
49
+ record_meta.heirarchy.model_finder_names.each do |finder_name|
50
+ unless instance_methods.include?(finder_name)
51
+ define_method finder_name do |*symbolic_names|
52
+ names = Array(symbolic_names)
53
+ models = names.inject([]) do |c,n|
54
+ c << dataset_session_binding.find_model(record_meta, n); c
55
+ end
56
+ names.size == 1 ? models.first : models
57
+ end
58
+ end
59
+ end
60
+
61
+ record_meta.heirarchy.id_finder_names.each do |finder_name|
62
+ unless instance_methods.include?(finder_name)
63
+ define_method finder_name do |*symbolic_names|
64
+ names = Array(symbolic_names)
65
+ ids = names.inject([]) do |c,n|
66
+ c << dataset_session_binding.find_id(record_meta, n); c
67
+ end
68
+ names.size == 1 ? ids.first : ids
69
+ end
70
+ end
71
+ end
72
+
73
+ @finders_generated << heirarchy_finders_hash
74
+ end
75
+ end
76
+
77
+ # Any Dataset::Base subclass, dataset block, or test method in a
78
+ # dataset-using test context (including setup/teardown/before/after) may
79
+ # create and access models through these methods. Note that you should use
80
+ # Dataset::ModelFinders if you can for finding your created data.
81
+ #
82
+ module RecordMethods
83
+
84
+ # Similar to old fashioned fixtures, this will do a direct database
85
+ # insert, without running any validations or preventing you from writing
86
+ # attr_protected attributes. Very nice for speed, but kind of a pain if
87
+ # you have complex structures or hard to keep right validations.
88
+ #
89
+ # create_record :type, :symbolic_name, :attr1 => 'value', :attr2 => 'value', :etc => 'etc'
90
+ #
91
+ # The _symbolic_name_ is an optional parameter. You may replace _type_
92
+ # with an ActiveRecord::Base subclass or anything that works with:
93
+ #
94
+ # to_s.classify.constantize
95
+ #
96
+ # The id of the model will be a hash of the symbolic name.
97
+ #
98
+ def create_record(*args)
99
+ dataset_session_binding.create_record(*args)
100
+ end
101
+
102
+ # This will instantiate your model class and assign each attribute WITHOUT
103
+ # using mass assignment. Validations will be run. Very nice for complex
104
+ # structures or hard to keep right validations, but potentially a bit
105
+ # slower, since it runs through all that ActiveRecord code.
106
+ #
107
+ # create_model :type, :symbolic_name, :attr1 => 'value', :attr2 => 'value', :etc => 'etc'
108
+ #
109
+ # The _symbolic_name_ is an optional parameter. You may replace _type_
110
+ # with an ActiveRecord::Base subclass or anything that works with:
111
+ #
112
+ # to_s.classify.constantize
113
+ #
114
+ # The id of the record will be kept from the instance that is saved.
115
+ #
116
+ def create_model(*args)
117
+ dataset_session_binding.create_model(*args)
118
+ end
119
+
120
+ # Dataset will track each of the records it creates by symbolic name to
121
+ # id. When you need the id of a record, there is no need to go to the
122
+ # database.
123
+ #
124
+ # find_id :person, :bobby => 23425234
125
+ #
126
+ # You may pass one name or many, with many returning an Array of ids.
127
+ #
128
+ def find_id(*args)
129
+ dataset_session_binding.find_id(*args)
130
+ end
131
+
132
+ # Dataset will track each of the records it creates by symbolic name to
133
+ # id. When you need an instance of a record, the stored id will be used to
134
+ # do the fastest lookup possible: Person.find(23425234).
135
+ #
136
+ # find_model :person, :bobby => <#Person :id => 23425234, :name => 'Bobby'>
137
+ #
138
+ # You may pass one name or many, with many returning an Array of
139
+ # instances.
140
+ #
141
+ def find_model(*args)
142
+ dataset_session_binding.find_model(*args)
143
+ end
144
+
145
+ # This is a great help when you want to create records in a custom helper
146
+ # method, then make it and maybe things associated to it available to
147
+ # tests through the Dataset::ModelFinders.
148
+ #
149
+ # thingy = create_very_complex_thingy_and_stuff
150
+ # name_model thingy, :thingy_bob
151
+ # name_model thingy.part, :thingy_part
152
+ #
153
+ # In tests:
154
+ #
155
+ # thingies(:thingy_bob)
156
+ # parts(:thingy_part)
157
+ #
158
+ def name_model(*args)
159
+ dataset_session_binding.name_model(*args)
160
+ end
161
+
162
+ # Converts string names into symbols for use in naming models
163
+ #
164
+ # name_to_sym 'my name' => :my_name
165
+ # name_to_sym 'RPaul' => :r_paul
166
+ #
167
+ def name_to_sym(name)
168
+ dataset_session_binding.name_to_sym(name)
169
+ end
170
+ end
171
+
172
+ class SessionBinding # :nodoc:
173
+ attr_reader :database, :parent_binding
174
+ attr_reader :model_finders, :record_methods
175
+ attr_reader :block_variables
176
+
177
+ def initialize(database_or_parent_binding)
178
+ @id_cache = Hash.new {|h,k| h[k] = {}}
179
+ @record_methods = new_record_methods_module
180
+ @model_finders = new_model_finders_module
181
+ @block_variables = Hash.new
182
+
183
+ case database_or_parent_binding
184
+ when Dataset::SessionBinding
185
+ @parent_binding = database_or_parent_binding
186
+ @database = parent_binding.database
187
+ @model_finders.module_eval { include database_or_parent_binding.model_finders }
188
+ @block_variables.update(database_or_parent_binding.block_variables)
189
+ else
190
+ @database = database_or_parent_binding
191
+ end
192
+ end
193
+
194
+ def copy_block_variables(dataset_block)
195
+ dataset_block.instance_variables.each do |name|
196
+ self.block_variables[name] = dataset_block.instance_variable_get(name)
197
+ end
198
+ end
199
+
200
+ def create_model(record_type, *args)
201
+ insert(Dataset::Record::Model, record_type, *args)
202
+ end
203
+
204
+ def create_record(record_type, *args)
205
+ insert(Dataset::Record::Fixture, record_type, *args)
206
+ end
207
+
208
+ def find_id(record_type_or_meta, symbolic_name)
209
+ record_meta = record_meta_for_type(record_type_or_meta)
210
+ heirarchy = record_meta.heirarchy
211
+ if local_id = @id_cache[heirarchy.id_cache_key][symbolic_name]
212
+ local_id
213
+ elsif !parent_binding.nil?
214
+ parent_binding.find_id record_meta, symbolic_name
215
+ else
216
+ raise RecordNotFound.new(heirarchy, symbolic_name)
217
+ end
218
+ end
219
+
220
+ def find_model(record_type_or_meta, symbolic_name)
221
+ record_meta = record_meta_for_type(record_type_or_meta)
222
+ heirarchy = record_meta.heirarchy
223
+ if local_id = @id_cache[heirarchy.id_cache_key][symbolic_name]
224
+ heirarchy.base_class.find local_id
225
+ elsif !parent_binding.nil?
226
+ parent_binding.find_model record_meta, symbolic_name
227
+ else
228
+ raise RecordNotFound.new(heirarchy, symbolic_name)
229
+ end
230
+ end
231
+
232
+ def install_block_variables(target)
233
+ block_variables.each do |k,v|
234
+ target.instance_variable_set(k,v)
235
+ end
236
+ end
237
+
238
+ def name_model(record, symbolic_name)
239
+ record_meta = database.record_meta(record.class)
240
+ @model_finders.create_finders(record_meta)
241
+ @id_cache[record_meta.heirarchy.id_cache_key][symbolic_name] = record.id
242
+ record
243
+ end
244
+
245
+ def record_meta_for_type(record_type)
246
+ record_type.is_a?(Record::Meta) ? record_type : begin
247
+ record_class = resolve_record_class(record_type)
248
+ database.record_meta(record_class)
249
+ end
250
+ end
251
+
252
+ def name_to_sym(name)
253
+ name.to_s.underscore.gsub("'", "").gsub("\"", "").gsub(" ", "_").to_sym if name
254
+ end
255
+
256
+ protected
257
+ def insert(dataset_record_class, record_type, *args)
258
+ symbolic_name, attributes = extract_creation_arguments args
259
+ record_meta = record_meta_for_type(record_type)
260
+ record = dataset_record_class.new(record_meta, attributes, symbolic_name, self)
261
+ return_value = nil
262
+
263
+ @model_finders.create_finders(record_meta)
264
+ ActiveRecord::Base.silence do
265
+ return_value = record.create
266
+ @id_cache[record_meta.heirarchy.id_cache_key][symbolic_name] = record.id
267
+ end
268
+ return_value
269
+ end
270
+
271
+ def extract_creation_arguments(arguments)
272
+ if arguments.size == 2 && arguments.last.kind_of?(Hash)
273
+ arguments
274
+ elsif arguments.size == 1 && arguments.last.kind_of?(Hash)
275
+ [nil, arguments.last]
276
+ elsif arguments.size == 1 && arguments.last.kind_of?(Symbol)
277
+ [arguments.last, Hash.new]
278
+ else
279
+ [nil, Hash.new]
280
+ end
281
+ end
282
+
283
+ def new_model_finders_module
284
+ mod = Module.new
285
+ dataset_binding = self
286
+ mod.module_eval do
287
+ define_method :dataset_session_binding do
288
+ dataset_binding
289
+ end
290
+ end
291
+ mod.extend ModelFinders
292
+ mod
293
+ end
294
+
295
+ def new_record_methods_module
296
+ mod = Module.new do
297
+ include RecordMethods
298
+ end
299
+ dataset_binding = self
300
+ mod.module_eval do
301
+ define_method :dataset_session_binding do
302
+ dataset_binding
303
+ end
304
+ end
305
+ mod
306
+ end
307
+
308
+ def resolve_record_class(record_type)
309
+ case record_type
310
+ when Symbol
311
+ resolve_record_class record_type.to_s.singularize.camelize
312
+ when Class
313
+ record_type
314
+ when String
315
+ record_type.constantize
316
+ end
317
+ end
318
+ end
319
+ end