nested_record 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8e0e28260ca8d52235022d6ebcb471d6ea5cb585ebe4391bb4bc76ef47166d8a
4
+ data.tar.gz: 73413b02eb3c334060f3098254e53c0795546ca1875df2874a7f99a8f4a5c9b0
5
+ SHA512:
6
+ metadata.gz: c665c138f57fed85d36071e6decb469e3e5488203cac1f604920f61978ddab323e2be3c16d689c8ba01db514ab04d072528531e1d59bcbadab086eeabe3dec13
7
+ data.tar.gz: cd2bda40861ee824ba6adae759d03fdb0582b4c49b1f18c39fbcdb3f649864be1e41e8c834a9eddaa2a16970dd807724d5510f00b921ad9472a796b733338453
@@ -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
@@ -0,0 +1,8 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5
7
+ - 2.6
8
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in nested_record.gemspec
4
+ gemspec
@@ -0,0 +1,152 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nested_record (0.1.0)
5
+ rails (~> 5.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actioncable (5.2.3)
11
+ actionpack (= 5.2.3)
12
+ nio4r (~> 2.0)
13
+ websocket-driver (>= 0.6.1)
14
+ actionmailer (5.2.3)
15
+ actionpack (= 5.2.3)
16
+ actionview (= 5.2.3)
17
+ activejob (= 5.2.3)
18
+ mail (~> 2.5, >= 2.5.4)
19
+ rails-dom-testing (~> 2.0)
20
+ actionpack (5.2.3)
21
+ actionview (= 5.2.3)
22
+ activesupport (= 5.2.3)
23
+ rack (~> 2.0)
24
+ rack-test (>= 0.6.3)
25
+ rails-dom-testing (~> 2.0)
26
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
27
+ actionview (5.2.3)
28
+ activesupport (= 5.2.3)
29
+ builder (~> 3.1)
30
+ erubi (~> 1.4)
31
+ rails-dom-testing (~> 2.0)
32
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
33
+ activejob (5.2.3)
34
+ activesupport (= 5.2.3)
35
+ globalid (>= 0.3.6)
36
+ activemodel (5.2.3)
37
+ activesupport (= 5.2.3)
38
+ activerecord (5.2.3)
39
+ activemodel (= 5.2.3)
40
+ activesupport (= 5.2.3)
41
+ arel (>= 9.0)
42
+ activestorage (5.2.3)
43
+ actionpack (= 5.2.3)
44
+ activerecord (= 5.2.3)
45
+ marcel (~> 0.3.1)
46
+ activesupport (5.2.3)
47
+ concurrent-ruby (~> 1.0, >= 1.0.2)
48
+ i18n (>= 0.7, < 2)
49
+ minitest (~> 5.1)
50
+ tzinfo (~> 1.1)
51
+ arel (9.0.0)
52
+ builder (3.2.3)
53
+ byebug (11.0.1)
54
+ coderay (1.1.2)
55
+ concurrent-ruby (1.1.5)
56
+ crass (1.0.4)
57
+ diff-lcs (1.3)
58
+ erubi (1.8.0)
59
+ globalid (0.4.2)
60
+ activesupport (>= 4.2.0)
61
+ i18n (1.6.0)
62
+ concurrent-ruby (~> 1.0)
63
+ loofah (2.2.3)
64
+ crass (~> 1.0.2)
65
+ nokogiri (>= 1.5.9)
66
+ mail (2.7.1)
67
+ mini_mime (>= 0.1.1)
68
+ marcel (0.3.3)
69
+ mimemagic (~> 0.3.2)
70
+ method_source (0.9.2)
71
+ mimemagic (0.3.3)
72
+ mini_mime (1.0.1)
73
+ mini_portile2 (2.4.0)
74
+ minitest (5.11.3)
75
+ nio4r (2.3.1)
76
+ nokogiri (1.10.3)
77
+ mini_portile2 (~> 2.4.0)
78
+ pry (0.12.2)
79
+ coderay (~> 1.1.0)
80
+ method_source (~> 0.9.0)
81
+ pry-byebug (3.7.0)
82
+ byebug (~> 11.0)
83
+ pry (~> 0.10)
84
+ rack (2.0.7)
85
+ rack-test (1.1.0)
86
+ rack (>= 1.0, < 3)
87
+ rails (5.2.3)
88
+ actioncable (= 5.2.3)
89
+ actionmailer (= 5.2.3)
90
+ actionpack (= 5.2.3)
91
+ actionview (= 5.2.3)
92
+ activejob (= 5.2.3)
93
+ activemodel (= 5.2.3)
94
+ activerecord (= 5.2.3)
95
+ activestorage (= 5.2.3)
96
+ activesupport (= 5.2.3)
97
+ bundler (>= 1.3.0)
98
+ railties (= 5.2.3)
99
+ sprockets-rails (>= 2.0.0)
100
+ rails-dom-testing (2.0.3)
101
+ activesupport (>= 4.2.0)
102
+ nokogiri (>= 1.6)
103
+ rails-html-sanitizer (1.0.4)
104
+ loofah (~> 2.2, >= 2.2.2)
105
+ railties (5.2.3)
106
+ actionpack (= 5.2.3)
107
+ activesupport (= 5.2.3)
108
+ method_source
109
+ rake (>= 0.8.7)
110
+ thor (>= 0.19.0, < 2.0)
111
+ rake (10.5.0)
112
+ rspec (3.8.0)
113
+ rspec-core (~> 3.8.0)
114
+ rspec-expectations (~> 3.8.0)
115
+ rspec-mocks (~> 3.8.0)
116
+ rspec-core (3.8.1)
117
+ rspec-support (~> 3.8.0)
118
+ rspec-expectations (3.8.4)
119
+ diff-lcs (>= 1.2.0, < 2.0)
120
+ rspec-support (~> 3.8.0)
121
+ rspec-mocks (3.8.1)
122
+ diff-lcs (>= 1.2.0, < 2.0)
123
+ rspec-support (~> 3.8.0)
124
+ rspec-support (3.8.2)
125
+ sprockets (3.7.2)
126
+ concurrent-ruby (~> 1.0)
127
+ rack (> 1, < 3)
128
+ sprockets-rails (3.2.1)
129
+ actionpack (>= 4.0)
130
+ activesupport (>= 4.0)
131
+ sprockets (>= 3.0.0)
132
+ thor (0.20.3)
133
+ thread_safe (0.3.6)
134
+ tzinfo (1.2.5)
135
+ thread_safe (~> 0.1)
136
+ websocket-driver (0.7.1)
137
+ websocket-extensions (>= 0.1.0)
138
+ websocket-extensions (0.1.4)
139
+
140
+ PLATFORMS
141
+ ruby
142
+
143
+ DEPENDENCIES
144
+ bundler (~> 2.0)
145
+ nested_record!
146
+ pry
147
+ pry-byebug
148
+ rake (~> 10.0)
149
+ rspec (~> 3.0)
150
+
151
+ BUNDLED WITH
152
+ 2.0.1
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Vladimir Kochnev
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.
@@ -0,0 +1,39 @@
1
+ # NestedRecord
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/nested_record`. 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 'nested_record'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install nested_record
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 tags, 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]/nested_record.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'nested_record'
5
+
6
+ require "pry"
7
+ Pry.start
@@ -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,15 @@
1
+ module NestedRecord
2
+ require 'nested_record/version'
3
+
4
+ require 'active_record'
5
+ require 'active_support/dependencies'
6
+ require 'active_support/concern'
7
+
8
+ require 'nested_record/macro'
9
+ require 'nested_record/base'
10
+ require 'nested_record/collection'
11
+ require 'nested_record/setup'
12
+ require 'nested_record/type'
13
+ require 'nested_record/errors'
14
+ require 'nested_record/lookup_const'
15
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ class NestedRecord::Base
4
+ include ActiveModel::Model
5
+ include ActiveModel::Attributes
6
+ include ActiveModel::Dirty
7
+ include NestedRecord::Macro
8
+
9
+ class << self
10
+ def attributes_builder # :nodoc:
11
+ unless defined?(@attributes_builder) && @attributes_builder
12
+ @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, _default_attributes)
13
+ end
14
+ @attributes_builder
15
+ end
16
+
17
+ def inherited(klass)
18
+ if self < NestedRecord::Base
19
+ klass.class_eval do
20
+ attribute :type, :string
21
+ @deep_inherted = true
22
+ end
23
+ end
24
+ super
25
+ end
26
+
27
+ def deep_inherited?
28
+ @deep_inherted == true
29
+ end
30
+
31
+ def new(attributes = nil)
32
+ if attributes
33
+ attributes = attributes.stringify_keys
34
+ klass = find_instance_class(attributes['type'])
35
+ else
36
+ klass = self
37
+ end
38
+ if self == klass
39
+ super(attributes)
40
+ else
41
+ klass.new(attributes)
42
+ end
43
+ end
44
+
45
+ def instantiate(attributes)
46
+ klass = find_instance_class(attributes['type'])
47
+ attributes = klass.attributes_builder.build_from_database(attributes)
48
+ klass.allocate.tap do |instance|
49
+ instance.instance_variable_set(:@attributes, attributes)
50
+ end
51
+ end
52
+
53
+ def collection_class_name
54
+ :NestedRecord_Collection
55
+ end
56
+
57
+ def collection_class
58
+ return const_get(collection_class_name, false) if const_defined?(collection_class_name, false)
59
+ record_class = self
60
+ collection_superclass = deep_inherited? ? superclass.collection_class : NestedRecord::Collection
61
+ const_set(
62
+ collection_class_name,
63
+ Class.new(collection_superclass) do
64
+ @record_class = record_class
65
+ end
66
+ )
67
+ end
68
+
69
+ def collection_methods(&block)
70
+ collection_class.class_eval(&block)
71
+ end
72
+
73
+ def inherited_types(options)
74
+ raise ArgumentError, '.inherited_types is supported only for base classes' if deep_inherited?
75
+ if options[:full] && options[:namespace]
76
+ raise ArgumentError, ':full and :namespace options cannot be used at the same time'
77
+ end
78
+ @inherited_types_options = options
79
+ end
80
+
81
+ def instance_type
82
+ @instance_type ||=
83
+ if inherited_types_underscored?
84
+ type_const.underscore
85
+ else
86
+ type_const.dup
87
+ end
88
+ end
89
+
90
+ protected
91
+
92
+ def inherited_types_options
93
+ @inherited_types_options ||= {}
94
+ if deep_inherited?
95
+ superclass.inherited_types_options.merge(@inherited_types_options)
96
+ else
97
+ @inherited_types_options
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def type_const
104
+ if inherited_types_namespace
105
+ name.gsub(/\A#{inherited_types_namespace}::/, '')
106
+ else
107
+ name
108
+ end
109
+ end
110
+
111
+ def inherited_types_store_full?
112
+ !inherited_types_namespace && inherited_types_options.fetch(:full) { true }
113
+ end
114
+
115
+ def inherited_types_namespace
116
+ return @inherited_types_namespace if defined?(@inherited_types_namespace)
117
+
118
+ namespace = inherited_types_options.fetch(:namespace) { false }
119
+ return (@inherited_types_namespace = false) unless namespace
120
+
121
+ @inherited_types_namespace = NestedRecord.lookup_const(self, namespace).name
122
+ end
123
+
124
+ def inherited_types_underscored?
125
+ inherited_types_options.fetch(:underscored) { false }
126
+ end
127
+
128
+ def find_instance_class(type_name)
129
+ return self unless type_name.present?
130
+
131
+ type_name = type_name.camelize
132
+
133
+ subclass =
134
+ begin
135
+ if type_name.start_with?('::')
136
+ ActiveSupport::Dependencies.constantize(type_name)
137
+ elsif inherited_types_store_full?
138
+ ActiveSupport::Dependencies.constantize(type_name)
139
+ elsif inherited_types_namespace
140
+ ActiveSupport::Dependencies.safe_constantize("#{inherited_types_namespace}::#{type_name}") || ActiveSupport::Dependencies.constantize(type_name)
141
+
142
+ else
143
+ NestedRecord.lookup_const(self, type_name)
144
+ end
145
+ rescue NameError
146
+ raise NestedRecord::InvalidTypeError, "Failed to locate type '#{type_name}'"
147
+ end
148
+ unless subclass.is_a? Class
149
+ raise NestedRecord::InvalidTypeError, "Invalid type '#{type_name}': should be a class"
150
+ end
151
+ unless subclass <= self
152
+ raise NestedRecord::InvalidTypeError, "Invalid type '#{type_name}': should be a subclass of #{self}"
153
+ end
154
+ subclass
155
+ end
156
+ end
157
+
158
+ def initialize(attributes = nil)
159
+ super
160
+ self.type = self.class.instance_type if self.class.deep_inherited?
161
+ end
162
+
163
+ def ==(other)
164
+ attributes == other.attributes
165
+ end
166
+
167
+ def as_json
168
+ attributes.transform_values(&:as_json)
169
+ end
170
+
171
+ def inspect
172
+ as = attributes.except('type').map { |k,v| "#{k}: #{v.inspect}" }
173
+ "#<#{self.class.name} #{as.join(', ')}>"
174
+ end
175
+
176
+ def read_attribute(attr)
177
+ @attributes.fetch_value(attr.to_s)
178
+ end
179
+
180
+ def match?(attrs)
181
+ attrs.all? do |attr, others|
182
+ ours = read_attribute(attr)
183
+ if others.is_a? Array
184
+ others.include? ours
185
+ else
186
+ others == ours
187
+ end
188
+ end
189
+ end
190
+ end