gamera 0.1.0

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