active_dry_form 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1c28b3e63865ce1e56a06a1e544e0bff16ed8404539ff57825ce34e529b1eae1
4
+ data.tar.gz: d470fc5ba86f63a5ba48b9355468b958c82f8f08f83182e455930c3f9c1dd3b7
5
+ SHA512:
6
+ metadata.gz: 11c27d50c4bf87a26ac9c9e1901e8961674fdd871f5cf7f3669eb44d3cdadb7f3bc6443690a26a6bee6a5866416f9854b22e9a8e34dd2731a81b81b16f31571d
7
+ data.tar.gz: 530b8d0416ad67c9b5db3080b44d348b7b9336723134499290b484ab245f46b9634b11f32a574f01c65069de86623214a723b70a9e19e4e1e1e3eb920a15fa7f
data/.editorconfig ADDED
@@ -0,0 +1,10 @@
1
+ [*]
2
+ indent_style = space
3
+ indent_size = 2
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ trim_trailing_whitespace = true
7
+
8
+ [Makefile]
9
+ indent_style = tab
10
+ indent_size = 3
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,3 @@
1
+ inherit_gem:
2
+ rubocop-gp:
3
+ - ./config/default.yml
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in active_dry_form.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem 'rubocop-gp', github: 'corp-gp/rubocop-gp', require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Ermolaev Andrey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # ActiveDryForm
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_dry_form`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'active_dry_form'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install active_dry_form
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_dry_form.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "active_dry_form"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveDryForm
4
+ class Builder < ActionView::Helpers::FormBuilder
5
+
6
+ def input(method, options = {})
7
+ dry_tag = ActiveDryForm::Input.new(self, __method__, method, options)
8
+
9
+ input_tag =
10
+ case dry_tag.input_type
11
+ when 'date' then text_field(method, dry_tag.input_opts.merge('data-controller': 'flatpickr'))
12
+ when 'date_time' then text_field(method, dry_tag.input_opts.merge('data-controller': 'flatpickr', 'data-flatpickr-enable-time': 'true'))
13
+ when 'integer' then number_field(method, dry_tag.input_opts)
14
+ when 'bool' then check_box(method, dry_tag.input_opts)
15
+ else
16
+ case method.to_s
17
+ when /password/ then password_field(method, dry_tag.input_opts)
18
+ when /email/ then email_field(method, dry_tag.input_opts)
19
+ when /phone/ then telephone_field(method, dry_tag.input_opts)
20
+ when /url/ then url_field(method, dry_tag.input_opts)
21
+ else text_field(method, dry_tag.input_opts)
22
+ end
23
+ end
24
+
25
+ dry_tag.wrap_tag input_tag
26
+ end
27
+
28
+ def input_select(method, collection, options = {}, html_options = {}) # rubocop:disable Gp/OptArgParameters
29
+ dry_tag = ActiveDryForm::Input.new(self, __method__, method, options)
30
+ dry_tag.wrap_tag select(method, collection, dry_tag.input_opts, html_options)
31
+ end
32
+
33
+ def input_checkbox_inline(method, options = {})
34
+ dry_tag = ActiveDryForm::Input.new(self, __method__, method, options)
35
+ dry_tag.wrap_tag check_box(method, dry_tag.input_opts), label_last: true
36
+ end
37
+
38
+ def input_text(method, options = {})
39
+ dry_tag = ActiveDryForm::Input.new(self, __method__, method, options)
40
+ dry_tag.wrap_tag text_area(method, dry_tag.input_opts)
41
+ end
42
+
43
+ def input_file(method, options = {})
44
+ dry_tag = ActiveDryForm::Input.new(self, __method__, method, options)
45
+ dry_tag.wrap_tag file_field(method, dry_tag.input_opts)
46
+ end
47
+
48
+ def input_hidden(method, options = {})
49
+ hidden_field(method, options)
50
+ end
51
+
52
+ def show_base_errors
53
+ return unless @object.base_errors
54
+
55
+ tag.div class: 'callout alert' do
56
+ tag.ul do
57
+ # внутри ошибки может быть html
58
+ @object.base_errors.map { tag.li _1.html_safe }.join.html_safe
59
+ end
60
+ end
61
+ end
62
+
63
+ def show_error(method)
64
+ ActiveDryForm::Input.new(self, __method__, method, {}).error_text
65
+ end
66
+
67
+ def button(value = nil, options = {}, &block) # rubocop:disable Gp/OptArgParameters
68
+ options[:class] = [options[:class], 'button'].compact
69
+ super(value, options, &block)
70
+ end
71
+
72
+ def fields_for(record_name, &block)
73
+ super(@object.send(record_name), &block)
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveDryForm
4
+ class Form
5
+
6
+ include Dry::Monads[:result]
7
+ Dry::Schema.load_extensions(:info)
8
+ ResultError = Class.new(StandardError)
9
+
10
+ class ContractBase < Dry::Validation::Contract
11
+
12
+ config.messages.load_paths << 'config/locales/dry_validation.ru.yml'
13
+ config.messages.default_locale = :ru
14
+
15
+ end
16
+
17
+ def self.fields(namespace, &block)
18
+ const_set :NAMESPACE, ActiveModel::Name.new(nil, nil, namespace.to_s)
19
+ const_set :CURRENT_CONTRACT, Class.new(ContractBase, &block).new
20
+ const_set :FIELDS_INFO, self::CURRENT_CONTRACT.schema.info[:keys]
21
+
22
+ self::FIELDS_INFO.each do |key, value|
23
+ if value[:keys]
24
+ sub_klass = Class.new do
25
+ const_set :NAMESPACE, ActiveModel::Name.new(nil, nil, key.to_s)
26
+ const_set :FIELDS_INFO, value[:keys]
27
+
28
+ def initialize(params, record, errors)
29
+ @params = params || {}
30
+ @record = record
31
+ @errors = errors || {}
32
+ end
33
+
34
+ self::FIELDS_INFO.each_key do |sub_key|
35
+ define_method sub_key do
36
+ @params[sub_key] || @record.try(sub_key)
37
+ end
38
+
39
+ def model_name
40
+ self.class::NAMESPACE
41
+ end
42
+
43
+ def info(sub_key)
44
+ self.class::FIELDS_INFO[sub_key]
45
+ end
46
+
47
+ # ActionView::Helpers::Tags::Translator#human_attribute_name
48
+ def to_model
49
+ self
50
+ end
51
+
52
+ def self.human_attribute_name(field)
53
+ I18n.t(field, scope: :"activerecord.attributes.#{self::NAMESPACE.i18n_key}")
54
+ end
55
+
56
+ def errors
57
+ @errors
58
+ end
59
+ end
60
+ end
61
+ define_method key do
62
+ sub_klass.new(@params[key], @record.try(key), errors[key])
63
+ end
64
+ else
65
+ define_method key do
66
+ @params[key] || @record.try(key)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def self.default(method)
73
+ alias_method :"__#{method}", method
74
+
75
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
76
+ # def create_default(...)
77
+ # @params.merge!(__create_default(...))
78
+ # end
79
+
80
+ def #{method}(...)
81
+ @params.merge!(__#{method}(...))
82
+ end
83
+ RUBY
84
+ end
85
+
86
+ def self.action(method)
87
+ alias_method :"__#{method}", method
88
+
89
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
90
+ # def create(...)
91
+ # if validator.failure?
92
+ # @base_errors = validator.errors.filter(:base?).map(&:to_s).presence
93
+ # return Failure(:validate_invalid)
94
+ # end
95
+ #
96
+ # result = __create(...)
97
+ #
98
+ # unless result.is_a?(::Dry::Monads::Result)
99
+ # raise ResultError, 'method `create` should be returning `monad`'
100
+ # end
101
+ #
102
+ # case result
103
+ # in Failure[:failure_service, base_errors]
104
+ # @base_errors = base_errors
105
+ # else
106
+ # end
107
+ #
108
+ # result
109
+ # end
110
+
111
+ def #{method}(...)
112
+ if validator.failure?
113
+ @base_errors = validator.errors.filter(:base?).map(&:to_s).presence
114
+ return Failure(:validate_invalid)
115
+ end
116
+
117
+ result = __#{method}(...)
118
+
119
+ unless result.is_a?(::Dry::Monads::Result)
120
+ raise ResultError, 'method `#{method}` should be returning `monad`'
121
+ end
122
+
123
+ case result
124
+ in Failure[:failure_service, base_errors]
125
+ @base_errors = base_errors
126
+ else
127
+ end
128
+
129
+ result
130
+ end
131
+ RUBY
132
+ end
133
+
134
+ attr_reader :base_errors, :record
135
+
136
+ def initialize(record: nil, params_form: nil, params_init: nil)
137
+ raise 'in `params_form` use `request.parameters` instead of `params`' if params_form.is_a?(::ActionController::Parameters)
138
+ raise 'in `params_init` use `request.parameters` instead of `params`' if params_init.is_a?(::ActionController::Parameters)
139
+
140
+ @params =
141
+ if params_form
142
+ _deep_transform_values_in_params!(params_form[self.class::NAMESPACE.param_key].deep_transform_keys!(&:to_sym))
143
+ elsif params_init
144
+ params_init.deep_transform_keys!(&:to_sym)
145
+ else
146
+ {}
147
+ end
148
+
149
+ @record = record if record
150
+ end
151
+
152
+ def info(key)
153
+ self.class::FIELDS_INFO[key]
154
+ end
155
+
156
+ def model_name
157
+ self.class::NAMESPACE
158
+ end
159
+
160
+ # ActionView::Helpers::Tags::Translator#human_attribute_name
161
+ def to_model
162
+ self
163
+ end
164
+
165
+ def self.human_attribute_name(field)
166
+ I18n.t(field, scope: :"activerecord.attributes.#{self::NAMESPACE.i18n_key}")
167
+ end
168
+
169
+ def persisted?
170
+ @record&.persisted?
171
+ end
172
+
173
+ def to_key
174
+ key = @record&.id
175
+ [key] if key
176
+ end
177
+
178
+ # используется при генерации URL, когда record.persisted?
179
+ def to_param
180
+ @record.id.to_s
181
+ end
182
+
183
+ def validator
184
+ @validator ||= self.class::CURRENT_CONTRACT.call(@params, { form: self })
185
+ end
186
+
187
+ def errors
188
+ @errors ||= @validator ? @validator.errors.to_h : {}
189
+ end
190
+
191
+ def view_component
192
+ self.class.module_parent::Component.new(self)
193
+ end
194
+
195
+ private def _deep_transform_values_in_params!(object)
196
+ case object
197
+ when String
198
+ object.strip.presence
199
+ when Hash
200
+ object.transform_values! { |value| _deep_transform_values_in_params!(value) }
201
+ when Array
202
+ object.map! { |e| _deep_transform_values_in_params!(e) }
203
+ object.compact!
204
+ object
205
+ else
206
+ object
207
+ end
208
+ end
209
+
210
+ end
211
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveDryForm
4
+ module FormHelper
5
+
6
+ def active_dry_form_for(name, options = {})
7
+ options[:builder] = ActiveDryForm::Builder
8
+ options[:html] ||= {}
9
+ options[:html][:class] = "active-dry-form #{options[:html][:class]}"
10
+
11
+ form_for(name, options) do |f|
12
+ concat f.show_base_errors
13
+ yield(f)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ ActiveSupport.on_load(:action_view) do
21
+ include ActiveDryForm::FormHelper
22
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveDryForm
4
+ class Input
5
+
6
+ attr_reader :input_opts, :input_type
7
+
8
+ def initialize(builder, method_type, method, options)
9
+ @builder = builder
10
+ @method_type = method_type
11
+ @method = method
12
+
13
+ info = builder.object.info(method)
14
+ @input_type = info[:type]
15
+ @required = info[:required]
16
+
17
+ @label_opts = options[:label]
18
+ @label_text = options[:label_text]
19
+ @hint_text = options[:hint]
20
+ @input_opts = options.except(:label, :hint, :label_text)
21
+ @input_opts[:required] = true if @required
22
+ end
23
+
24
+ def css_classes
25
+ [
26
+ 'input',
27
+ @method_type,
28
+ @input_type,
29
+ @method,
30
+ ('required' if @required),
31
+ ('error' if error?(@method)),
32
+ ].compact
33
+ end
34
+
35
+ def wrap_tag(input, label_last: nil)
36
+ @builder.tag.div class: css_classes do
37
+ [
38
+ label_last ? input : label,
39
+ label_last ? label : input,
40
+ hint_text,
41
+ error_text,
42
+ ].compact.join.html_safe
43
+ end
44
+ end
45
+
46
+ def label
47
+ @builder.label(@method, @label_text) unless @label_opts == false
48
+ end
49
+
50
+ def hint_text
51
+ return unless @hint_text
52
+
53
+ @builder.tag.small @hint_text, class: 'help-text'
54
+ end
55
+
56
+ def error_text
57
+ return unless error?(@method)
58
+
59
+ obj_error_text =
60
+ case e = @builder.object.errors[@method]
61
+ when Hash then e.values
62
+ else e
63
+ end
64
+
65
+ @builder.tag.div obj_error_text.join('<br />'), class: 'form-error is-visible'
66
+ end
67
+
68
+ def error?(method)
69
+ @builder.object.errors.key?(method)
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ require "dry/schema/extensions/info/schema_compiler"
2
+
3
+ require "dry/schema/constants"
4
+
5
+ module Dry
6
+ module Schema
7
+ module Info
8
+ class SchemaCompiler
9
+ def visit_not(_node, opts = {})
10
+ key = opts[:key]
11
+ keys[key][:nullable] = true
12
+ end
13
+
14
+ def visit_predicate(node, opts = {})
15
+ name, rest = node
16
+
17
+ key = opts[:key]
18
+
19
+ if name.equal?(:key?)
20
+ keys[rest[0][1]] = { required: opts.fetch(:required, true) }
21
+ elsif name.equal?(:array?)
22
+ keys[key][:array] = true
23
+ else
24
+ type = PREDICATE_TO_TYPE[name]
25
+ keys[key][:type] = type if type
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveDryForm
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "active_dry_form/version"
4
+ require_relative "active_dry_form/schema_compiler_patch"
5
+ require_relative "active_dry_form/builder"
6
+ require_relative "active_dry_form/form"
7
+ require_relative "active_dry_form/input"
8
+ require_relative "active_dry_form/form_helper"
9
+
10
+ module ActiveDryForm
11
+ class Error < StandardError; end
12
+ # Your code goes here...
13
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_dry_form
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ermolaev Andrey
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-validation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-monads
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Form validation for Rails with Dry-Validation
42
+ email:
43
+ - andruhafirst@yandex.ru
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".editorconfig"
49
+ - ".rspec"
50
+ - ".rubocop.yml"
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - bin/console
56
+ - bin/setup
57
+ - lib/active_dry_form.rb
58
+ - lib/active_dry_form/builder.rb
59
+ - lib/active_dry_form/form.rb
60
+ - lib/active_dry_form/form_helper.rb
61
+ - lib/active_dry_form/input.rb
62
+ - lib/active_dry_form/schema_compiler_patch.rb
63
+ - lib/active_dry_form/version.rb
64
+ homepage: https://github.com/corp-gp/active_dry_form
65
+ licenses:
66
+ - MIT
67
+ metadata:
68
+ allowed_push_host: https://rubygems.org
69
+ homepage_uri: https://github.com/corp-gp/active_dry_form
70
+ source_code_uri: https://github.com/corp-gp/active_dry_form
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 2.6.0
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubygems_version: 3.1.6
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Form validation for Rails with Dry-Validation
90
+ test_files: []