readymade 0.1.4 → 0.1.7

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: a730db0a4c2ccfefcb7c48a607ba75e691b8133ac8f6f7cdc6a67554bed180c3
4
- data.tar.gz: 84567afe201fd84977f5a56a46b68093c4bc7d814f4395d2cbb87325f268fa17
3
+ metadata.gz: 9d8b08e5bcf92035a49dbec37847653f0b99743c2f508c96e50b0aba312b3905
4
+ data.tar.gz: 2ec517133167b7ec909d9bf2e77c0fdb185774017f70f8871586ec0eb942925e
5
5
  SHA512:
6
- metadata.gz: 53c0991bce0adcfc2c9e2cbe8498936c90bcd01b197d3483027e04b594bb2ea9fbaf4b69cf7306312b4b2cb3784613f5facbde0d6ee46a6fba6b07fcab7b1814
7
- data.tar.gz: 6d280789685dc5bead01c40191668c58e56124108d9c2d5c81a9537b0ee147e4d2718d03460b2e4b512ee4b43ca98947781eff1f006746fb1108bac352294fb0
6
+ metadata.gz: c2388b93ffa8fd5bb5fe988546dab3383a70ec36296ca0efe6a34475c2e06514a43ff0b12c61986aaf50d6a03c529076c92d1b802647a310a12b44b966dc27d4
7
+ data.tar.gz: ec9b42d63e9ba52d2a27fa10ac2ccf572de6c287b14a1f0e9269c7554adb0f10deee2ad0a6fb62a05c5ac7db2854e96abb6ca554774b632bfaa4f037057822b0
data/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [0.1.6] - 2021-12-20
5
+
6
+ ### Features
7
+
8
+ * Add Readymade::Controller::Serialization
9
+
10
+ ## [0.1.5] - 2021-12-07
11
+
12
+ ### Improvements
13
+
14
+ * Better errors with Rails 6.1.
15
+
4
16
  ## [0.1.4] - 2021-11-22
5
17
 
6
18
  ### Features
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- readymade (0.1.4)
4
+ readymade (0.1.7)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Readymade
1
+ # Readymade 0.1.7
2
2
 
3
3
  This gems contains basic components to follow [ABDI architecture](https://github.com/OrestF/OrestF/blob/master/abdi/ABDI_architecture.md)
4
4
 
@@ -35,6 +35,41 @@ Inherit your components from:
35
35
 
36
36
  ```TODO: add```
37
37
 
38
+ #### #form_options
39
+
40
+ ```ruby
41
+ # app/forms/my_form.rb
42
+
43
+ class MyForm < Readymade::Form
44
+ PERMITTED_ATTRIBUTES = %i[email name category country]
45
+ REQUIRED_ATTRIBUTES = %i[email]
46
+
47
+ def form_options
48
+ {
49
+ categories: args[:company].categories,
50
+ countries: Country.all
51
+ }
52
+ end
53
+ end
54
+ ```
55
+
56
+ ```ruby
57
+ # app/controllers/items_controller.rb
58
+
59
+ def new
60
+ @form_options = MyForm.form_options(company: current_company)
61
+ end
62
+ ```
63
+
64
+ ```slim
65
+ / app/views/items/new.html.slim
66
+
67
+ = f.select :category, collection: @form_options[:categories]
68
+ = f.select :country, collection: @form_options[:countries]
69
+ = f.text_field :email, required: @form_options.required?(:email) # true
70
+ = f.text_field :name, required: @form_options.required?(:name) # false
71
+ ```
72
+
38
73
  ### Readymade::InstantForm
39
74
 
40
75
  Permit params and validates presence inline
@@ -60,7 +95,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
60
95
 
61
96
  ## Contributing
62
97
 
63
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/readymade. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/readymade/blob/master/CODE_OF_CONDUCT.md).
98
+ Bug reports and pull requests are welcome on GitHub at https://github.com/OrestF/readymade. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/readymade/blob/master/CODE_OF_CONDUCT.md).
64
99
 
65
100
 
66
101
  ## License
@@ -0,0 +1,70 @@
1
+ require 'active_support/concern'
2
+
3
+ module Readymade
4
+ module Controller
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+
8
+ def record_response(record, *args)
9
+ options = args.extract_options!
10
+ return render_json({}, :not_found) if record.nil?
11
+
12
+ hash = serialize_record(record, options)
13
+
14
+ status = if record.errors.none?
15
+ options[:status] || :ok
16
+ else
17
+ hash[:errors] = record.errors.messages
18
+ :bad_request
19
+ end
20
+ render_json(hash, status)
21
+ end
22
+
23
+ def collection_response(collection, *args)
24
+ options = args.extract_options!
25
+
26
+ render_json(
27
+ {
28
+ items: serialize_collection(paginate(collection, options), options),
29
+ count: collection.count
30
+ },
31
+ options[:status] || :ok
32
+ )
33
+ end
34
+
35
+ def render_json(message, status)
36
+ render json: message, status: status
37
+ end
38
+
39
+ def serialize_collection(collection, options = {})
40
+ serializer = options.delete(:serializer) || "#{collection.klass.name}Serializer".constantize
41
+ serializer.render_as_hash(collection, options)
42
+ end
43
+
44
+ def serialize_record(record, options = {})
45
+ serializer = options.delete(:serializer) || "#{record.class.name}Serializer".constantize
46
+ serializer.render_as_hash(record, { root: :data }.merge!(options))
47
+ end
48
+
49
+ def operation_response(response_obj, options = {})
50
+ message, status = case response_obj.status.to_sym
51
+ when :success, :ok
52
+ [
53
+ if response_obj.data[:record].present?
54
+ serialize_record(response_obj.data[:record],
55
+ options)
56
+ else
57
+ {}
58
+ end,
59
+ :ok
60
+ ]
61
+ when :validation_fail, :bad_request
62
+ [{ errors: response_obj.data[:errors] }, :bad_request]
63
+ else
64
+ [response_obj.status.to_sym, {}]
65
+ end
66
+ render_json(message, status)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -31,6 +31,7 @@ module Readymade
31
31
  attr_accessor key
32
32
  end
33
33
  end
34
+
34
35
  # automatically validates all REQUIRED_ATTRIBUTES
35
36
  singleton_class.validates(*required_attributes, presence: true) if required_attributes.present?
36
37
 
@@ -52,8 +53,13 @@ module Readymade
52
53
  next if params[attr].blank?
53
54
 
54
55
  if form_class.is_a?(Array)
55
- n_forms = params[attr].map { |_i, attrs| form_class[0].new(attrs) }
56
-
56
+ n_forms = if params[attr].is_a?(Hash)
57
+ # { 0 => {id: 1, name: 'my name'}, 1 => { id: 2, name: 'other name' }}
58
+ params[attr].map { |_i, attrs| form_class[0].new(attrs) }
59
+ else
60
+ # [{id: 1, name: 'my name'}, { id: 2, name: 'other name' }]
61
+ params[attr].map { |attrs| form_class[0].new(attrs) }
62
+ end
57
63
  @nested_forms.push(*n_forms)
58
64
  define_singleton_method("#{attr}_forms") { n_forms }
59
65
  else
@@ -71,6 +77,7 @@ module Readymade
71
77
  nested_forms.compact.map(&:validate).all? || sync_nested_errors(nested_forms)
72
78
  end
73
79
 
80
+ # copy errors from nested forms into parent form
74
81
  def sync_nested_errors(nested_forms)
75
82
  nested_forms.each do |n_form|
76
83
  n_form.errors.each do |code, text|
@@ -81,14 +88,23 @@ module Readymade
81
88
  false
82
89
  end
83
90
 
91
+ # sync errors from form to record or vice-versa
84
92
  def sync_errors(from: self, to: record)
85
93
  return if [from, to].any?(&:blank?)
86
94
 
87
- errors = from.errors.instance_variable_get('@messages').to_h
88
- errors.merge!(to.errors.instance_variable_get('@messages').to_h)
95
+ if Rails.version.to_f > 6.0
96
+ from.errors.messages.each do |key, values|
97
+ Array.wrap(values).uniq.each do |uv|
98
+ to.errors.add(key, uv)
99
+ end
100
+ end
101
+ else
102
+ errors = from.errors.instance_variable_get('@messages').to_h
103
+ errors.merge!(to.errors.instance_variable_get('@messages').to_h)
89
104
 
90
- to.errors.instance_variable_set('@messages', errors)
91
- to.errors.messages.transform_values!(&:uniq) # Does not work with rails 6.1
105
+ to.errors.instance_variable_set('@messages', errors)
106
+ to.errors.messages.transform_values!(&:uniq) # Does not work with rails 6.1
107
+ end
92
108
  rescue FrozenError => _e
93
109
  end
94
110
 
@@ -128,5 +144,53 @@ module Readymade
128
144
  def nested_forms_mapping
129
145
  {}
130
146
  end
147
+
148
+ # EXAMPLE
149
+ # class Items::Forms::Create::Value < ::Readymade::Form
150
+ # PERMITTED_ATTRIBUTES = %i[attr1 attr2].freeze
151
+ # REQUIRED_ATTRIBUTES = %i[attr1].freeze
152
+ #
153
+ # class Value < ::Readymade::Form::Value
154
+ # def to_h
155
+ # {
156
+ # vat_percent: Item::VAT_OPTIONS,
157
+ # price_type: Item.price_types.keys,
158
+ # item_category: args[:company].item_categories
159
+ # }
160
+ # end
161
+ # end
162
+
163
+ # @form_options = Items::Forms::Create::Value.new(company: current_company)
164
+ # f.association :item_category, collection: @form_options[:item_category]
165
+
166
+ def self.form_options(**args)
167
+ Readymade::Form::FormOptions.new(**args.merge!(form_class: self))
168
+ end
169
+ class FormOptions
170
+ attr_reader :args
171
+
172
+ def initialize(**args)
173
+ @args = args
174
+ @f_class = args.delete(:form_class)
175
+ end
176
+
177
+ def [](key)
178
+ to_h[key]
179
+ end
180
+
181
+ def to_h
182
+ raise Readymade::Error.new('define form_options on your form') unless (f = @f_class.new({}, @args)).respond_to?(:form_options)
183
+
184
+ f.form_options
185
+ end
186
+
187
+ def as_json(options = {})
188
+ to_h.as_json(options)
189
+ end
190
+
191
+ def required?(attr)
192
+ @f_class::REQUIRED_ATTRIBUTES.include?(attr.to_sym)
193
+ end
194
+ end
131
195
  end
132
196
  end
@@ -2,6 +2,5 @@
2
2
 
3
3
  module Readymade
4
4
  class InstantForm < Form
5
-
6
5
  end
7
6
  end
@@ -26,7 +26,7 @@ module Readymade
26
26
  def record_valid?
27
27
  return true if record.errors.none? && record.valid?
28
28
 
29
- sync_errors_to_form && false
29
+ false
30
30
  end
31
31
 
32
32
  def save_record
@@ -43,6 +43,7 @@ module Readymade
43
43
 
44
44
  def validation_fail(status = :validation_fail, args = {})
45
45
  sync_errors_to_form
46
+
46
47
  response(status, args.merge!(record: record,
47
48
  record_params: record_params,
48
49
  form: form,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Readymade
4
- VERSION = '0.1.4'
4
+ VERSION = '0.1.7'
5
5
  end
data/lib/readymade.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'readymade/controller/serialization'
3
4
  require 'readymade/action'
4
5
  require 'readymade/form'
5
6
  require 'readymade/instant_form'
data/readymade.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
26
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
27
  end
28
+ spec.files << 'lib/readymade/controller/serialization.rb'
28
29
  spec.bindir = 'exe'
29
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
31
  spec.require_paths = ['lib']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: readymade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - OrestF
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-22 00:00:00.000000000 Z
11
+ date: 2022-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -73,6 +73,7 @@ files:
73
73
  - bin/setup
74
74
  - lib/readymade.rb
75
75
  - lib/readymade/action.rb
76
+ - lib/readymade/controller/serialization.rb
76
77
  - lib/readymade/form.rb
77
78
  - lib/readymade/instant_form.rb
78
79
  - lib/readymade/operation.rb