golden-objects 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +101 -0
  7. data/Rakefile +2 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/golden-objects.gemspec +32 -0
  11. data/lib/golden/active_model_concerns/datepicker_concern.rb +42 -0
  12. data/lib/golden/active_model_concerns/has_persisted_record_concern.rb +54 -0
  13. data/lib/golden/active_model_concerns/yaml_coder_concern.rb +44 -0
  14. data/lib/golden/active_model_concerns.rb +10 -0
  15. data/lib/golden/active_record_concerns/deleted_at_concern.rb +24 -0
  16. data/lib/golden/active_record_concerns/disabled_at_concern.rb +28 -0
  17. data/lib/golden/active_record_concerns/enum_value_concern.rb +27 -0
  18. data/lib/golden/active_record_concerns/rejected_at_concern.rb +24 -0
  19. data/lib/golden/active_record_concerns.rb +11 -0
  20. data/lib/golden/attribute_accessors/boolean_accessor.rb +34 -0
  21. data/lib/golden/attribute_accessors/date_time_accessor.rb +52 -0
  22. data/lib/golden/attribute_accessors/enum_accessor.rb +33 -0
  23. data/lib/golden/attribute_accessors/extended_attr_accessor.rb +53 -0
  24. data/lib/golden/attribute_accessors.rb +18 -0
  25. data/lib/golden/objects/application/application_calculator.rb +29 -0
  26. data/lib/golden/objects/application/application_context.rb +34 -0
  27. data/lib/golden/objects/application/application_form.rb +41 -0
  28. data/lib/golden/objects/application/application_generator.rb +18 -0
  29. data/lib/golden/objects/application/application_operator.rb +20 -0
  30. data/lib/golden/objects/application/application_presenter.rb +14 -0
  31. data/lib/golden/objects/application/application_service.rb +14 -0
  32. data/lib/golden/objects/application/application_transformer.rb +22 -0
  33. data/lib/golden/objects/form/action_form_operator.rb +84 -0
  34. data/lib/golden/objects/form/active_record_form.rb +35 -0
  35. data/lib/golden/objects/form/single_form_presenter.rb +40 -0
  36. data/lib/golden/objects/query/query_context.rb +114 -0
  37. data/lib/golden/objects/query/query_form.rb +59 -0
  38. data/lib/golden/objects/query/query_form_operator.rb +66 -0
  39. data/lib/golden/objects/query/query_result_presenter.rb +30 -0
  40. data/lib/golden/objects/version.rb +5 -0
  41. data/lib/golden/objects.rb +34 -0
  42. metadata +129 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4854525e660454fcdcd717c48e05addb718eb6571e7ee2203068587968bc570d
4
+ data.tar.gz: b12b5850a5da5b637fe56005acf318a22085347dd9a22483dfc52bd24204978e
5
+ SHA512:
6
+ metadata.gz: ab570549fa989c52516de15a1c87a2439f3aba9928ef5563f9c9e02e82daa270c88f8c55cbd9936eaff22c23a6147a731d3bb3299f56b96fc17cbf50fb50324d
7
+ data.tar.gz: d149d32f6989842cb7407323d56a160651cb820f030f5b9ce5b649bfefa6ca7202ed5892e18e23745b3a9bb7c9fc9c6d6826e0088904ce0bf877bdc57bee693c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Change Logs
2
+
3
+ ### v0.1.0
4
+
5
+ * Initial release.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in golden-objects.gemspec
4
+ gemspec
5
+
6
+ gem 'rake', '~> 12.0'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Tse-Ching Ho
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,101 @@
1
+ # Golden::Objects
2
+
3
+ Provide ruby classes and modules help you build business logics as ruby components.
4
+
5
+ Let's compose business logics as ruby components to improve efficiency of maintenance.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'golden-objects'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install golden-objects
22
+
23
+ ## Usage
24
+
25
+ Create your class and inherite appropriate golden object directly or thought another class.
26
+
27
+ ```
28
+ class ApplicationPresenter < Golden::ApplicationPresenter
29
+ include Rails.application.routes.url_helpers
30
+ end
31
+
32
+ class CartPresenter < ApplicationPresenter
33
+ attr_accessor :product_variants
34
+
35
+ def line_items_presenter
36
+ @line_items_presenter ||= CartLineItemPresenter.all(product_variants: product_variants)
37
+ end
38
+ end
39
+ ```
40
+
41
+ And put into suggested pathes.
42
+
43
+ * business logic classes => app/components
44
+ * data model logic classes => app/objects
45
+
46
+ Grouping classes by multi layers of folders are also suggested.
47
+
48
+ ```
49
+ app/components/[major business logic]/[secondary business logic]/xxx_xxx.rb
50
+ app/objects/[major data model logic]/[secondary data model logic]/xxx_xxx.rb
51
+ ```
52
+
53
+ For example:
54
+
55
+ ```
56
+ app/components/identity/profile/update_form.rb
57
+ app/components/frontend/popup_cart/main_presenter.rb
58
+ app/objects/orders/line_item/create_operator.rb
59
+ app/objects/orders/create_operator.rb
60
+ ```
61
+
62
+ ## Modules
63
+
64
+ 3 kinds of modules are provied.
65
+
66
+ * attribute accessors
67
+ * active model concerns
68
+ * active record concerns
69
+
70
+ ## Objects
71
+
72
+ 3 groups of objects are provied.
73
+
74
+ * Application objects
75
+ * Golden::ApplicationCalculator
76
+ * Golden::ApplicationContext
77
+ * Golden::ApplicationForm
78
+ * Golden::ApplicationGenerator
79
+ * Golden::ApplicationOperator
80
+ * Golden::ApplicationPresenter
81
+ * Golden::ApplicationService
82
+ * Golden::ApplicationTransformer
83
+ * Form objects
84
+ * Golden::ActionFormOperator
85
+ * Golden::ActiveRecordForm
86
+ * Golden::SingleFormPresenter
87
+ * Query objects
88
+ * Golden::QueryContext
89
+ * Golden::QueryForm
90
+ * Golden::QueryFormOperator
91
+ * Golden::QueryResultPresenter
92
+
93
+ ## Development
94
+
95
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
96
+
97
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
98
+
99
+ ## Contributing
100
+
101
+ Bug reports and pull requests are welcome on GitHub at https://github.com/goldenio/golden-objects.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'golden/objects'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ 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,32 @@
1
+ require_relative 'lib/golden/objects/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'golden-objects'
5
+ spec.version = Golden::Objects::VERSION
6
+ spec.authors = ['Tse-Ching Ho']
7
+ spec.email = ['tsechingho@gmail.com']
8
+
9
+ spec.summary = %q{Provide ruby classes and modules help you build business logics as ruby components.}
10
+ spec.description = %q{Let's compose business logics as ruby components to improve efficiency of maintenance.}
11
+ spec.homepage = 'https://github.com/goldenio/golden-objects'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+
15
+ # spec.metadata['allowed_push_host'] = 'https://github.com'
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/goldenio/golden-objects.git'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/goldenio/golden-objects/CHANGELOG.md'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'activesupport', '>= 5.2'
30
+ spec.add_dependency 'activemodel', '>= 5.2'
31
+ spec.add_dependency 'activerecord', '>= 5.2'
32
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module DatepickerConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def permit_datepicker_of(fields, attrs)
9
+ fields.each do |field|
10
+ attrs.delete(field)
11
+ attrs.push(field => [])
12
+ end
13
+ end
14
+ end
15
+
16
+ def filter_dates_of_datepicker(fields, attrs)
17
+ fields.each do |field|
18
+ dates = attrs.delete(field)
19
+ attrs[field] = date_of_datepicker(dates)
20
+ end
21
+ end
22
+
23
+ def filter_duration_of_datepicker(field, attrs)
24
+ return [] unless attrs.key? field
25
+
26
+ dates = attrs.delete(field)
27
+ duration_of_datepicker(dates)
28
+ end
29
+
30
+ private
31
+
32
+ def date_of_datepicker(values)
33
+ values.is_a?(Array) ? (values.first || '') : values
34
+ end
35
+
36
+ def duration_of_datepicker(values)
37
+ first_date = values.is_a?(Array) ? (values.first || '') : values
38
+ last_date = values.is_a?(Array) && values.size > 1 ? (values.last || '') : ''
39
+ [first_date, last_date]
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module HasPersistedRecordConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ # Outcome:
9
+ # * xxx_id
10
+ # * xxx_id=
11
+ # * xxx
12
+ # * xxx=
13
+ # * load_persisted_attributes_of_xxx_by
14
+ # * load_persisted_attributes_of_xxx
15
+ #
16
+ # Add validation if needed
17
+ # validates :xxx, presence: true
18
+ #
19
+ # rubocop:disable Naming/PredicateName
20
+ def has_persisted_record(name, class_name: nil)
21
+ class_name ||= name.to_s.classify
22
+ klass = class_name.constantize
23
+ attr_accessor "#{name}_id"
24
+ attr_writer name
25
+
26
+ define_method name do
27
+ if instance_variable_get(:"@#{name}").blank?
28
+ object = klass.find_by id: instance_variable_get(:"@#{name}_id")
29
+ instance_variable_set(:"@#{name}", object) unless object.nil?
30
+ end
31
+ instance_variable_get(:"@#{name}")
32
+ end
33
+
34
+ define_method :"load_persisted_attributes_of_#{name}_by" do |id:|
35
+ instance_variable_set(:"@#{name}_id", id)
36
+ object = send(name)
37
+ send(:"load_persisted_attributes_of_#{name}", object)
38
+ end
39
+
40
+ define_method :"load_persisted_attributes_of_#{name}" do |object|
41
+ return {} if object.nil?
42
+
43
+ instance_variable_set(:"@#{name}", object) if instance_variable_get(:"@#{name}").blank?
44
+
45
+ attrs = object.attributes.symbolize_keys
46
+ return attrs unless self.class.respond_to? :permits
47
+
48
+ attrs.slice(*self.class.permits)
49
+ end
50
+ end
51
+ # rubocop:enable Naming/PredicateName
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Golden
6
+ module YamlCoderConcern
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def load(yaml)
11
+ obj = YAML.safe_load(yaml, [Symbol]) if yaml.is_a?(String) && /^---/.match?(yaml)
12
+ obj.is_a?(Hash) ? new(obj || {}) : obj
13
+ end
14
+
15
+ def dump(obj)
16
+ return if obj.nil?
17
+
18
+ if obj.respond_to? :to_h
19
+ YAML.dump obj.to_h
20
+ else
21
+ assert_valid_value(obj, action: 'dump')
22
+ YAML.dump obj
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def assert_valid_value(obj, action:)
29
+ return if obj.nil? || obj.is_a?(self)
30
+
31
+ error = %(
32
+ can't #{action}: was supposed to be a #{name},
33
+ but was a #{obj.class.name}. -- #{obj.inspect}
34
+ ).gsub(/\s+/, ' ').strip
35
+
36
+ if ::Object.const_defined? 'ActiveRecord'
37
+ raise ::ActiveRecord::SerializationTypeMismatch, error
38
+ else
39
+ raise ::Golden::Objects::Error, error
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'golden/active_model_concerns/has_persisted_record_concern'
4
+ require 'golden/active_model_concerns/yaml_coder_concern'
5
+ require 'golden/active_model_concerns/datepicker_concern'
6
+
7
+ module Golden
8
+ module ActiveModelConcerns
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module DeletedAtConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :undeleted, -> { where(deleted_at: nil) }
9
+ scope :deleted, -> { where.not(deleted_at: nil) }
10
+ end
11
+
12
+ def soft_destroy!
13
+ update deleted_at: Time.zone.now
14
+ end
15
+
16
+ def deleted?
17
+ deleted_at?
18
+ end
19
+
20
+ def undeleted?
21
+ !deleted?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module DisabledAtConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :enabled, -> { where(disabled_at: nil) }
9
+ scope :disabled, -> { where.not(disabled_at: nil) }
10
+ end
11
+
12
+ def disable!
13
+ update disabled_at: Time.zone.now
14
+ end
15
+
16
+ def enable!
17
+ update disabled_at: nil
18
+ end
19
+
20
+ def disabled?
21
+ disabled_at?
22
+ end
23
+
24
+ def enabled?
25
+ !disabled?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module EnumValueConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def human_attribute_enum_value(attribute_name, value)
9
+ return if value.blank?
10
+
11
+ human_attribute_name("#{attribute_name}.#{value}")
12
+ end
13
+
14
+ def enum_options_for_select(attribute_name)
15
+ enums = send(attribute_name.to_s.pluralize)
16
+ enums.values.uniq.map do |value|
17
+ key = enums.key(value)
18
+ [human_attribute_enum_value(attribute_name, key), key]
19
+ end.to_h
20
+ end
21
+ end
22
+
23
+ def human_attribute_enum(attribute_name)
24
+ self.class.human_attribute_enum_value(attribute_name, send(attribute_name))
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module RejectedAtConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :not_rejected, -> { where(rejected_at: nil) }
9
+ scope :rejected, -> { where.not(rejected_at: nil) }
10
+ end
11
+
12
+ def cancel_reject!
13
+ update rejected_at: nil, rejected_by: nil
14
+ end
15
+
16
+ def rejected?
17
+ rejected_at?
18
+ end
19
+
20
+ def not_rejected?
21
+ !rejected?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'golden/active_record_concerns/enum_value_concern'
4
+ require 'golden/active_record_concerns/deleted_at_concern'
5
+ require 'golden/active_record_concerns/disabled_at_concern'
6
+ require 'golden/active_record_concerns/rejected_at_concern'
7
+
8
+ module Golden
9
+ module ActiveRecordConcerns
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module BooleanAccessor
5
+ extend ActiveSupport::Concern
6
+
7
+ TRUE_VALUES = [
8
+ true, 1, '1',
9
+ 'true', 'TRUE', 'True', 't', 'T',
10
+ 'yes', 'YES', 'Yes', 'y', 'Y'
11
+ ].freeze
12
+
13
+ class_methods do
14
+ def boolean_accessor(*attributes, allow_nil: false)
15
+ [*attributes].uniq.each do |attribute_name|
16
+ attr_accessor attribute_name
17
+
18
+ class_eval do
19
+ define_method("#{attribute_name}?") do
20
+ instance_variable_get("@#{attribute_name}")
21
+ end
22
+
23
+ define_method("#{attribute_name}=") do |value|
24
+ boolean = TRUE_VALUES.include?(value)
25
+ boolean = nil if allow_nil && value.is_a?(String) && value.blank?
26
+ boolean = nil if allow_nil && value.is_a?(NilClass)
27
+ instance_variable_set("@#{attribute_name}", boolean)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/time_with_zone'
4
+
5
+ module Golden
6
+ module DateTimeAccessor
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ # rubocop:disable Metrics/CyclomaticComplexity
11
+ def datetime_accessor(*attributes, date_only: false)
12
+ [*attributes].uniq.each do |attribute_name|
13
+ attr_accessor attribute_name
14
+
15
+ class_eval do
16
+ define_method("#{attribute_name}?") do
17
+ instance_variable_get("@#{attribute_name}")
18
+ end
19
+
20
+ define_method("#{attribute_name}=") do |value|
21
+ case value
22
+ when ActiveSupport::TimeWithZone
23
+ time = value
24
+ when Date, Time, DateTime
25
+ time = value
26
+ when String
27
+ time = parse_time(value)
28
+ when NilClass
29
+ time = nil
30
+ else
31
+ raise "#{value.class} is unsupported!"
32
+ end
33
+ time = time.to_date if time && date_only
34
+ instance_variable_set("@#{attribute_name}", time)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ # rubocop:enable Metrics/CyclomaticComplexity
40
+
41
+ def date_accessor(*attributes)
42
+ datetime_accessor(*attributes, date_only: true)
43
+ end
44
+ end
45
+
46
+ def parse_time(string, zone: Golden::AttributeAccessors.default_zone)
47
+ ActiveSupport::TimeZone.new(zone)&.parse(string)
48
+ rescue StandardError
49
+ nil
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module EnumAccessor
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def enum_accessor(*attributes, model:)
9
+ [*attributes].uniq.each do |attribute_name|
10
+ plural_name = attribute_name.to_s.pluralize
11
+ model = model.constantize unless model.is_a?(Class)
12
+
13
+ attr_accessor attribute_name
14
+
15
+ define_singleton_method(plural_name) do
16
+ alias_name = "#{plural_name}_alias"
17
+ values = model.send(plural_name).clone
18
+ values.merge!(model.send(alias_name)) if model.respond_to?(alias_name)
19
+ values
20
+ end
21
+
22
+ class_eval do
23
+ define_method("#{attribute_name}=") do |value|
24
+ enum_value = self.class.send(plural_name)[value]
25
+ enum_key = model.send(plural_name).key(enum_value)
26
+ instance_variable_set("@#{attribute_name}", enum_key)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Golden
4
+ module ExtendedAttrAccessor
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :attribute_accessors, instance_accessor: false, default: []
9
+ end
10
+
11
+ class_methods do
12
+ def attr_accessor(*attrs)
13
+ self.attribute_accessors += attrs.map(&:to_sym)
14
+ self.attribute_accessors.uniq!
15
+ super(*attrs)
16
+ end
17
+
18
+ def accessor_attributes
19
+ @accessor_attributes ||= attribute_accessors.dup
20
+ end
21
+ alias_method :attributes, :accessor_attributes unless respond_to? :attributes
22
+
23
+ private
24
+
25
+ def merge_attributes!(*attrs)
26
+ accessor_attributes.concat(attrs.map(&:to_sym)).uniq!
27
+ end
28
+
29
+ def delete_attributes!(*attrs)
30
+ attrs.each { |attr| accessor_attributes.delete(attr) }
31
+ end
32
+ end
33
+
34
+ # NOTE: conflict with ActiveModel::Attributes#attributes
35
+ def attributes
36
+ self.class.attributes.inject({}) do |hash, attribute|
37
+ hash.merge(attribute => public_send(attribute))
38
+ end.with_indifferent_access
39
+ end
40
+
41
+ def strip_attributes(attrs)
42
+ attrs.transform_values do |value|
43
+ value.respond_to?(:strip) ? value.strip : value
44
+ end
45
+ end
46
+
47
+ def strip_attributes!(attrs)
48
+ attrs.transform_values do |value|
49
+ value.respond_to?(:strip!) ? value.strip! : value
50
+ end
51
+ end
52
+ end
53
+ end