validacity 0.1.0 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37e0d8cfd4674671362980b8431da1c4ed1714e9c9217ec6453c7eb0179db44c
4
- data.tar.gz: 6382080b9a6bcc1ec95c31539ba5e597fc7b9029e185aaae5527e31a1d2ef70f
3
+ metadata.gz: ff1728df7b05d4d9882b039884cba8985df95824810d8f64f2a363d6f109655a
4
+ data.tar.gz: 1b2071f2cf21c10aeda419eb264e456942550a50fe79acb9fdaabbd7b3110304
5
5
  SHA512:
6
- metadata.gz: b3e8278b9b416944cffef47ae862b447d92d1ec9ec405f0f684cf0c95cfca8143b1864093ce06c38e9f6f9f52ce96e032ff8a34fd762a29173515d808881762e
7
- data.tar.gz: 1c4d95d4ff6742ae90992d840e9e5b0b2561b8718f60068c8a453b544e0550ca83cb8f1c75af8e7e2be6ed7150539ef7217dd5be3becf34aaf241d554a854c6b
6
+ metadata.gz: 15d81cf9ad56fd8255984d73e98a1e825bd380f70b002324f22c7e292b098ee6ec01266b95cae5c90906418fc27f7c4f74974a680cbdb9780cc9a1cb4401b69c
7
+ data.tar.gz: 9ac2b3080c9c2cd2cc34ad483b58961050d4bf63fca2c33020bc9bdbb185e8499959207fc202463dcd7c8a0bdcf458510d210e8b2fdc154f2ba5900f97aff302
data/README.md CHANGED
@@ -1,8 +1,49 @@
1
1
  # Validacity
2
- Short description and motivation.
2
+
3
+ Move your validation logic out of your models or form objects.
3
4
 
4
5
  ## Usage
5
- How to use my plugin.
6
+
7
+ You can append validators on-fly and then remove some of them if not needed.
8
+
9
+ It can be very suitable if you need to validate different states of the same object differently. Say you're using a state machine and each state have it's own field set and the fields from other satates shouldn't be validated at this time.
10
+
11
+ Or perhaps you just have to many constructions like `validate :blablabla, presence: true` in your model and you want to get rid of it.
12
+
13
+ All you need is to add a concern to your model:
14
+
15
+ ```ruby
16
+
17
+ # app/models/user.rb
18
+
19
+ class User
20
+ include Validacity::Validatable
21
+ validations :user_personal_data
22
+ end
23
+
24
+ ```
25
+
26
+ And generate a validator:
27
+
28
+ ```ruby
29
+
30
+ # app/validators/user_personal_data_validator.rb
31
+
32
+ class UserPersonalDataValidator
33
+ validate :name, presence_of: true
34
+ # ...a ton of different validators
35
+ end
36
+
37
+ ```
38
+
39
+ Now call validator with the regular methods like:
40
+
41
+
42
+ ```ruby
43
+
44
+ user.valid?
45
+
46
+ ```
6
47
 
7
48
  ## Installation
8
49
  Add this line to your application's Gemfile:
@@ -16,9 +57,14 @@ And then execute:
16
57
  $ bundle
17
58
  ```
18
59
 
19
- Or install it yourself as:
60
+ Now run the validator installation command:
61
+ ```bash
62
+ $ bundle exec rails g validacity:install
63
+ ```
64
+
65
+ And the event validator:
20
66
  ```bash
21
- $ gem install validacity
67
+ $ bundle exec rails g validacity:validator Event
22
68
  ```
23
69
 
24
70
  ## Contributing
@@ -1,21 +1,453 @@
1
+ begin
2
+ require "thor/group"
3
+ rescue LoadError
4
+ puts "Thor is not available.\nIf you ran this command from a git checkout " \
5
+ "of Rails, please make sure thor is installed,\nand run this command " \
6
+ "as `ruby #{$0} #{(ARGV | ['--dev']).join(" ")}`"
7
+ exit
8
+ end
9
+
1
10
  module Validacity
2
11
  module Generators
3
- class ValidacityGenerator < Rails::Generators::NamedBase
12
+ class InstallGenerator < ::Rails::Generators::Base
4
13
  source_root File.expand_path("templates", __dir__)
5
14
 
6
15
  def create_initializer_file
7
- content = <<-'RUBY'
16
+ content = <<-'RUBY'.gsub(/^\ {10}/, "")
17
+ require_dependency "validacity"
18
+
8
19
  Validacity.configure do |config|
9
- config.search_paths "#{namespaced_path}/app/validators/**/*_validator.rb"
20
+ config.search_paths "#{__dir__}/../app/validators/**/*_validator.rb"
10
21
  end
11
22
  RUBY
12
- create_file "#{namespaced_path}/config/initializers/validacity_initializer.rb",
13
- content
23
+ create_file "config/initializers/validacity_initializer.rb", content
14
24
  end
15
25
 
16
26
  def copy_application_policy
17
- template "application_validator.rb",
18
- "app/validators/application_validator.rb"
27
+ validator_file = File.join("app/validators", class_path,
28
+ "application_validator.rb")
29
+ template "application_validator.rb", validator_file
30
+ end
31
+
32
+ include Thor::Actions
33
+ include Rails::Generators::Actions
34
+
35
+ class_option :skip_namespace, type: :boolean, default: false,
36
+ desc: "Skip namespace (affects only isolated applications)"
37
+
38
+ add_runtime_options!
39
+ strict_args_position!
40
+
41
+ # Returns the source root for this generator using default_source_root as default.
42
+ def self.source_root(path = nil)
43
+ @_source_root = path if path
44
+ @_source_root ||= default_source_root
45
+ end
46
+
47
+ # Tries to get the description from a USAGE file one folder above the source
48
+ # root otherwise uses a default description.
49
+ def self.desc(description = nil)
50
+ return super if description
51
+
52
+ @desc ||= if usage_path
53
+ ERB.new(File.read(usage_path)).result(binding)
54
+ else
55
+ "Description:\n Create #{base_name.humanize.downcase} files for #{generator_name} generator."
56
+ end
57
+ end
58
+
59
+ # Convenience method to get the namespace from the class name. It's the
60
+ # same as Thor default except that the Generator at the end of the class
61
+ # is removed.
62
+ def self.namespace(name = nil)
63
+ return super if name
64
+ @namespace ||= super.sub(/_generator$/, "").sub(/:generators:/, ":")
65
+ end
66
+
67
+ # Convenience method to hide this generator from the available ones when
68
+ # running rails generator command.
69
+ def self.hide!
70
+ Rails::Generators.hide_namespace(namespace)
71
+ end
72
+
73
+ # Invoke a generator based on the value supplied by the user to the
74
+ # given option named "name". A class option is created when this method
75
+ # is invoked and you can set a hash to customize it.
76
+ #
77
+ # ==== Examples
78
+ #
79
+ # module Rails::Generators
80
+ # class ControllerGenerator < Base
81
+ # hook_for :test_framework, aliases: "-t"
82
+ # end
83
+ # end
84
+ #
85
+ # The example above will create a test framework option and will invoke
86
+ # a generator based on the user supplied value.
87
+ #
88
+ # For example, if the user invoke the controller generator as:
89
+ #
90
+ # rails generate controller Account --test-framework=test_unit
91
+ #
92
+ # The controller generator will then try to invoke the following generators:
93
+ #
94
+ # "rails:test_unit", "test_unit:controller", "test_unit"
95
+ #
96
+ # Notice that "rails:generators:test_unit" could be loaded as well, what
97
+ # Rails looks for is the first and last parts of the namespace. This is what
98
+ # allows any test framework to hook into Rails as long as it provides any
99
+ # of the hooks above.
100
+ #
101
+ # ==== Options
102
+ #
103
+ # The first and last part used to find the generator to be invoked are
104
+ # guessed based on class invokes hook_for, as noticed in the example above.
105
+ # This can be customized with two options: :in and :as.
106
+ #
107
+ # Let's suppose you are creating a generator that needs to invoke the
108
+ # controller generator from test unit. Your first attempt is:
109
+ #
110
+ # class AwesomeGenerator < Rails::Generators::Base
111
+ # hook_for :test_framework
112
+ # end
113
+ #
114
+ # The lookup in this case for test_unit as input is:
115
+ #
116
+ # "test_unit:awesome", "test_unit"
117
+ #
118
+ # Which is not the desired lookup. You can change it by providing the
119
+ # :as option:
120
+ #
121
+ # class AwesomeGenerator < Rails::Generators::Base
122
+ # hook_for :test_framework, as: :controller
123
+ # end
124
+ #
125
+ # And now it will look up at:
126
+ #
127
+ # "test_unit:controller", "test_unit"
128
+ #
129
+ # Similarly, if you want it to also look up in the rails namespace, you
130
+ # just need to provide the :in value:
131
+ #
132
+ # class AwesomeGenerator < Rails::Generators::Base
133
+ # hook_for :test_framework, in: :rails, as: :controller
134
+ # end
135
+ #
136
+ # And the lookup is exactly the same as previously:
137
+ #
138
+ # "rails:test_unit", "test_unit:controller", "test_unit"
139
+ #
140
+ # ==== Switches
141
+ #
142
+ # All hooks come with switches for user interface. If you do not want
143
+ # to use any test framework, you can do:
144
+ #
145
+ # rails generate controller Account --skip-test-framework
146
+ #
147
+ # Or similarly:
148
+ #
149
+ # rails generate controller Account --no-test-framework
150
+ #
151
+ # ==== Boolean hooks
152
+ #
153
+ # In some cases, you may want to provide a boolean hook. For example, webrat
154
+ # developers might want to have webrat available on controller generator.
155
+ # This can be achieved as:
156
+ #
157
+ # Rails::Generators::ControllerGenerator.hook_for :webrat, type: :boolean
158
+ #
159
+ # Then, if you want webrat to be invoked, just supply:
160
+ #
161
+ # rails generate controller Account --webrat
162
+ #
163
+ # The hooks lookup is similar as above:
164
+ #
165
+ # "rails:generators:webrat", "webrat:generators:controller", "webrat"
166
+ #
167
+ # ==== Custom invocations
168
+ #
169
+ # You can also supply a block to hook_for to customize how the hook is
170
+ # going to be invoked. The block receives two arguments, an instance
171
+ # of the current class and the class to be invoked.
172
+ #
173
+ # For example, in the resource generator, the controller should be invoked
174
+ # with a pluralized class name. But by default it is invoked with the same
175
+ # name as the resource generator, which is singular. To change this, we
176
+ # can give a block to customize how the controller can be invoked.
177
+ #
178
+ # hook_for :resource_controller do |instance, controller|
179
+ # instance.invoke controller, [ instance.name.pluralize ]
180
+ # end
181
+ #
182
+ def self.hook_for(*names, &block)
183
+ options = names.extract_options!
184
+ in_base = options.delete(:in) || base_name
185
+ as_hook = options.delete(:as) || generator_name
186
+
187
+ names.each do |name|
188
+ unless class_options.key?(name)
189
+ defaults = if options[:type] == :boolean
190
+ {}
191
+ elsif [true, false].include?(default_value_for_option(name, options))
192
+ { banner: "" }
193
+ else
194
+ { desc: "#{name.to_s.humanize} to be invoked", banner: "NAME" }
195
+ end
196
+
197
+ class_option(name, defaults.merge!(options))
198
+ end
199
+
200
+ hooks[name] = [ in_base, as_hook ]
201
+ invoke_from_option(name, options, &block)
202
+ end
203
+ end
204
+
205
+ # Remove a previously added hook.
206
+ #
207
+ # remove_hook_for :orm
208
+ def self.remove_hook_for(*names)
209
+ remove_invocation(*names)
210
+
211
+ names.each do |name|
212
+ hooks.delete(name)
213
+ end
214
+ end
215
+
216
+ # Make class option aware of Rails::Generators.options and Rails::Generators.aliases.
217
+ def self.class_option(name, options = {}) #:nodoc:
218
+ options[:desc] = "Indicates when to generate #{name.to_s.humanize.downcase}" unless options.key?(:desc)
219
+ options[:aliases] = default_aliases_for_option(name, options)
220
+ options[:default] = default_value_for_option(name, options)
221
+ super(name, options)
222
+ end
223
+
224
+ # Returns the default source root for a given generator. This is used internally
225
+ # by rails to set its generators source root. If you want to customize your source
226
+ # root, you should use source_root.
227
+ def self.default_source_root
228
+ return unless base_name && generator_name
229
+ return unless default_generator_root
230
+ path = File.join(default_generator_root, "templates")
231
+ path if File.exist?(path)
232
+ end
233
+
234
+ # Returns the base root for a common set of generators. This is used to dynamically
235
+ # guess the default source root.
236
+ def self.base_root
237
+ __dir__
238
+ end
239
+
240
+ # Cache source root and add lib/generators/base/generator/templates to
241
+ # source paths.
242
+ def self.inherited(base) #:nodoc:
243
+ super
244
+
245
+ # Invoke source_root so the default_source_root is set.
246
+ base.source_root
247
+
248
+ if base.name && base.name !~ /Base$/
249
+ Rails::Generators.subclasses << base
250
+
251
+ Rails::Generators.templates_path.each do |path|
252
+ if base.name.include?("::")
253
+ base.source_paths << File.join(path, base.base_name, base.generator_name)
254
+ else
255
+ base.source_paths << File.join(path, base.generator_name)
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ private
262
+
263
+ # Check whether the given class names are already taken by user
264
+ # application or Ruby on Rails.
265
+ def class_collisions(*class_names)
266
+ return unless behavior == :invoke
267
+
268
+ class_names.flatten.each do |class_name|
269
+ class_name = class_name.to_s
270
+ next if class_name.strip.empty?
271
+
272
+ # Split the class from its module nesting
273
+ nesting = class_name.split("::")
274
+ last_name = nesting.pop
275
+ last = extract_last_module(nesting)
276
+
277
+ if last && last.const_defined?(last_name.camelize, false)
278
+ raise Error, "The name '#{class_name}' is either already used in your application " \
279
+ "or reserved by Ruby on Rails. Please choose an alternative and run " \
280
+ "this generator again."
281
+ end
282
+ end
283
+ end
284
+
285
+ # Takes in an array of nested modules and extracts the last module
286
+ def extract_last_module(nesting) # :doc:
287
+ nesting.inject(Object) do |last_module, nest|
288
+ break unless last_module.const_defined?(nest, false)
289
+ last_module.const_get(nest)
290
+ end
291
+ end
292
+
293
+ # Wrap block with namespace of current application
294
+ # if namespace exists and is not skipped
295
+ def module_namespacing(&block) # :doc:
296
+ content = capture(&block)
297
+ content = wrap_with_namespace(content) if namespaced?
298
+ concat(content)
299
+ end
300
+
301
+ def indent(content, multiplier = 2) # :doc:
302
+ spaces = " " * multiplier
303
+ content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join
304
+ end
305
+
306
+ def wrap_with_namespace(content) # :doc:
307
+ content = indent(content).chomp
308
+ "module #{namespace.name}\n#{content}\nend\n"
309
+ end
310
+
311
+ def namespace # :doc:
312
+ Rails::Generators.namespace
313
+ end
314
+
315
+ def namespaced? # :doc:
316
+ !options[:skip_namespace] && namespace
317
+ end
318
+
319
+ def namespace_dirs
320
+ @namespace_dirs ||= namespace.name.split("::").map(&:underscore)
321
+ end
322
+
323
+ def namespaced_path # :doc:
324
+ @namespaced_path ||= namespace_dirs.join("/")
325
+ end
326
+
327
+ # Use Rails default banner.
328
+ def self.banner # :doc:
329
+ "rails generate #{namespace.sub(/^rails:/, '')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ")
330
+ end
331
+
332
+ # Sets the base_name taking into account the current class namespace.
333
+ def self.base_name # :doc:
334
+ @base_name ||= begin
335
+ if base = name.to_s.split("::").first
336
+ base.underscore
337
+ end
338
+ end
339
+ end
340
+
341
+ # Removes the namespaces and get the generator name. For example,
342
+ # Rails::Generators::ModelGenerator will return "model" as generator name.
343
+ def self.generator_name # :doc:
344
+ @generator_name ||= begin
345
+ if generator = name.to_s.split("::").last
346
+ generator.sub!(/Generator$/, "")
347
+ generator.underscore
348
+ end
349
+ end
350
+ end
351
+
352
+ # Returns the default value for the option name given doing a lookup in
353
+ # Rails::Generators.options.
354
+ def self.default_value_for_option(name, options) # :doc:
355
+ default_for_option(Rails::Generators.options, name, options, options[:default])
356
+ end
357
+
358
+ # Returns default aliases for the option name given doing a lookup in
359
+ # Rails::Generators.aliases.
360
+ def self.default_aliases_for_option(name, options) # :doc:
361
+ default_for_option(Rails::Generators.aliases, name, options, options[:aliases])
362
+ end
363
+
364
+ # Returns default for the option name given doing a lookup in config.
365
+ def self.default_for_option(config, name, options, default) # :doc:
366
+ if generator_name && (c = config[generator_name.to_sym]) && c.key?(name)
367
+ c[name]
368
+ elsif base_name && (c = config[base_name.to_sym]) && c.key?(name)
369
+ c[name]
370
+ elsif config[:rails].key?(name)
371
+ config[:rails][name]
372
+ else
373
+ default
374
+ end
375
+ end
376
+
377
+ # Keep hooks configuration that are used on prepare_for_invocation.
378
+ def self.hooks #:nodoc:
379
+ @hooks ||= from_superclass(:hooks, {})
380
+ end
381
+
382
+ # Prepare class invocation to search on Rails namespace if a previous
383
+ # added hook is being used.
384
+ def self.prepare_for_invocation(name, value) #:nodoc:
385
+ return super unless value.is_a?(String) || value.is_a?(Symbol)
386
+
387
+ if value && constants = hooks[name]
388
+ value = name if TrueClass === value
389
+ Rails::Generators.find_by_namespace(value, *constants)
390
+ elsif klass = Rails::Generators.find_by_namespace(value)
391
+ klass
392
+ else
393
+ super
394
+ end
395
+ end
396
+
397
+ # Small macro to add ruby as an option to the generator with proper
398
+ # default value plus an instance helper method called shebang.
399
+ def self.add_shebang_option! # :doc:
400
+ class_option :ruby, type: :string, aliases: "-r", default: Thor::Util.ruby_command,
401
+ desc: "Path to the Ruby binary of your choice", banner: "PATH"
402
+
403
+ no_tasks {
404
+ define_method :shebang do
405
+ @shebang ||= begin
406
+ command = if options[:ruby] == Thor::Util.ruby_command
407
+ "/usr/bin/env #{File.basename(Thor::Util.ruby_command)}"
408
+ else
409
+ options[:ruby]
410
+ end
411
+ "#!#{command}"
412
+ end
413
+ end
414
+ }
415
+ end
416
+
417
+ def self.usage_path # :doc:
418
+ paths = [
419
+ source_root && File.expand_path("../USAGE", source_root),
420
+ default_generator_root && File.join(default_generator_root, "USAGE")
421
+ ]
422
+ paths.compact.detect { |path| File.exist? path }
423
+ end
424
+
425
+ def self.default_generator_root # :doc:
426
+ path = File.expand_path(File.join(base_name, generator_name), base_root)
427
+ path if File.exist?(path)
428
+ end
429
+
430
+ def class_path # :doc:
431
+ inside_template? || !namespaced? ? regular_class_path : namespaced_class_path
432
+ end
433
+
434
+ def inside_template # :doc:
435
+ @inside_template = true
436
+ yield
437
+ ensure
438
+ @inside_template = false
439
+ end
440
+
441
+ def inside_template? # :doc:
442
+ @inside_template
443
+ end
444
+
445
+ def regular_class_path # :doc:
446
+ @class_path || []
447
+ end
448
+
449
+ def namespaced_class_path # :doc:
450
+ @namespaced_class_path ||= namespace_dirs + regular_class_path
19
451
  end
20
452
  end
21
453
  end
@@ -1,11 +1,10 @@
1
1
  module Validacity
2
2
  module Generators
3
- class ValidacityGenerator < ::Rails::Generators::NamedBase
3
+ class ValidatorGenerator < ::Rails::Generators::NamedBase
4
4
  source_root File.expand_path("templates", __dir__)
5
5
 
6
6
  def create_validator
7
- validator_file = File.join("app/validators",
8
- class_path,
7
+ validator_file = File.join("app/validators", class_path,
9
8
  "#{file_name}_validator.rb")
10
9
  template "validator.rb", validator_file
11
10
  end
@@ -2,13 +2,35 @@ module Validacity
2
2
  module Validatable
3
3
  extend ActiveSupport::Concern
4
4
 
5
- def valid?
5
+ class_methods do
6
+ def validations(*names)
7
+ names.each { |n| validacity_validators << n }
8
+ end
9
+
10
+ def validacity_validators
11
+ @_validacity_validators ||= Set.new
12
+ end
13
+
14
+ def new(resource)
15
+ instance = super
16
+ instance.validacity_validators.merge(validacity_validators.dup)
17
+ instance
18
+ end
19
+ end
20
+
21
+ def validacity_validators
22
+ @_validacity_validators ||= Set.new
23
+ end
24
+
25
+ def valid?(context = nil)
6
26
  super
7
- validacity_validators_run
27
+ validacity_validators_run(context)
8
28
  errors.empty?
9
29
  end
10
30
 
11
- def validacity_validators_run
31
+ private
32
+
33
+ def validacity_validators_run(_context = nil)
12
34
  each_validator_instance(&:validate)
13
35
  end
14
36
 
@@ -22,9 +44,5 @@ module Validacity
22
44
  yield(validator)
23
45
  end
24
46
  end
25
-
26
- def validacity_validators
27
- @_validacity_validators ||= Set.new
28
- end
29
47
  end
30
48
  end
@@ -1,3 +1,3 @@
1
1
  module Validacity
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validacity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Zinovyev
@@ -123,9 +123,8 @@ files:
123
123
  - Rakefile
124
124
  - lib/generators/validacity/install_generator.rb
125
125
  - lib/generators/validacity/templates/application_validator.rb
126
- - lib/generators/validator/templates/validator.rb
127
- - lib/generators/validator/validator_generator.rb
128
- - lib/tasks/validacity_tasks.rake
126
+ - lib/generators/validacity/templates/validator.rb
127
+ - lib/generators/validacity/validator_generator.rb
129
128
  - lib/validacity.rb
130
129
  - lib/validacity/base_validator.rb
131
130
  - lib/validacity/configuration.rb
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :validacity do
3
- # # Task goes here
4
- # end