squixtures 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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