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 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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 2
3
+ :patch: 0
4
+ :major: 1
@@ -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