squixtures 0.0.2

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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'logjam'
4
+ gem 'sequel'
5
+
6
+ # Specify your gem's dependencies in squixtures.gemspec
7
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Peter Wood
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,148 @@
1
+ # Squixtures
2
+
3
+ Squixtures is a library that provides a simple data fixtures facility, ala
4
+ the fixtures generally used in unit tests. The library came about because it
5
+ is quite difficult to make use of Rails fixtures outside of the Rails
6
+ framework itself.
7
+
8
+ The Squixtures library makes use of the Sequel library in an attempt to
9
+ attain database independence. At the moment it has only been coded for and
10
+ tested with SQLite3 and Postgres. Squixtures also makes use of the LogJam
11
+ library to centralize it's logging.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'squixtures'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install squixtures
26
+
27
+ ## Usage
28
+
29
+ Squixtures is a library so the first thing to do is to incorporated it
30
+ into the file where you plan to use it. This can be done with a simple
31
+ require such as the following...
32
+
33
+ require 'rubygems'
34
+ require 'squixtures'
35
+
36
+ Squixtures is based around amodule so how you deploy it will depend heavily
37
+ on how you want to use it. If, for example, you only want to deploy fixtures
38
+ once, when a class definition is loaded, you can use the extend statement
39
+ to incorporate the functionality at the class level and then invoke the
40
+ fixtures you want. For example...
41
+
42
+ class MyClass
43
+ # Extend the class with Squixtures functionality.
44
+ extend Squixtures::Fixtures
45
+
46
+ # Load the fixtures required.
47
+ fixtures :users, :accounts, :orders
48
+ end
49
+
50
+ Alternatively, if you want the fixtures reloaded regularly, say as part of
51
+ the set up process for a unit test, you would include the squixtures
52
+ functionality like this...
53
+
54
+ class TestMyClass
55
+ # Incorporate the Squixtures functionality.
56
+ include Squixtures::Fixtures
57
+
58
+ def setup
59
+ # Load the desired fixtures.
60
+ fixtures :users, :accounts, :orders
61
+ end
62
+ end
63
+
64
+ By default the Squixtures fixture loader empties the target table between
65
+ fixture loads. This feature is configurable but you should be aware of it
66
+ as it could cause data loss. Squixtures currently only recognises YAML
67
+ style fixtures which, as an example, follow this format...
68
+
69
+ one:
70
+ id: 1
71
+ email: jsmith@blah.com
72
+ password: password
73
+ status: 1
74
+ created_at: <%= Time.now %>
75
+ updated_at: <%= Time.now %>
76
+
77
+ A file containing this would define a single record made up of 6 fields.
78
+ The first line specifies the record name and isn't really relevant except
79
+ in that it must be unique within the file that defines it. Note that the
80
+ fixture files are run through ERB as part of the load process so it's
81
+ possible to include simple Ruby statements into the definitions. In the
82
+ example above Ruby code is used to generate values for the created_at
83
+ and updated_at field values.
84
+
85
+ ## Advanced Usage
86
+ The Squixtures library makes a set of assumptions to attempt to ease the
87
+ process of using fixtures. For example, the library assumes that a database.yml
88
+ file can be found and that fixtures will be loaded into the test database
89
+ specified in the database configuration. This is convenient but facilities
90
+ have also been provided to work around these assumptions.
91
+
92
+ To change the database configuration entry that the library will use you can
93
+ change the environment setting for Squixtures by executing a line like the
94
+ following before performing any fixtures loads...
95
+
96
+ Squixtures.environment = "development"
97
+
98
+ If you want to alter the location that the Squixtures library obtains fixture
99
+ files from you can do so with a line such as the following...
100
+
101
+ Squixtures.fixtures_dir = "/my/fixtures/directory"
102
+
103
+ By default Squixtures does not make use of transactions when loading fixtures.
104
+ You can specifying the use of transactions on a per fixture basis with a line
105
+ such as the following...
106
+
107
+ Squixtures.transactional = true
108
+
109
+ ## Platform Independence
110
+ The Sequel library has been used in minimize the amount of RDBMS specific code
111
+ exists in the library. Unfortunately, it's impossible to be complete free of
112
+ such considerations. There are two main areas where the underlying database
113
+ system impinges on the code...
114
+
115
+ * Database connections. The Sequel library requires a URL like string to
116
+ obtain a database connection. The form and content of this string are
117
+ very much dictated by the underlying database.
118
+
119
+ * Referential integrity. Database systems which enforce strict referential
120
+ integrity can make the injection of fixtures more difficult, especially
121
+ in the case where the data model has circular references. This can, in
122
+ part, be avoided by proper ordering of the fixtures load. This is not
123
+ a complete solution however and the Squixtures library takes steps to
124
+ deactivate relational integrity temporarily where this will alleviate
125
+ the issue. Unfortunately the facilities for doing this are very much
126
+ specific to the database.
127
+
128
+ At the moment the Squixtures library only supports (i.e. has been tested with)
129
+ the SQLite3 and Postgres databases. Adding support for additional databases
130
+ would require that the library be extended to accommodate the specified database
131
+ in the areas detailed above.
132
+
133
+ The approach currently taken to this issue is to provide a database specific
134
+ helper class. These helper classes provide three basic pieces of functionality.
135
+ First they provide a class level function that takes a Hash of database
136
+ connection configuration parameters and generates a connection URL from them.
137
+ Second they provide before and after load functionality that is currently
138
+ intended to be the location where referential integrity is deactivate on the
139
+ appropriate table prior to the data load. There are currently two such classes
140
+ defined in the library - PostgresHelper and SQLite3Helper.
141
+
142
+ ## Contributing
143
+
144
+ 1. Fork it
145
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
146
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
147
+ 4. Push to the branch (`git push origin my-new-feature`)
148
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,170 @@
1
+ #! /usr/bin/env ruby
2
+ # Copyright (c), 2012 Peter Wood
3
+
4
+ require "logjam"
5
+ require "sequel"
6
+ require "stringio"
7
+ require "squixtures/version"
8
+ require "squixtures/exceptions"
9
+ require "squixtures/fixture"
10
+ require "squixtures/fixtures"
11
+ require "squixtures/loader"
12
+ require "squixtures/postgres_helper"
13
+ require "squixtures/sqlite3_helper"
14
+ require "squixtures/helper_factory"
15
+
16
+ module Squixtures
17
+ # Definition of the default fixtures directory.
18
+ DEFAULT_FIXTURES_DIR = "#{Dir.getwd}/test/fixtures"
19
+
20
+ # Definition for the default database configuration file name.
21
+ DEFAULT_DATABASE_CFG_FILE = "database.yml"
22
+
23
+ # Definition of the default configuration search paths.
24
+ DEFAULT_CFG_SEARCH_PATHS = ["#{Dir.getwd}/config",
25
+ "#{Dir.getwd}/test/fixtures",
26
+ Dir.getwd]
27
+
28
+ # Definition of the fixtures search paths.
29
+ FIXTURES_SEARCH_PATHS = ["#{Dir.getwd}/test/fixtures",
30
+ "#{Dir.getwd}/fixtures"]
31
+
32
+ # Definition of the configuration defaults.
33
+ CONFIGURATION_DEFAULTS = {:clear_tables => true,
34
+ :database => nil,
35
+ :environment => "test",
36
+ :search_paths => DEFAULT_CFG_SEARCH_PATHS,
37
+ :transactional => false}
38
+
39
+ # Module configuration store.
40
+ @@configuration = {}.merge(CONFIGURATION_DEFAULTS)
41
+
42
+ # This method performs the actual configuration and load of a set of
43
+ # fixtures.
44
+ #
45
+ # ==== Parameters
46
+ # *names:: The list of fixture names to be loaded.
47
+ def self.fixtures(*names)
48
+ load_database_configuration if !@@configuration[:database]
49
+ Loader.new(@@configuration).load(*names)
50
+ end
51
+
52
+ # This method allows the configuration of the Squixtures module.
53
+ def self.configuration=(configuration)
54
+ @@configuration = CONFIGURATION_DEFAULTS.merge(configuration)
55
+ end
56
+
57
+ # This method fetches configuration associated with the Squixtures module.
58
+ # Note that, due to the late loading of the database configuration, the
59
+ # connection details will not be available until after a load.
60
+ def self.configuration
61
+ @@configuration
62
+ end
63
+
64
+ # This method is used to set the environment that the Squixtures module
65
+ # will use when loading fixtures. Note that calling this method will also
66
+ # invoke an immediate reload of database adapter/connection details.
67
+ #
68
+ # ==== Parameters
69
+ # setting:: A String, usually one of "test", "development" or "production"
70
+ # but can be anything else as long as it matches an entry in the
71
+ # database configuration settings.
72
+ def self.environment=(setting)
73
+ @@configuration[:environment] = setting
74
+ Squixtures.load_database_configuration
75
+ end
76
+
77
+ # This method fetches the current environment setting for the Squixtures
78
+ # module.
79
+ def self.environment
80
+ @@configuration[:environment]
81
+ end
82
+
83
+ # This method provides a means of fetching the path to the directory that
84
+ # is expected to contain the fixtures files.
85
+ def self.fixtures_dir
86
+ @@configuration[:fixtures_dir].nil? ? Squixtures.find_fixtures_dir : @@configuration[:fixtures_dir]
87
+ end
88
+
89
+ # This method allows for the specification of the directory that contains
90
+ # the fixture files.
91
+ #
92
+ # ==== Parameters
93
+ # path:: The path of the fixtures directory.
94
+ def self.fixtures_dir=(path)
95
+ @@configuration[:fixtures_dir] = path
96
+ end
97
+
98
+ # This method provides a means of checking whether transactional fixtures
99
+ # have been activated in the library.
100
+ def self.transactional?
101
+ @@configuration[:transactional]
102
+ end
103
+
104
+ # This method allows the toggling of transactional fixture loading.
105
+ #
106
+ # ==== Parameters
107
+ # setting:: A boolean value indicating whether transactional loading should
108
+ # be used with the fixtures.
109
+ def self.transactional=(setting)
110
+ @@configuration[:transactional] = setting
111
+ end
112
+
113
+ # This method loads the database configuration details into the current
114
+ # Squixtures configuration.
115
+ def self.load_database_configuration
116
+ log = LogJam.get_logger("squixtures")
117
+ log.debug "Loading database configuration."
118
+ success = @@configuration[:search_paths].find do |path|
119
+ found = false
120
+ file_path = "#{path}/#{DEFAULT_DATABASE_CFG_FILE}"
121
+ log.debug "Checking for the existence of #{file_path}."
122
+ if File.exist?(file_path)
123
+ begin
124
+ log.debug "#{file_path} exists, attempting a load."
125
+ settings = YAML.load_file(file_path)
126
+ environment = @@configuration[:environment]
127
+ if settings.include?(environment)
128
+ @@configuration[:database] = settings[environment]
129
+ found = true
130
+ log.debug "Database configuration loaded from #{file_path}."
131
+ else
132
+ log.warn "The #{file_path} database configuration does not contain a #{environment} entry."
133
+ end
134
+ rescue => error
135
+ log.warn "Load of the #{file_path} database configuration file failed."
136
+ end
137
+ end
138
+ found
139
+ end
140
+
141
+ if !success
142
+ raise SquixtureError.new("Unable to locate a database configuration file.")
143
+ end
144
+ end
145
+
146
+ # This method searches for a directory containing fixtures files, returning
147
+ # a string with a path to the directory if it's found or nil if it isn't.
148
+ def self.find_fixtures_dir
149
+ return @@configuration[:fixtures_dir] if !@@configuration[:fixtures_dir].nil?
150
+ FIXTURES_SEARCH_PATHS.find do |entry|
151
+ found = false
152
+ if File.exist?(entry)
153
+ found = Dir.glob("#{entry}/*.yml").size > 0
154
+ @@configuration[:fixtures_dir] = entry if found
155
+ end
156
+ found
157
+ end
158
+ end
159
+
160
+ # This method provides a means of converting a typical set of database
161
+ # connection settings, such as those in a Rails database.yml file, into
162
+ # the URL to be used to connect to the database using the Sequel library.
163
+ #
164
+ # ==== Parameters
165
+ # settings:: A Hash of the settings that will be used to generate the
166
+ # connection URL.
167
+ def self.get_connection_url(settings)
168
+ HelperFactory.create_helper(settings).get_connection_url(settings)
169
+ end
170
+ end
@@ -0,0 +1,38 @@
1
+ #! /usr/bin/env ruby
2
+ # Copyright (c), 2012 Peter Wood
3
+
4
+ require 'stringio'
5
+
6
+ module Squixtures
7
+ # This class provides the exception class used by the Squixtures library.
8
+ class SquixtureError < StandardError
9
+ # Attribute accessor/mutator declarations.
10
+ attr_accessor :verbose
11
+
12
+ # Constructor for the SquixtureError class.
13
+ #
14
+ # ==== Parameters
15
+ # message:: The message to be associated with the error.
16
+ # cause:: Any underlying exception to be associated with the error.
17
+ # Defaults to nil.
18
+ def initialize(message, cause=nil, verbose=true)
19
+ super(message)
20
+ @cause = cause
21
+ @verbose = verbose
22
+ end
23
+
24
+ # This method fetches a stringified interpretation of an exception.
25
+ def to_s()
26
+ text = StringIO.new
27
+ text << super
28
+ if @verbose
29
+ text << "\n" + self.backtrace.join("\n")
30
+ if !@cause.nil?
31
+ text << "\n\nCause: #{@cause}"
32
+ text << "\n" + @cause.backtrace.join("\n")
33
+ end
34
+ end
35
+ text.string
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,96 @@
1
+ #! /usr/bin/env ruby
2
+ # Copyright (c), 2012 Peter Wood
3
+
4
+ require 'erb'
5
+ require 'yaml'
6
+
7
+ module Squixtures
8
+ # This class represents a single fixtures and provides the functionality
9
+ # to load it's contents into the database.
10
+ class Fixture
11
+ # Add logging to the class.
12
+ LogJam.apply(self, 'squixtures')
13
+
14
+ # Constructor for the Fixture class.
15
+ #
16
+ # ==== Parameters
17
+ # name:: The name of the fixture. This will be used to work out
18
+ # the fixture file and table names.
19
+ # configuration:: The configuration to be used by the Fixture.
20
+ def initialize(name, configuration)
21
+ @name = name
22
+ @configuration = configuration
23
+ end
24
+
25
+ # This method attempts to locate a fixture file, load it's contents and
26
+ # then insert the details held with the fixture file into the database.
27
+ #
28
+ # ==== Parameters
29
+ # connection:: The database connection to be used to insert the fixture
30
+ # details into the database.
31
+ def load(connection)
32
+ Fixture.log.debug "Loading the #{@name} fixture."
33
+ fixture_path = file_path
34
+ if fixture_path.nil?
35
+ raise SquixtureError.new("Unable to locate a data file for the #{@name} fixture.")
36
+ end
37
+
38
+ begin
39
+ Fixture.log.debug "Loading the data for the #{@name} fixture from #{fixture_path}."
40
+ content = File.open(fixture_path, "r") {|file| file.readlines.join("")}
41
+ #Fixture.log.debug "Read:\n#{content}"
42
+ entries = YAML.load(ERB.new(content).result)
43
+ if entries && entries.size > 0
44
+ data_set = connection[@name.intern]
45
+ data_set.delete if @configuration[:clear_tables]
46
+ entries.each do |key, values|
47
+ data_set.insert(convert_values(values))
48
+ end
49
+ end
50
+ rescue => error
51
+ message = "Load of the #{@name} fixture failed."
52
+ Fixture.log.error "#{message}\nCause: #{error}\n" + error.backtrace.join("\n")
53
+ raise SquixtureError.new(message, error)
54
+ end
55
+ end
56
+
57
+ # This method returns the name of the database table that the fixture
58
+ # will load data into.
59
+ def table_name
60
+ @name.to_s
61
+ end
62
+
63
+ # This method fetches the path and name of the file that the fixture
64
+ # will load it's data from. This will be nil if a file cannot be located
65
+ # for the fixture.
66
+ def file_path
67
+ path = nil
68
+ full_path = "#{@configuration[:fixtures_dir]}/#{@name}.yml"
69
+ Fixture.log.debug "Checking for the existence of #{full_path}."
70
+ if !File.exist?(full_path)
71
+ @configuration[:search_paths].find do |entry|
72
+ full_path = "#{entry}/#{@name}.yml"
73
+ Fixture.log.debug "Checking for the existence of #{full_path}."
74
+ path = full_path if File.exist?(full_path)
75
+ !path.nil?
76
+ end
77
+ else
78
+ path = full_path
79
+ end
80
+ path.nil? ? path : File.expand_path(path)
81
+ end
82
+
83
+ private
84
+
85
+ # This method converts a standard string keyed hash into a symbol keyed
86
+ # one.
87
+ #
88
+ # ==== Parameters
89
+ # input:: The Hash to be converted.
90
+ def convert_values(input)
91
+ output = {}
92
+ input.each {|key, value| output[key.intern] = value} if input
93
+ output
94
+ end
95
+ end
96
+ end