validacity 0.1.0 → 0.2.0

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