gamera 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7007ce468ed9467af78521e0e36a52dab78663fc
4
+ data.tar.gz: 31c9a98bd54d7444bcafc59cd800624f7cdb4146
5
+ SHA512:
6
+ metadata.gz: eab144b8457ab116a3d956cf31bdcbb871ec35bfcb1a273892d62b0c21c1a54973614f0b3fe1d562f4e3eb87125a684604669d33a2e291ab63ed0ec71eca38bb
7
+ data.tar.gz: 20d1199ef3b546c9b7030b92f723a7efc28e11dfc17c219c1f5439643070efae0c91ab04c73968440112ae773cfef29ee5a4021adf98e9b59495e2e753d6b576
@@ -0,0 +1,9 @@
1
+ require_relative 'gamera/builder'
2
+ require_relative 'gamera/builders/sequel_fixture_builder'
3
+ require_relative 'gamera/exceptions'
4
+ require_relative 'gamera/page'
5
+
6
+ require_relative 'gamera/page_sections/form'
7
+ require_relative 'gamera/page_sections/table'
8
+
9
+ require_relative 'gamera/utils/database_cleaner'
@@ -0,0 +1,244 @@
1
+ # encoding:utf-8
2
+ #--
3
+ # The MIT License (MIT)
4
+ #
5
+ # Copyright (c) 2015, The Gamera Development Team. See the COPYRIGHT file at
6
+ # the top-level directory of this distribution and at
7
+ # http://github.com/gamera-team/gamera/COPYRIGHT.
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in
17
+ # all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ # THE SOFTWARE.
26
+ #++
27
+
28
+ require 'forwardable'
29
+
30
+ module Gamera
31
+ # Builders provide a generic, standard interface for creating/setting
32
+ # data in an app.
33
+ #
34
+ # For easy creation of a builder use the +with_options()+ class
35
+ # builder method. For example:
36
+ #
37
+ # class UserBuilder < Builder.with_options(:name, :bday, :nickname)
38
+ # def build
39
+ # User.create!(name: name, bday: bday, nickname: nickname)
40
+ # end
41
+ # end
42
+ #
43
+ # The created builder will automatically have methods to set each of the
44
+ # options. In the example above the +UserBuilder#with_nickname+ method
45
+ # will return a new +UserBuilder+ that has the specified nickname.
46
+ #
47
+ # UserBuilder.new
48
+ # .with_nickname("shorty")
49
+ # .result
50
+ # #=> User(name: nil, bday: nil, nickname: "shorty")
51
+ #
52
+ # Sometimes the way you refer to values inside the builder may be
53
+ # different than how clients do. In that case, builders can define
54
+ # setter methods that use terminology that matches the client's point
55
+ # of view.
56
+ #
57
+ # class UserBuilder < Builder.with_options(:name, :bday)
58
+ # def born_on(a_date)
59
+ # refine_with bday: a_date
60
+ # end
61
+ # end
62
+ #
63
+ # Default values can be specified using the +default_for+ class
64
+ # method.
65
+ #
66
+ # class UserBuilder < Builder.with_options(:name, :bday)
67
+ # default_for :name, "Jane"
68
+ # default_for :bday { Time.now }
69
+ # end
70
+ #
71
+ # You can handle type conversions using coercion methods.
72
+ #
73
+ # class UserBuilder < Builder.with_options(:name, :bday)
74
+ # def name_coercion(new_name)
75
+ # new_name ? new_name.to_s : "Bob"
76
+ # end
77
+ # end
78
+ #
79
+ # To use this builder:
80
+ #
81
+ # UserBuilder.new
82
+ # .born_on(25.years.ago)
83
+ # .result
84
+ # #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
85
+ #
86
+ # or
87
+ #
88
+ # UserBuilder.new
89
+ # .with_bday(25.years.ago)
90
+ # .result
91
+ # #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
92
+ #
93
+ # or
94
+ #
95
+ # UserBuilder.new(bday: 25.years.ago)
96
+ # .result
97
+ # #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
98
+ class Builder
99
+ extend Forwardable
100
+
101
+ # One way to create builders.
102
+ #
103
+ # b = Builder.create_with(name: "Bob", bday: 25.years.ago) do
104
+ # User.create!(name: name, bday: bday)
105
+ # end
106
+ #
107
+ # b.build
108
+ # #=> User(name: "Bob", bday: #<Date: Sat, 09 Dec 1989>)
109
+ def self.create_with(spec, &block)
110
+ struct = Struct.new(*(spec.keys)) do
111
+ def initialize(options = {})
112
+ super
113
+ options.each { |opt, value| self[opt] = value }
114
+ end
115
+
116
+ def with(options, &block)
117
+ new_struct = dup
118
+ options.each { |opt, value| new_struct[opt] = value }
119
+ if block_given?
120
+ new_struct.class.class_eval do
121
+ define_method :build, &block
122
+ end
123
+ end
124
+ new_struct
125
+ end
126
+
127
+ members.each do |opt|
128
+ define_method "with_#{opt}" do |value, &inner_block|
129
+ with(opt => value, &inner_block)
130
+ end
131
+ end
132
+
133
+ define_method :build, &block
134
+ end
135
+
136
+ struct.new spec
137
+ end
138
+
139
+ # Module to extend the Builder DSL
140
+ module Dsl
141
+ # Sets the default value of an option
142
+ #
143
+ # @param option_name [String] Name of the builder option
144
+ # @param val [Object] the simple default value of the option
145
+ # @param gen [Block] block that returns default values
146
+ #
147
+ # Yields self to block (+gen+) if a block is provided. Return
148
+ # value will be the default value.
149
+ def default_for(option_name, val = nil, &gen)
150
+ gen ||= ->(_) { val }
151
+
152
+ prepend(Module.new do
153
+ define_method :"#{option_name}_coercion" do |v|
154
+ super v.nil? ? gen.call(self) : v
155
+ end
156
+ end)
157
+ end
158
+ end
159
+
160
+ extend Dsl
161
+
162
+ # Another way to create builders.
163
+ #
164
+ # For easy creation of a builder use the +with_options()+ class
165
+ # builder method. For example:
166
+ #
167
+ # class UserBuilder < Builder.with_options(:name, :bday, :nickname)
168
+ # def build
169
+ # User.create!(name: name, bday: bday, nickname: nickname)
170
+ # end
171
+ # end
172
+ #
173
+ # The created builder will automatically have methods to set each of the
174
+ # options. In the example above the +UserBuilder#with_nickname+ method
175
+ # will return a new +UserBuilder+ that has the specified nickname.
176
+ #
177
+ # UserBuilder.new
178
+ # .with_nickname("shorty")
179
+ # .result
180
+ # #=> User(name: nil, bday: nil, nickname: "shorty")
181
+ #
182
+ def self.with_options(*option_names)
183
+ init_arg_list = option_names.map { |o| "#{o}: nil" }.join(', ')
184
+ args_to_ivars = option_names.map { |o| "@#{o} = #{o}_coercion(#{o})" }.join('; ')
185
+
186
+ Class.new(self) do
187
+ module_eval <<-METH
188
+ def initialize(#{init_arg_list}) # def initialize(tags: nil, name: nil)
189
+ #{args_to_ivars} # @tags = tags_coercion(tags); @name = name_coercion(name)
190
+ super() # super()
191
+ end # end
192
+ METH
193
+
194
+ # +with_...+ methods
195
+ option_names.each do |name|
196
+ define_method(:"with_#{name}") do |new_val, *extra|
197
+ val = if extra.any?
198
+ # called with multiple params, eg (p1,p2,p3,...), so
199
+ # package those as an array and pass them in
200
+ [new_val] + extra
201
+ else
202
+ # called with single param
203
+ new_val
204
+ end
205
+
206
+ refine_with(name => val)
207
+ end
208
+ end
209
+
210
+ protected
211
+
212
+ attr_reader(*option_names)
213
+
214
+ define_method(:options) do
215
+ Hash[option_names.map { |o| [o, send(o)] }]
216
+ end
217
+
218
+ option_names.each do |o_name|
219
+ define_method(:"#{o_name}_coercion") { |new_val| new_val }
220
+ end
221
+ end
222
+ end
223
+
224
+ # The object built by this builder
225
+ def result
226
+ @result ||= build
227
+ end
228
+
229
+ # Executes the builder
230
+ #
231
+ # @note Don't call this method directly, use +#result+ instead.
232
+ def build
233
+ fail NotImplementedError
234
+ end
235
+
236
+ def_delegator :self, :build, :call
237
+
238
+ # Returns a clone of this object but with options listed in
239
+ # +alterations+ updated to match.
240
+ def refine_with(alterations)
241
+ self.class.new options.merge(alterations)
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,249 @@
1
+ # encoding:utf-8
2
+ #--
3
+ # The MIT License (MIT)
4
+ #
5
+ # Copyright (c) 2015, The Gamera Development Team. See the COPYRIGHT file at
6
+ # the top-level directory of this distribution and at
7
+ # http://github.com/gamera-team/gamera/COPYRIGHT.
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in
17
+ # all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ # THE SOFTWARE.
26
+ #++
27
+
28
+ require 'yaml'
29
+ require 'sequel'
30
+ require 'sequel-fixture'
31
+
32
+ module Gamera
33
+ module Builders
34
+ # A builder for loading YAML fixtures using a Sequel DB connection.
35
+ #
36
+ # Usage:
37
+ #
38
+ # Gamera::Builders::SequelFixtureBuilder.new(
39
+ # database_config: "/path/to/database.yml",
40
+ # fixture_directory: "/path/to/fixtures/",
41
+ # database_cleaner_options: { skip: false, tables: ['users', 'messages'] }
42
+ # ).build
43
+ #
44
+ # Or:
45
+ #
46
+ # Gamera::Builders::SequelFixtureBuilder.new.
47
+ # with_database_config("/path/to/database.yml").
48
+ # with_fixture_directory("/path/to/fixtures/").
49
+ # with_database_cleaner_options({ skip: false, tables: ['users', 'messages'] }).
50
+ # build
51
+ #
52
+ # Defaults:
53
+ # database_config: ./config/database.yml
54
+ # fixture_directory: ./spec/fixtures/ or ./test/fixtures/
55
+ # database_cleaner_options:
56
+ # skip: false
57
+ # tables: (all tables in the database)
58
+ #
59
+ # So if you follow these defaults, you can simply do:
60
+ #
61
+ # Gamera::Builders::SequelFixtureBuilder.new.build
62
+ class SequelFixtureBuilder < Gamera::Builder.with_options(:database_config, :fixture_directory, :database_cleaner_options)
63
+ DEFAULT_DATABASE_CONFIG = './config/database.yml'
64
+ DEFAULT_SPEC_FIXTURE_DIRECTORY = './spec/fixtures'
65
+ DEFAULT_TEST_FIXTURE_DIRECTORY = './test/fixtures'
66
+
67
+ # Truncates the database and imports the fixtures.
68
+ # Returns a +Sequel::Fixture+ object, containing
69
+ # hashes of all the fixtures by table name
70
+ # (https://github.com/whitepages/sequel-fixture).
71
+ def build
72
+ # Truncate all tables
73
+ unless skip_database_cleaner
74
+ cleaner = Utils::DatabaseCleaner.new(db, database_cleaner_tables)
75
+ cleaner.clean
76
+ end
77
+
78
+ fixture_path, fixture_dir = File.split(path_to_fixtures)
79
+
80
+ Sequel::Fixture.path = fixture_path
81
+
82
+ Sequel::Fixture.new(fixture_dir.to_sym, db)
83
+ end
84
+
85
+ # The +Sequel+ database connection.
86
+ # Raises +Gamera::DatabaseNotConfigured+ if it fails
87
+ # to initialize the database from the given config
88
+ # or defaults.
89
+ def db
90
+ @db ||= self.class.db(database_config)
91
+ end
92
+
93
+ # Finds the full path to the fixtures directory.
94
+ # Uses the given +fixture_directory+ if given.
95
+ # Otherwise tries to use ./spec/fixtures or ./test/fixtures.
96
+ #
97
+ # Raises +Gamera::DatabaseNotConfigured+ if it cannot find
98
+ def path_to_fixtures
99
+ @path_to_fixtures ||= begin
100
+ if fixture_directory && !fixture_directory.empty?
101
+ unless File.exist?(fixture_directory)
102
+ fail DatabaseNotConfigured, "Invalid fixture directory #{fixture_directory}"
103
+ end
104
+ fixture_directory
105
+ elsif File.exist?(DEFAULT_SPEC_FIXTURE_DIRECTORY)
106
+ DEFAULT_SPEC_FIXTURE_DIRECTORY
107
+ elsif File.exist?(DEFAULT_TEST_FIXTURE_DIRECTORY)
108
+ DEFAULT_TEST_FIXTURE_DIRECTORY
109
+ else
110
+ fail DatabaseNotConfigured, 'Unable to find fixtures to load'
111
+ end
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ # The cache of +Sequel+ database connections by database config.
118
+ # Raises +Gamera::DatabaseNotConfigured+ if it fails to initialize
119
+ # the database from the given config
120
+ def self.db(database_config)
121
+ @db ||= {}
122
+ @db[database_config || DEFAULT_DATABASE_CONFIG] ||= begin
123
+ config = database_config_from_file(database_config) || database_config_from_hash(database_config) || database_config_from_default
124
+ if config
125
+ Sequel.connect(config)
126
+ else
127
+ fail DatabaseNotConfigured, 'Unable to connect to database'
128
+ end
129
+ end
130
+ end
131
+
132
+ # Attempts to load database config by interpretting the given
133
+ # config as a string path to a YAML config file.
134
+ #
135
+ # Can accept a file with top-level keys matching environments,
136
+ # like those found in Rails database.yml files,
137
+ # in which case, it chooses the 'test' environment.
138
+ #
139
+ # Alternatively, it can accept a file with top-level keys matching
140
+ # the database config keys.
141
+ #
142
+ # Database config keys are:
143
+ # => adapter
144
+ # => database
145
+ # => username
146
+ # => password (optional)
147
+ # => host (optional)
148
+ #
149
+ # Returns hash containing the database config options
150
+ # if possible, and nil if not.
151
+ def self.database_config_from_file(config = nil)
152
+ return nil unless config.is_a?(String) && File.exist?(config)
153
+
154
+ db_config = YAML.load_file(config) rescue nil
155
+ return nil unless db_config
156
+
157
+ database_config_from_hash(db_config)
158
+ end
159
+
160
+ # Attempts to load database config by interpretting the given
161
+ # config as a hash with the config options.
162
+ #
163
+ # Can accept a hash with top-level keys matching environments,
164
+ # like those found in Rails database.yml files,
165
+ # in which case, it chooses the 'test' or :test environment.
166
+ #
167
+ # Alternatively, it can accept a hash with top-level keys matching
168
+ # the database config keys.
169
+ #
170
+ # Database config keys are:
171
+ # => adapter
172
+ # => database
173
+ # => username
174
+ # => password (optional)
175
+ # => host (optional)
176
+ #
177
+ # Returns hash containing the database config options
178
+ # if possible, and nil if not.
179
+ def self.database_config_from_hash(config = nil)
180
+ return nil unless config.is_a?(Hash)
181
+
182
+ db_config = if config.key?('test')
183
+ config['test']
184
+ elsif config.key?(:test)
185
+ config[:test]
186
+ else
187
+ config
188
+ end
189
+
190
+ verify_database_config(db_config)
191
+
192
+ db_config
193
+ end
194
+
195
+ # Attempts to load the database config from the default
196
+ # location of ./config/database.yml if the file exists.
197
+ #
198
+ # Delegates to #database_config_from_file
199
+ def self.database_config_from_default
200
+ database_config_from_file(DEFAULT_DATABASE_CONFIG)
201
+ end
202
+
203
+ # Given a hash, confirms all required DB config fields are
204
+ # present. Otherwise raises +Gamera::DatabaseNotConfigured+.
205
+ #
206
+ # Required fields (string or symbol):
207
+ # => adapter
208
+ # => database
209
+ # => username
210
+ def self.verify_database_config(db_config)
211
+ db_config ||= {}
212
+ missing_fields = [:adapter, :database, :username].reject do |field|
213
+ db_config.key?(field) || db_config.key?(field.to_s)
214
+ end
215
+ unless missing_fields.empty?
216
+ fail DatabaseNotConfigured, "Unable to connect to database: Missing config for #{missing_fields.join(', ')}"
217
+ end
218
+ end
219
+
220
+ # Boolean from database_cleaner_options.
221
+ # Defaults to +false+ if not set.
222
+ def skip_database_cleaner
223
+ database_cleaner_option(:skip, false)
224
+ end
225
+
226
+ # Array of table names from database_cleaner_options.
227
+ # Defaults to +nil+, which is interpretted as "all tables".
228
+ def database_cleaner_tables
229
+ database_cleaner_option(:tables, nil)
230
+ end
231
+
232
+ # Retrieves options from the +database_cleaner_options+
233
+ # hash, returning the given +default+ if the
234
+ # database_cleaner_options don't exist, or the given
235
+ # key (as a symbol or string) isn't set.
236
+ def database_cleaner_option(symbol, default)
237
+ options = database_cleaner_options || {}
238
+
239
+ if options.key?(symbol)
240
+ options[symbol]
241
+ elsif options.key?(symbol.to_s)
242
+ options[symbol.to_s]
243
+ else
244
+ default
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end