aiwilliams-dataset 1.2.0
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/CHANGELOG +54 -0
- data/LICENSE +19 -0
- data/README +111 -0
- data/Rakefile +28 -0
- data/TODO +15 -0
- data/VERSION.yml +4 -0
- data/lib/dataset/base.rb +157 -0
- data/lib/dataset/collection.rb +19 -0
- data/lib/dataset/database/base.rb +30 -0
- data/lib/dataset/database/mysql.rb +34 -0
- data/lib/dataset/database/postgresql.rb +34 -0
- data/lib/dataset/database/sqlite3.rb +32 -0
- data/lib/dataset/extensions/cucumber.rb +20 -0
- data/lib/dataset/extensions/rspec.rb +21 -0
- data/lib/dataset/extensions/test_unit.rb +60 -0
- data/lib/dataset/instance_methods.rb +10 -0
- data/lib/dataset/load.rb +47 -0
- data/lib/dataset/record/fixture.rb +73 -0
- data/lib/dataset/record/meta.rb +58 -0
- data/lib/dataset/record/model.rb +50 -0
- data/lib/dataset/resolver.rb +110 -0
- data/lib/dataset/session.rb +51 -0
- data/lib/dataset/session_binding.rb +317 -0
- data/lib/dataset/version.rb +9 -0
- data/lib/dataset.rb +125 -0
- data/plugit/descriptor.rb +25 -0
- data/tasks/dataset.rake +19 -0
- metadata +83 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
*1.2.0 [Cucumber] (April 8, 2009)
|
2
|
+
|
3
|
+
* Support for cucumber [jgarber, seancribbs]
|
4
|
+
|
5
|
+
|
6
|
+
*1.1.0 [STI, belongs_to] (February 14, 2009)
|
7
|
+
|
8
|
+
* STI is better supported for inserting, naming and finding records [aiwilliams]
|
9
|
+
|
10
|
+
class Place < ActiveRecord::Base; end
|
11
|
+
class State < Place; end
|
12
|
+
class NorthCarolina < State; end
|
13
|
+
|
14
|
+
create_record(NorthCarolina, :state) # no need to define the 'type' column value
|
15
|
+
states(:state) == places(:state) == north_carolinas(:state) # read with the class names pluralized
|
16
|
+
|
17
|
+
* Moved to jeweler for much cleaner, github embracing gem building [aiwilliams]
|
18
|
+
* Support for simple belongs to associations [aiwilliams]
|
19
|
+
|
20
|
+
class Person < ActiveRecord::Base; end
|
21
|
+
class Note < ActiveRecord::Base
|
22
|
+
belongs_to :person
|
23
|
+
end
|
24
|
+
|
25
|
+
person_id = create_record Person, :myguy
|
26
|
+
create_record Note, :person => :myguy
|
27
|
+
Note.last.person_id == person_id
|
28
|
+
|
29
|
+
* Models inside modules are supported a little more conveniently [aiwilliams]
|
30
|
+
|
31
|
+
module MList
|
32
|
+
class Message < ActiveRecord::Base
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# We'll get rid of the underscore in 'm_list_messages'
|
37
|
+
create_record MList::Message, :test
|
38
|
+
mlist_messages(:test)
|
39
|
+
|
40
|
+
* Helper method for converting strings to useful symbols for finding records [siannopollo]
|
41
|
+
|
42
|
+
This is useful if you write creator methods of your own.
|
43
|
+
|
44
|
+
def create_person(attributes)
|
45
|
+
create_record Person, name_to_sym(attributes[:name]), attributes
|
46
|
+
end
|
47
|
+
|
48
|
+
create_person(:name => 'Little John')
|
49
|
+
people(:little_john)
|
50
|
+
|
51
|
+
|
52
|
+
*1.0.0 [Scenarios Replacement] (December 15, 2008)
|
53
|
+
|
54
|
+
* Drop-in replacement for Scenarios plugin of old [aiwilliams]
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008-2009, Adam Williams
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
= Dataset
|
2
|
+
|
3
|
+
Dataset provides a simple API for creating and finding sets of data in your database. Check out Dataset::RecordMethods and Dataset::ModelFinders.
|
4
|
+
|
5
|
+
Dataset loads data intelligently if you use 'nested contexts' in your tests (RSpec, anything that uses Test::Unit::TestCase subclassing for creating nested contexts):
|
6
|
+
|
7
|
+
describe Something do
|
8
|
+
dataset :a => Dataset :a is loaded (at the right time)
|
9
|
+
|
10
|
+
it 'should whatever'
|
11
|
+
end
|
12
|
+
|
13
|
+
describe More do
|
14
|
+
dataset :b => Dataset :b is loaded. :a data is still there
|
15
|
+
|
16
|
+
it 'should'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Another do => Database is restored to :a, without re-running :a logic
|
21
|
+
it 'should'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
The goal is to see a marked improvement in overall test run speed, basing this on the assumption that it is faster to have the OS copy a file or mySQL dump and load. Of course, we may find this to be a false assumption, but there were plenty of bugs in the former 'Scenarios' - addressing that afforded the opportunity to test the assumption.
|
27
|
+
|
28
|
+
|
29
|
+
Dataset does not prevent you from using other libraries like Machinist or factory_girl. If you were to used either of those, you could have a dataset like this:
|
30
|
+
|
31
|
+
require 'faker'
|
32
|
+
|
33
|
+
class OrganizationsDataset < Dataset::Base
|
34
|
+
Sham.name { Faker::Name.name }
|
35
|
+
|
36
|
+
Organization.blueprint do
|
37
|
+
name { Sham.name }
|
38
|
+
end
|
39
|
+
|
40
|
+
def load
|
41
|
+
name_model Organization.make, :org_one
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
The benefit is that you can reuse interesting sets of data, without sacrificing the utility of those other libraries.
|
46
|
+
|
47
|
+
describe Organization, 'stuff' do
|
48
|
+
dataset :organizations
|
49
|
+
end
|
50
|
+
|
51
|
+
describe Organization, 'other stuff' do
|
52
|
+
dataset :organizations
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
Get things installed, then read more in the Dataset documentation at http://aiwilliams.github.com/dataset
|
57
|
+
|
58
|
+
|
59
|
+
== Installation
|
60
|
+
|
61
|
+
Install the plugin:
|
62
|
+
|
63
|
+
./script/plugin install git://github.com/aiwilliams/dataset.git
|
64
|
+
|
65
|
+
In your test_helper.rb/spec_helper.rb:
|
66
|
+
|
67
|
+
require 'dataset'
|
68
|
+
class Test::Unit::TestCase
|
69
|
+
include Dataset
|
70
|
+
datasets_directory "#{RAILS_ROOT}/spec/datasets"
|
71
|
+
end
|
72
|
+
|
73
|
+
If you don't use rspec_on_rails, or you have specs that aren't of the RailsExampleGroup type, you should do this in spec_helper.rb:
|
74
|
+
|
75
|
+
require 'dataset'
|
76
|
+
class Spec::Example::ExampleGroup
|
77
|
+
include Dataset
|
78
|
+
datasets_directory "#{RAILS_ROOT}/spec/datasets"
|
79
|
+
end
|
80
|
+
|
81
|
+
If you were a user of the Scenarios plugin, and want to do as little as possible to get going (assumes you are using rspec_on_rails):
|
82
|
+
|
83
|
+
require 'dataset'
|
84
|
+
Scenario = Scenarios = Dataset
|
85
|
+
class Test::Unit::TestCase
|
86
|
+
include Dataset
|
87
|
+
class << self
|
88
|
+
alias_method :scenario, :dataset
|
89
|
+
end
|
90
|
+
end
|
91
|
+
class ScenariosResolver < Dataset::DirectoryResolver
|
92
|
+
def suffix
|
93
|
+
@suffix ||= 'Scenario'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
Dataset::Resolver.default = ScenariosResolver.new("#{RAILS_ROOT}/spec/scenarios")
|
97
|
+
|
98
|
+
|
99
|
+
== Credits
|
100
|
+
|
101
|
+
Written by [Adam Williams](http://github.com/aiwilliams).
|
102
|
+
|
103
|
+
Contributors:
|
104
|
+
|
105
|
+
- [Saturn Flyer](http://www.saturnflyer.com) [github](http://github.com/saturnflyer)
|
106
|
+
- [Steve Iannopollo](http://github.com/siannopollo)
|
107
|
+
- [John Long](http://github.com/jlong)
|
108
|
+
|
109
|
+
---
|
110
|
+
|
111
|
+
Dataset is released under the MIT-License and is Copyright (c)2008 Adam Williams.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'plugit/descriptor')
|
4
|
+
require 'rubygems'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
desc "Run all specs"
|
10
|
+
Spec::Rake::SpecTask.new do |t|
|
11
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
12
|
+
t.spec_opts = ['--options', 'spec/spec.opts']
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |s|
|
18
|
+
s.name = 'dataset'
|
19
|
+
s.summary = 'A simple API for creating and finding sets of data in your database, built on ActiveRecord.'
|
20
|
+
s.email = 'adam@thewilliams.ws'
|
21
|
+
s.files = FileList["[A-Z]*", "{lib,tasks}/**/*", "plugit/descriptor.rb"].exclude("tmp")
|
22
|
+
s.homepage = "http://github.com/aiwilliams/dataset"
|
23
|
+
s.description = s.summary
|
24
|
+
s.authors = ['Adam Williams']
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
28
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
take any instance variables already in context and make them available to dataset blocks - this is for nested describes
|
2
|
+
I'm not sure about this one. It can be very frustrating to lose context of when the state of an iv is modified.
|
3
|
+
|
4
|
+
add ability to clear the database (some tests wanted to guarantee a clear db)
|
5
|
+
This is acheived with "dataset {}"
|
6
|
+
|
7
|
+
clear database completely at beginning of session, only tables where data was created within a session??
|
8
|
+
|
9
|
+
clear all dumps on new run of tests
|
10
|
+
be sure we are capturing a dataset if it has already be captured before during a run
|
11
|
+
describe what happens when someone has a fixtures file - they get loaded after our datasets, thereby causing all the data in the table of the fixture file (like things.yml) to be deleted - the fixtures are then loaded
|
12
|
+
look into truncating database instead individual table deletes
|
13
|
+
allow configuration of dataset
|
14
|
+
* permatable / global scope
|
15
|
+
re-evaluation location of some tests that depend on TestCase in non-test/unit tests
|
data/VERSION.yml
ADDED
data/lib/dataset/base.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
module Dataset
|
2
|
+
|
3
|
+
# The superclass of your Dataset classes.
|
4
|
+
#
|
5
|
+
# It is recommended that you create a dataset using the Dataset::Block
|
6
|
+
# method first, then grow into using classes as you recognize patterns in
|
7
|
+
# your test data creation. This will help you to keep simple things simple.
|
8
|
+
#
|
9
|
+
class Base
|
10
|
+
class << self
|
11
|
+
# Allows a subclass to define helper methods that should be made
|
12
|
+
# available to instances of this dataset, to datasets that use this
|
13
|
+
# dataset, and to tests that use this dataset.
|
14
|
+
#
|
15
|
+
# This feature is great for providing any kind of method that would help
|
16
|
+
# test the code around the data your dataset creates. Be careful,
|
17
|
+
# though, to keep from adding business logic to these methods! That
|
18
|
+
# belongs in your production code.
|
19
|
+
#
|
20
|
+
def helpers(&method_definitions)
|
21
|
+
@helper_methods ||= begin
|
22
|
+
mod = Module.new
|
23
|
+
include mod
|
24
|
+
mod
|
25
|
+
end
|
26
|
+
@helper_methods.module_eval &method_definitions
|
27
|
+
end
|
28
|
+
|
29
|
+
def helper_methods # :nodoc:
|
30
|
+
@helper_methods
|
31
|
+
end
|
32
|
+
|
33
|
+
# Allows a subsclass to declare which datasets it uses.
|
34
|
+
#
|
35
|
+
# Dataset is designed to promote 'design by composition', rather than
|
36
|
+
# 'design by inheritance'. You should not use class hiearchies to share
|
37
|
+
# data and code in your datasets. Instead, you can write something like
|
38
|
+
# this:
|
39
|
+
#
|
40
|
+
# class PeopleDataset < Dataset::Base; end
|
41
|
+
# class DepartmentsDataset < Dataset::Base; end
|
42
|
+
# class OrganizationsDataset < Dataset::Base
|
43
|
+
# uses :people, :departments
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# When the OrganizationsDataset is loaded, it will have all the data
|
47
|
+
# from the datasets is uses, as well as all of the helper methods
|
48
|
+
# defined by those datasets.
|
49
|
+
#
|
50
|
+
# When a dataset uses other datasets, and those datasets themselves use
|
51
|
+
# datasets, things will be loaded in the order of dependency you would
|
52
|
+
# expect:
|
53
|
+
#
|
54
|
+
# C uses B
|
55
|
+
# A uses C
|
56
|
+
# B, C, A is the load order
|
57
|
+
#
|
58
|
+
def uses(*datasets)
|
59
|
+
@used_datasets = datasets
|
60
|
+
end
|
61
|
+
|
62
|
+
def used_datasets # :nodoc:
|
63
|
+
@used_datasets
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Invoked once before a collection of tests is run. If you use a dataset
|
68
|
+
# in multiple test classes, it will be called once for each of them -
|
69
|
+
# remember that the database will be cleared at the beginning of running a
|
70
|
+
# 'suite' or 'group' of tests, unless you are using nested contexts (as in
|
71
|
+
# nested describe blocks in RSpec).
|
72
|
+
#
|
73
|
+
# Override this method in your subclasses.
|
74
|
+
#
|
75
|
+
def load; end
|
76
|
+
end
|
77
|
+
|
78
|
+
# The easiest way to create some data before a suite of tests is run is by
|
79
|
+
# using a Dataset::Block. An example works wonders:
|
80
|
+
#
|
81
|
+
# class PeopleTest < Test::Unit::TestCase
|
82
|
+
# dataset do
|
83
|
+
# create_record :person, :billy, :name => 'Billy'
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# def test_name
|
87
|
+
# assert_equal 'Billy', people(:billy).name
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# The database will be cleared and billy will be inserted once before
|
92
|
+
# running each of the tests within a transaction. All the normal transaction
|
93
|
+
# fixtures stuff will still work.
|
94
|
+
#
|
95
|
+
# One of the great features of Dataset, at least when things get really
|
96
|
+
# interesting in your data needs, is that nested contexts will be additive.
|
97
|
+
# Consider this:
|
98
|
+
#
|
99
|
+
# describe Something do
|
100
|
+
# dataset :a => Dataset :a is loaded (at the right time)
|
101
|
+
#
|
102
|
+
# it 'should whatever'
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# describe More do
|
106
|
+
# dataset :b => Dataset :b is loaded. :a data is still there
|
107
|
+
#
|
108
|
+
# it 'should'
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# describe Another do => Database is restored to :a, without re-running :a logic
|
113
|
+
# it 'should'
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# == Instance Variables
|
119
|
+
#
|
120
|
+
# You may also assign instance variables in a dataset block, and they will
|
121
|
+
# be available to your test methods. You have to be careful with this in a
|
122
|
+
# similar way that you must with an RSpec before :all block. Since the
|
123
|
+
# instance variables are pointing to the same instances accross all tests,
|
124
|
+
# things can get weird if you intend to change their state. It's best use is
|
125
|
+
# for loading objects that you want to read a lot without loading over and
|
126
|
+
# over again for each test.
|
127
|
+
#
|
128
|
+
# == Building on Other Datasets
|
129
|
+
#
|
130
|
+
# You may pass any number of Dataset::Base subclasses - or better yet, their
|
131
|
+
# names - to the dataset method. When you use a block, this adds a lot of
|
132
|
+
# clarity:
|
133
|
+
#
|
134
|
+
# class PersonTest < Test::Unit::TestCase
|
135
|
+
# dataset :organization, :people do
|
136
|
+
# id = create_record :person, :second_admin, :name => 'Admin Three'
|
137
|
+
# create_record :organization_administratorship, :organization_id => organization_id(:first_bank), :person_id => id
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# def test_admins
|
141
|
+
# assert organizations(:first_bank).admins.include?(people(:second_admin))
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# == Reusing a Dataset
|
146
|
+
#
|
147
|
+
# When you need to go beyond the block, create a Dataset::Base subclass!
|
148
|
+
class Block < Base
|
149
|
+
include Dataset::InstanceMethods
|
150
|
+
|
151
|
+
def load # :nodoc:
|
152
|
+
dataset_session_binding.install_block_variables(self)
|
153
|
+
doload
|
154
|
+
dataset_session_binding.copy_block_variables(self)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Dataset
|
4
|
+
class Collection < Array # :nodoc:
|
5
|
+
def initialize(parent)
|
6
|
+
concat parent
|
7
|
+
end
|
8
|
+
|
9
|
+
def <<(dataset)
|
10
|
+
super
|
11
|
+
uniq!
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def subset?(other)
|
16
|
+
Set.new(self).subset?(Set.new(other))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Dataset
|
4
|
+
module Database # :nodoc:
|
5
|
+
|
6
|
+
# Provides Dataset a way to clear, dump and load databases.
|
7
|
+
class Base
|
8
|
+
include FileUtils
|
9
|
+
|
10
|
+
def clear
|
11
|
+
connection = ActiveRecord::Base.connection
|
12
|
+
ActiveRecord::Base.silence do
|
13
|
+
connection.tables.each do |table_name|
|
14
|
+
connection.delete "DELETE FROM #{connection.quote_table_name(table_name)}",
|
15
|
+
"Dataset::Database#clear" unless table_name == ActiveRecord::Migrator.schema_migrations_table_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def record_meta(record_class)
|
21
|
+
record_metas[record_class] ||= Dataset::Record::Meta.new(record_class)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def record_metas
|
26
|
+
@record_metas ||= Hash.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Dataset
|
2
|
+
module Database # :nodoc:
|
3
|
+
|
4
|
+
# The interface to a mySQL database, this will capture by creating a dump
|
5
|
+
# file and restore by loading one of the same.
|
6
|
+
#
|
7
|
+
class Mysql < Base
|
8
|
+
def initialize(database_spec, storage_path)
|
9
|
+
@database = database_spec[:database]
|
10
|
+
@username = database_spec[:username]
|
11
|
+
@password = database_spec[:password]
|
12
|
+
@storage_path = storage_path
|
13
|
+
FileUtils.mkdir_p(@storage_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def capture(datasets)
|
17
|
+
return if datasets.nil? || datasets.empty?
|
18
|
+
`mysqldump -u #{@username} --password=#{@password} --compact --extended-insert --no-create-db --add-drop-table --quick --quote-names #{@database} > #{storage_path(datasets)}`
|
19
|
+
end
|
20
|
+
|
21
|
+
def restore(datasets)
|
22
|
+
store = storage_path(datasets)
|
23
|
+
if File.file?(store)
|
24
|
+
`mysql -u #{@username} --password=#{@password} --database=#{@database} < #{store}`
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def storage_path(datasets)
|
30
|
+
"#{@storage_path}/#{datasets.collect {|c| c.__id__}.join('_')}.sql"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Dataset
|
2
|
+
module Database # :nodoc:
|
3
|
+
|
4
|
+
# The interface to a PostgreSQL database, this will capture by creating a dump
|
5
|
+
# file and restore by loading one of the same.
|
6
|
+
#
|
7
|
+
class Postgresql < Base
|
8
|
+
def initialize(database_spec, storage_path)
|
9
|
+
@database = database_spec[:database]
|
10
|
+
@username = database_spec[:username]
|
11
|
+
@password = database_spec[:password]
|
12
|
+
@storage_path = storage_path
|
13
|
+
FileUtils.mkdir_p(@storage_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def capture(datasets)
|
17
|
+
return if datasets.nil? || datasets.empty?
|
18
|
+
`pg_dump -c #{@database} > #{storage_path(datasets)}`
|
19
|
+
end
|
20
|
+
|
21
|
+
def restore(datasets)
|
22
|
+
store = storage_path(datasets)
|
23
|
+
if File.file?(store)
|
24
|
+
`psql -U #{@username} -p #{@password} -e #{@database} < #{store}`
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def storage_path(datasets)
|
30
|
+
"#{@storage_path}/#{datasets.collect {|c| c.__id__}.join('_')}.sql"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Dataset
|
2
|
+
module Database # :nodoc:
|
3
|
+
|
4
|
+
# The interface to a sqlite3 database, this will capture by copying the db
|
5
|
+
# file and restore by replacing and reconnecting to one of the same.
|
6
|
+
#
|
7
|
+
class Sqlite3 < Base
|
8
|
+
def initialize(database_spec, storage_path)
|
9
|
+
@database_path, @storage_path = database_spec[:database], storage_path
|
10
|
+
FileUtils.mkdir_p(@storage_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def capture(datasets)
|
14
|
+
return if datasets.nil? || datasets.empty?
|
15
|
+
cp @database_path, storage_path(datasets)
|
16
|
+
end
|
17
|
+
|
18
|
+
def restore(datasets)
|
19
|
+
store = storage_path(datasets)
|
20
|
+
if File.file?(store)
|
21
|
+
mv store, @database_path
|
22
|
+
ActiveRecord::Base.establish_connection 'test'
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def storage_path(datasets)
|
28
|
+
"#{@storage_path}/#{datasets.collect {|c| c.__id__}.join('_')}.sqlite3.db"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Dataset
|
2
|
+
module Extensions # :nodoc:
|
3
|
+
|
4
|
+
module CucumberWorld # :nodoc:
|
5
|
+
def dataset(*datasets, &block)
|
6
|
+
add_dataset(*datasets, &block)
|
7
|
+
|
8
|
+
load = nil
|
9
|
+
$__cucumber_toplevel.Before do
|
10
|
+
load = dataset_session.load_datasets_for(self.class)
|
11
|
+
extend_from_dataset_load(load)
|
12
|
+
end
|
13
|
+
# Makes sure the datasets are reloaded after each scenario
|
14
|
+
Cucumber::Rails.use_transactional_fixtures
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
Cucumber::Rails::World.extend Dataset::Extensions::CucumberWorld
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dataset
|
2
|
+
module Extensions # :nodoc:
|
3
|
+
|
4
|
+
module RSpecExampleGroup # :nodoc:
|
5
|
+
def dataset(*datasets, &block)
|
6
|
+
add_dataset(*datasets, &block)
|
7
|
+
|
8
|
+
load = nil
|
9
|
+
before(:all) do
|
10
|
+
load = dataset_session.load_datasets_for(self.class)
|
11
|
+
extend_from_dataset_load(load)
|
12
|
+
end
|
13
|
+
before(:each) do
|
14
|
+
extend_from_dataset_load(load)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
Spec::Example::ExampleGroup.extend Dataset::Extensions::RSpecExampleGroup
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Dataset
|
2
|
+
class TestSuite # :nodoc:
|
3
|
+
def initialize(suite, test_class)
|
4
|
+
@suite = suite
|
5
|
+
@test_class = test_class
|
6
|
+
end
|
7
|
+
|
8
|
+
def dataset_session
|
9
|
+
@test_class.dataset_session
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(result, &progress_block)
|
13
|
+
if dataset_session
|
14
|
+
load = dataset_session.load_datasets_for(@test_class)
|
15
|
+
@suite.tests.each { |e| e.extend_from_dataset_load(load) }
|
16
|
+
end
|
17
|
+
@suite.run(result, &progress_block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method_symbol, *args)
|
21
|
+
@suite.send(method_symbol, *args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Extensions # :nodoc:
|
26
|
+
|
27
|
+
module TestUnitTestCase # :nodoc:
|
28
|
+
def self.extended(test_case)
|
29
|
+
class << test_case
|
30
|
+
alias_method_chain :suite, :dataset
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def suite_with_dataset
|
35
|
+
Dataset::TestSuite.new(suite_without_dataset, self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def dataset(*datasets, &block)
|
39
|
+
add_dataset(*datasets, &block)
|
40
|
+
|
41
|
+
# Unfortunately, if we have rspec loaded, TestCase has it's suite method
|
42
|
+
# modified for the test/unit runners, but uses a different mechanism to
|
43
|
+
# collect tests if the rspec runners are used.
|
44
|
+
if included_modules.find {|m| m.name =~ /ExampleMethods\Z/}
|
45
|
+
load = nil
|
46
|
+
before(:all) do
|
47
|
+
load = dataset_session.load_datasets_for(self.class)
|
48
|
+
extend_from_dataset_load(load)
|
49
|
+
end
|
50
|
+
before(:each) do
|
51
|
+
extend_from_dataset_load(load)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Test::Unit::TestCase.extend Dataset::Extensions::TestUnitTestCase
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Dataset
|
2
|
+
module InstanceMethods # :nodoc:
|
3
|
+
def extend_from_dataset_load(load)
|
4
|
+
load.dataset_binding.install_block_variables(self)
|
5
|
+
self.extend load.dataset_binding.record_methods
|
6
|
+
self.extend load.dataset_binding.model_finders
|
7
|
+
self.extend load.helper_methods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|