has_embedded_document 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f6ef129f786a3baadf4af323f020e1383c5e32f98d71ad0e652b9f76dce86193
4
+ data.tar.gz: f7bcdaea631373c214f13f074360d845eeae081dee58831d7e25694dfab67878
5
+ SHA512:
6
+ metadata.gz: 608a5f23ec84dcf6d0a653a237a34e98a632f05c30ed95c6e02ad4b3a38974faaee67239b56710daa397649aeaa55242cebf55685a5fea8c334be5f300ae6958
7
+ data.tar.gz: 834235bff08e6f449a26919a714cd82f1e2ebdf866a2e3df68872ae1e71d2cc84684c77254f76c18aa050ae4e1da594bf6cbeb0795232980b7558d5e1534813d
@@ -0,0 +1,16 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.0.1
14
+ bundler-cache: true
15
+ - name: Run the default task
16
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.6
7
+ NewCops: enable
8
+ SuggestExtensions: false
9
+
10
+ Layout/LineLength:
11
+ Max: 120
12
+
13
+ Layout/AccessModifierIndentation:
14
+ EnforcedStyle: outdent
15
+
16
+ Layout/SpaceInLambdaLiteral:
17
+ EnforcedStyle: require_space
18
+
19
+ Style/Documentation:
20
+ Enabled: false
21
+
22
+ Style/StringLiterals:
23
+ Enabled: true
24
+ EnforcedStyle: single_quotes
25
+
26
+ Style/StringLiteralsInInterpolation:
27
+ Enabled: true
28
+ EnforcedStyle: single_quotes
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in has_embedded_document.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rspec', '~> 3.0'
10
+ gem 'rubocop', '~> 1.25'
11
+ gem 'rubocop-performance'
12
+ gem 'rubocop-rails'
13
+ gem 'rubocop-rspec'
data/Gemfile.lock ADDED
@@ -0,0 +1,85 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ has_embedded_document (0.1.0)
5
+ activerecord (>= 5.2.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (7.0.2.2)
11
+ activesupport (= 7.0.2.2)
12
+ activerecord (7.0.2.2)
13
+ activemodel (= 7.0.2.2)
14
+ activesupport (= 7.0.2.2)
15
+ activesupport (7.0.2.2)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 1.6, < 2)
18
+ minitest (>= 5.1)
19
+ tzinfo (~> 2.0)
20
+ ast (2.4.2)
21
+ concurrent-ruby (1.1.9)
22
+ diff-lcs (1.5.0)
23
+ i18n (1.10.0)
24
+ concurrent-ruby (~> 1.0)
25
+ minitest (5.15.0)
26
+ parallel (1.21.0)
27
+ parser (3.1.0.0)
28
+ ast (~> 2.4.1)
29
+ rack (2.2.3)
30
+ rainbow (3.1.1)
31
+ rake (13.0.6)
32
+ regexp_parser (2.2.1)
33
+ rexml (3.2.5)
34
+ rspec (3.10.0)
35
+ rspec-core (~> 3.10.0)
36
+ rspec-expectations (~> 3.10.0)
37
+ rspec-mocks (~> 3.10.0)
38
+ rspec-core (3.10.2)
39
+ rspec-support (~> 3.10.0)
40
+ rspec-expectations (3.10.2)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.10.0)
43
+ rspec-mocks (3.10.3)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.10.0)
46
+ rspec-support (3.10.3)
47
+ rubocop (1.25.1)
48
+ parallel (~> 1.10)
49
+ parser (>= 3.1.0.0)
50
+ rainbow (>= 2.2.2, < 4.0)
51
+ regexp_parser (>= 1.8, < 3.0)
52
+ rexml
53
+ rubocop-ast (>= 1.15.1, < 2.0)
54
+ ruby-progressbar (~> 1.7)
55
+ unicode-display_width (>= 1.4.0, < 3.0)
56
+ rubocop-ast (1.15.2)
57
+ parser (>= 3.0.1.1)
58
+ rubocop-performance (1.13.2)
59
+ rubocop (>= 1.7.0, < 2.0)
60
+ rubocop-ast (>= 0.4.0)
61
+ rubocop-rails (2.13.2)
62
+ activesupport (>= 4.2.0)
63
+ rack (>= 1.1)
64
+ rubocop (>= 1.7.0, < 2.0)
65
+ rubocop-rspec (2.8.0)
66
+ rubocop (~> 1.19)
67
+ ruby-progressbar (1.11.0)
68
+ tzinfo (2.0.4)
69
+ concurrent-ruby (~> 1.0)
70
+ unicode-display_width (2.1.0)
71
+
72
+ PLATFORMS
73
+ x86_64-linux
74
+
75
+ DEPENDENCIES
76
+ has_embedded_document!
77
+ rake (~> 13.0)
78
+ rspec (~> 3.0)
79
+ rubocop (~> 1.25)
80
+ rubocop-performance
81
+ rubocop-rails
82
+ rubocop-rspec
83
+
84
+ BUNDLED WITH
85
+ 2.2.17
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # HasEmbeddedDocument
2
+
3
+ Embedded JSON document objects for your ActiveRecord models.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'has_embedded_document'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Then run:
18
+
19
+ $ bin/rails has_embedded_document:install
20
+
21
+ ### Installing the DSL
22
+
23
+ Add to your application record class:
24
+
25
+ ```ruby
26
+ class ApplicationRecord < ActiveRecord::Base
27
+ extend HasEmbeddedDocument::DSL
28
+
29
+ # ...
30
+ end
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```ruby
36
+ class Address < ApplicationDocument
37
+ attribute :line1, :string
38
+ attribute :city, :string
39
+ attribute :region, :string
40
+ attribute :country, :string
41
+ end
42
+ ```
43
+
44
+ ```ruby
45
+ class User < ApplicationRecord
46
+ has_embedded_document :address, Address
47
+ end
48
+ ```
49
+
50
+ ```ruby
51
+ user = User.new
52
+ user.address = Address.new(country: 'CA', region: 'ON')
53
+
54
+ user.address.country # => 'CA'
55
+ user.address.region # => 'ON'
56
+ ```
57
+
58
+ ### Validations
59
+
60
+ ```ruby
61
+ class Address < ApplicationDocument
62
+ attribute :line1, :string
63
+ attribute :city, :string
64
+ attribute :region, :string
65
+ attribute :country, :string
66
+
67
+ validates :line1, :city, :region, presence: true
68
+ validates :country, inclusion: { in: ['CA', 'US'] }
69
+ end
70
+ ```
71
+
72
+ ```ruby
73
+ user = User.new
74
+ user.address = Address.new(country: 'CA', region: 'ON', city: 'Toronto')
75
+
76
+ user.valid? # => false
77
+ user.errors # => address.line1 can't be blank, etc.
78
+ ```
79
+
80
+ ### Disable Validations
81
+
82
+ ```ruby
83
+ class User < ApplicationRecord
84
+ has_embedded_document :address, Address, validate: false
85
+ end
86
+ ```
87
+
88
+ ## Development
89
+
90
+ 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.
91
+
92
+ 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).
93
+
94
+ ## Contributing
95
+
96
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mintyfresh/has_embedded_document.
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 'has_embedded_document'
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/rspec ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('rspec-core', 'rspec')
data/bin/rubocop ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('rubocop', 'rubocop')
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,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/has_embedded_document/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'has_embedded_document'
7
+ spec.version = HasEmbeddedDocument::VERSION
8
+ spec.authors = ['Minty Fresh']
9
+ spec.email = ['7896757+mintyfresh@users.noreply.github.com']
10
+
11
+ spec.summary = 'Embedded data objects for ActiveRecord models'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/mintyfresh/has_embedded_document'
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
15
+
16
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
17
+ spec.metadata['rubygems_mfa_required'] = 'true'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = spec.homepage
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ # Uncomment to register a new dependency of your gem
32
+ spec.add_dependency 'activerecord', '>= 5.2.0'
33
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module HasEmbeddedDocument
6
+ class DocumentGenerator < Rails::Generators::NamedBase
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ def create_document
10
+ template 'document.rb.erb', "app/models/#{document_file_name}.rb"
11
+ end
12
+
13
+ private
14
+
15
+ # @return [String]
16
+ def document_class_name
17
+ "#{name.camelize.chomp('Document')}Document"
18
+ end
19
+
20
+ # @return [String]
21
+ def document_file_name
22
+ document_class_name.underscore
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module HasEmbeddedDocument
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ def create_install
10
+ template 'application_document.rb', 'app/models/application_document.rb'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationDocument < HasEmbeddedDocument::Base
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= document_class_name %> < ApplicationDocument
4
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model'
4
+ require 'active_record'
5
+
6
+ module HasEmbeddedDocument
7
+ class Base
8
+ include ActiveModel::Validations
9
+
10
+ Attribute = Struct.new(:name, :type, :default)
11
+
12
+ # @return [Hash{Symbol => Attribute}]
13
+ def self.attributes
14
+ @attributes ||= {}
15
+ end
16
+
17
+ # @param name [Symbol]
18
+ # @param type [Symbol]
19
+ # @return [void]
20
+ def self.attribute(name, type = :string, default: nil, **options)
21
+ name = name.to_sym
22
+ type = ActiveRecord::Type.lookup(type, **options)
23
+
24
+ attributes[name] = Attribute.new(name, type, default).freeze
25
+
26
+ define_method(name) { read_attribute(name) }
27
+ define_method("#{name}=") { |value| write_attribute(name, value) }
28
+ end
29
+
30
+ # @param attributes [Hash{String => Object}]
31
+ def initialize(attributes = {})
32
+ @attributes = attributes.stringify_keys
33
+ end
34
+
35
+ # @return [Hash{Symbol => Object}]
36
+ def attributes
37
+ @attributes.dup
38
+ end
39
+
40
+ # @param attributes [Hash{Symbol => Object}]
41
+ # @return [void]
42
+ def attributes=(attributes)
43
+ attributes.each do |name, value|
44
+ write_attribute(name, value)
45
+ end
46
+ end
47
+
48
+ # @return [Base]
49
+ def dup
50
+ self.class.new(attributes)
51
+ end
52
+
53
+ # @param attributes [Hash]
54
+ # @return [Base]
55
+ def dup_with(attributes)
56
+ dup.tap do |object|
57
+ object.attributes = attributes
58
+ end
59
+ end
60
+
61
+ # @param name [Symbol]
62
+ def read_attribute(name)
63
+ attribute = self.class.attributes[name.to_sym]
64
+ raise ArgumentError, "Unknown attribute: #{name}" if attribute.nil?
65
+
66
+ @attributes.fetch(name.to_s) do
67
+ case (default = attribute.default)
68
+ when Proc then instance_eval(&default)
69
+ when Symbol then send(default)
70
+ else default
71
+ end
72
+ end
73
+ end
74
+
75
+ # @param name [Symbol]
76
+ # @param value [Object]
77
+ # @return [void]
78
+ def write_attribute(name, value)
79
+ attribute = self.class.attributes[name.to_sym]
80
+ raise ArgumentError, "Unknown attribute: #{name}" if attribute.nil?
81
+
82
+ @attributes[name.to_s] = attribute.type.cast(value)
83
+ end
84
+
85
+ # @return [Boolean]
86
+ def readonly?
87
+ @attributes.frozen?
88
+ end
89
+
90
+ # @return [self]
91
+ def readonly!
92
+ tap { @attributes.freeze }
93
+ end
94
+
95
+ # @return [Hash]
96
+ def to_h
97
+ attributes
98
+ end
99
+
100
+ # @param other [Base]
101
+ # @return [Boolean]
102
+ def ==(other)
103
+ other.is_a?(self.class) && other.attributes == @attributes
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HasEmbeddedDocument
4
+ module DSL
5
+ # @param name [Symbol]
6
+ # @param document_class [Class<HasEmbeddedDocument::Base>, String]
7
+ # @param validate [Boolean]
8
+ # @param optional [Boolean]
9
+ # @return [void]
10
+ def has_embedded_document(name, document_class, validate: true, optional: true) # rubocop:disable Naming/PredicateName
11
+ define_embedded_reader(name, document_class)
12
+ define_embedded_writer(name, document_class)
13
+
14
+ validates(name, presence: { message: :required }) unless optional
15
+ validate_embedded_document(name) if validate
16
+ end
17
+
18
+ # @param name [Symbol]
19
+ # @param document_class [Class<HasEmbeddedDocument::Base>, String]
20
+ # @param validate [Boolean]
21
+ # @param optional [Boolean]
22
+ # @return [void]
23
+ def has_many_embedded_documents(name, document_class, validate: true, optional: true) # rubocop:disable Naming/PredicateName
24
+ define_embedded_array_reader(name, document_class)
25
+ define_embedded_array_writer(name, document_class)
26
+
27
+ validates(name, presence: { message: :required }) unless optional
28
+ validate_many_embedded_documents(name) if validate
29
+ end
30
+
31
+ # @param name [Symbol]
32
+ # @return [void]
33
+ def validate_embedded_document(name)
34
+ validate do
35
+ document = send(name)
36
+
37
+ if document&.invalid?
38
+ document.errors.each do |error|
39
+ errors.add("#{name}.#{error.attribute}", error.message)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # @param name [Symbol]
46
+ # @return [void]
47
+ def validate_many_embedded_documents(name)
48
+ validate do
49
+ documents = send(name)
50
+
51
+ documents&.each_with_index do |document, index|
52
+ next if document.valid?
53
+
54
+ document.errors.each do |error|
55
+ errors.add("#{name}[#{index}].#{error.attribute}", error.message)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ # @param document_class [Class, String]
62
+ # @return [Class<HasEmbeddedDocument::Base>]
63
+ def resolve_document_class(document_class)
64
+ document_class = document_class.constantize if document_class.is_a?(String)
65
+ raise ArgumentError, "Unknown document class: #{document_class}" unless document_class < HasEmbeddedDocument::Base
66
+
67
+ document_class
68
+ end
69
+
70
+ private
71
+
72
+ # @param name [Symbol]
73
+ # @param document_class [Class<HasEmbeddedDocument::Base>, String]
74
+ # @return [void]
75
+ def define_embedded_reader(name, document_class)
76
+ reader = wrapped_attribute_reader(name)
77
+
78
+ define_method(name) do
79
+ document_class = self.class.resolve_document_class(document_class)
80
+
81
+ document = instance_variable_get(:"@__#{name}_cache")
82
+ return document if instance_variable_defined?(:"@__#{name}_cache")
83
+
84
+ attributes = reader.call(self)
85
+ document = attributes && document_class.new(attributes.dup).readonly!
86
+ instance_variable_set(:"@__#{name}_cache", document)
87
+ end
88
+ end
89
+
90
+ # @param name [Symbol]
91
+ # @param document_class [Class<HasEmbeddedDocument::Base>, String]
92
+ # @return [void]
93
+ def define_embedded_writer(name, document_class)
94
+ writer = wrapped_attribute_writer(name)
95
+
96
+ define_method("#{name}=") do |value|
97
+ document_class = self.class.resolve_document_class(document_class)
98
+
99
+ attributes = value.is_a?(document_class) ? value.attributes : value
100
+ writer.call(self, attributes)
101
+
102
+ document = attributes && document_class.new(attributes.dup)
103
+ instance_variable_set(:"@__#{name}_cache", document.readonly!)
104
+ end
105
+ end
106
+
107
+ # @param name [Symbol]
108
+ # @param document_class [Class<HasEmbeddedDocument::Base>, String]
109
+ # @return [void]
110
+ def define_embedded_array_reader(name, document_class)
111
+ reader = wrapped_attribute_reader(name)
112
+
113
+ define_method(name) do
114
+ document_class = self.class.resolve_document_class(document_class)
115
+
116
+ documents = instance_variable_get(:"@__#{name}_cache")
117
+ return documents if instance_variable_defined?(:"@__#{name}_cache")
118
+
119
+ values = reader.call(self)
120
+ documents = values&.map { |attributes| document_class.new(attributes.dup).readonly! }
121
+ instance_variable_set(:"@__#{name}_cache", documents)
122
+ end
123
+ end
124
+
125
+ # @param name [Symbol]
126
+ # @param document_class [Class<HasEmbeddedDocument::Base>, String]
127
+ # @return [void]
128
+ def define_embedded_array_writer(name, document_class)
129
+ writer = wrapped_attribute_writer(name)
130
+
131
+ define_method("#{name}=") do |values|
132
+ document_class = self.class.resolve_document_class(document_class)
133
+
134
+ values = values&.map { |value| value.is_a?(document_class) ? value.attributes : value }
135
+ writer.call(self, values)
136
+
137
+ documents = values&.map { |attributes| document_class.new(attributes.dup).readonly! }
138
+ instance_variable_set(:"@__#{name}_cache", documents)
139
+ end
140
+ end
141
+
142
+ # @param name [Symbol]
143
+ # @return [Proc]
144
+ def wrapped_attribute_reader(name)
145
+ if respond_to?(:has_attribute?) && has_attribute?(name)
146
+ -> (object) { object.read_attribute(name) }
147
+ else
148
+ wrapped_method = instance_method(name)
149
+ -> (object) { wrapped_method.bind(object).call }
150
+ end
151
+ end
152
+
153
+ # @param name [Symbol]
154
+ # @return [Proc]
155
+ def wrapped_attribute_writer(name)
156
+ if respond_to?(:has_attribute?) && has_attribute?(name)
157
+ -> (object, value) { object.write_attribute(name, value) }
158
+ else
159
+ wrapped_method = instance_method("#{name}=")
160
+ -> (object, value) { wrapped_method.bind(object).call(value) }
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HasEmbeddedDocument
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'has_embedded_document/version'
4
+
5
+ module HasEmbeddedDocument
6
+ autoload :Base, 'has_embedded_document/base'
7
+ autoload :DSL, 'has_embedded_document/dsl'
8
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_embedded_document
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Minty Fresh
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-03-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.0
27
+ description: Embedded data objects for ActiveRecord models
28
+ email:
29
+ - 7896757+mintyfresh@users.noreply.github.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/workflows/main.yml"
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - README.md
41
+ - Rakefile
42
+ - bin/console
43
+ - bin/rspec
44
+ - bin/rubocop
45
+ - bin/setup
46
+ - has_embedded_document.gemspec
47
+ - lib/generators/has_embedded_document/document_generator.rb
48
+ - lib/generators/has_embedded_document/install_generator.rb
49
+ - lib/generators/has_embedded_document/templates/application_document.rb
50
+ - lib/generators/has_embedded_document/templates/document.rb.erb
51
+ - lib/has_embedded_document.rb
52
+ - lib/has_embedded_document/base.rb
53
+ - lib/has_embedded_document/dsl.rb
54
+ - lib/has_embedded_document/version.rb
55
+ homepage: https://github.com/mintyfresh/has_embedded_document
56
+ licenses: []
57
+ metadata:
58
+ allowed_push_host: https://rubygems.org
59
+ rubygems_mfa_required: 'true'
60
+ homepage_uri: https://github.com/mintyfresh/has_embedded_document
61
+ source_code_uri: https://github.com/mintyfresh/has_embedded_document
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 2.6.0
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.2.15
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Embedded data objects for ActiveRecord models
81
+ test_files: []