active_data 1.0.0 → 1.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 +5 -5
- data/.codeclimate.yml +13 -0
- data/.rubocop.yml +56 -0
- data/.rubocop_todo.yml +53 -0
- data/.rvmrc +1 -1
- data/.travis.yml +15 -2
- data/Appraisals +1 -1
- data/CHANGELOG.md +31 -0
- data/Guardfile +8 -8
- data/README.md +256 -0
- data/Rakefile +2 -4
- data/active_data.gemspec +8 -7
- data/gemfiles/rails.4.0.gemfile +1 -1
- data/gemfiles/rails.4.1.gemfile +1 -1
- data/gemfiles/rails.4.2.gemfile +1 -1
- data/gemfiles/rails.5.0.gemfile +1 -1
- data/gemfiles/rails.5.1.gemfile +14 -0
- data/lib/active_data/active_record/associations.rb +18 -13
- data/lib/active_data/active_record/nested_attributes.rb +8 -14
- data/lib/active_data/base.rb +13 -0
- data/lib/active_data/config.rb +4 -4
- data/lib/active_data/errors.rb +29 -13
- data/lib/active_data/extensions.rb +22 -21
- data/lib/active_data/model/associations/base.rb +22 -6
- data/lib/active_data/model/associations/embeds_any.rb +17 -0
- data/lib/active_data/model/associations/embeds_many.rb +29 -19
- data/lib/active_data/model/associations/embeds_one.rb +30 -26
- data/lib/active_data/model/associations/nested_attributes.rb +82 -50
- data/lib/active_data/model/associations/persistence_adapters/active_record/referenced_proxy.rb +31 -0
- data/lib/active_data/model/associations/persistence_adapters/active_record.rb +66 -0
- data/lib/active_data/model/associations/persistence_adapters/base.rb +53 -0
- data/lib/active_data/model/associations/references_any.rb +41 -0
- data/lib/active_data/model/associations/references_many.rb +51 -37
- data/lib/active_data/model/associations/references_one.rb +43 -41
- data/lib/active_data/model/associations/reflections/base.rb +19 -29
- data/lib/active_data/model/associations/reflections/embeds_any.rb +43 -0
- data/lib/active_data/model/associations/reflections/embeds_many.rb +3 -13
- data/lib/active_data/model/associations/reflections/embeds_one.rb +5 -37
- data/lib/active_data/model/associations/reflections/references_any.rb +62 -0
- data/lib/active_data/model/associations/reflections/references_many.rb +7 -7
- data/lib/active_data/model/associations/reflections/references_one.rb +9 -7
- data/lib/active_data/model/associations/reflections/singular.rb +35 -0
- data/lib/active_data/model/associations/validations.rb +2 -27
- data/lib/active_data/model/associations.rb +12 -10
- data/lib/active_data/model/attributes/attribute.rb +10 -10
- data/lib/active_data/model/attributes/base.rb +8 -7
- data/lib/active_data/model/attributes/localized.rb +4 -4
- data/lib/active_data/model/attributes/reference_many.rb +6 -8
- data/lib/active_data/model/attributes/reference_one.rb +17 -9
- data/lib/active_data/model/attributes/reflections/attribute.rb +2 -2
- data/lib/active_data/model/attributes/reflections/base.rb +8 -11
- data/lib/active_data/model/attributes/reflections/localized.rb +2 -2
- data/lib/active_data/model/attributes/reflections/reference_one.rb +11 -22
- data/lib/active_data/model/attributes/reflections/represents.rb +5 -6
- data/lib/active_data/model/attributes/represents.rb +6 -5
- data/lib/active_data/model/attributes.rb +33 -87
- data/lib/active_data/model/callbacks.rb +6 -7
- data/lib/active_data/model/conventions.rb +2 -0
- data/lib/active_data/model/dirty.rb +4 -4
- data/lib/active_data/model/lifecycle.rb +18 -20
- data/lib/active_data/model/localization.rb +5 -2
- data/lib/active_data/model/persistence.rb +2 -2
- data/lib/active_data/model/primary.rb +19 -14
- data/lib/active_data/model/representation.rb +81 -0
- data/lib/active_data/model/scopes.rb +22 -12
- data/lib/active_data/model/validations/associated.rb +3 -2
- data/lib/active_data/model/validations/nested.rb +6 -1
- data/lib/active_data/model/validations.rb +3 -3
- data/lib/active_data/model.rb +2 -1
- data/lib/active_data/undefined_class.rb +9 -0
- data/lib/active_data/version.rb +1 -1
- data/lib/active_data.rb +40 -17
- data/spec/lib/active_data/active_record/associations_spec.rb +107 -45
- data/spec/lib/active_data/active_record/nested_attributes_spec.rb +1 -2
- data/spec/lib/active_data/config_spec.rb +37 -15
- data/spec/lib/active_data/model/associations/embeds_many_spec.rb +475 -172
- data/spec/lib/active_data/model/associations/embeds_one_spec.rb +353 -96
- data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +108 -12
- data/spec/lib/active_data/model/associations/persistence_adapters/active_record_spec.rb +58 -0
- data/spec/lib/active_data/model/associations/references_many_spec.rb +440 -64
- data/spec/lib/active_data/model/associations/references_one_spec.rb +347 -36
- data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +8 -7
- data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +7 -6
- data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +81 -33
- data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +116 -37
- data/spec/lib/active_data/model/associations/validations_spec.rb +27 -43
- data/spec/lib/active_data/model/associations_spec.rb +34 -25
- data/spec/lib/active_data/model/attributes/attribute_spec.rb +26 -23
- data/spec/lib/active_data/model/attributes/base_spec.rb +5 -6
- data/spec/lib/active_data/model/attributes/collection_spec.rb +7 -8
- data/spec/lib/active_data/model/attributes/dictionary_spec.rb +40 -33
- data/spec/lib/active_data/model/attributes/localized_spec.rb +27 -28
- data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +6 -6
- data/spec/lib/active_data/model/attributes/represents_spec.rb +10 -78
- data/spec/lib/active_data/model/attributes_spec.rb +150 -45
- data/spec/lib/active_data/model/callbacks_spec.rb +69 -70
- data/spec/lib/active_data/model/conventions_spec.rb +0 -1
- data/spec/lib/active_data/model/dirty_spec.rb +22 -13
- data/spec/lib/active_data/model/lifecycle_spec.rb +49 -23
- data/spec/lib/active_data/model/persistence_spec.rb +5 -6
- data/spec/lib/active_data/model/representation_spec.rb +126 -0
- data/spec/lib/active_data/model/scopes_spec.rb +1 -3
- data/spec/lib/active_data/model/typecasting_spec.rb +6 -5
- data/spec/lib/active_data/model/validations/associated_spec.rb +26 -18
- data/spec/lib/active_data/model/validations/nested_spec.rb +89 -18
- data/spec/lib/active_data/model_spec.rb +1 -2
- data/spec/lib/active_data_spec.rb +0 -1
- data/spec/shared/nested_attribute_examples.rb +332 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/model_helpers.rb +2 -2
- data/spec/support/muffle_helper.rb +7 -0
- metadata +52 -18
- data/lib/active_data/model/associations/collection/referenced.rb +0 -26
- data/lib/active_data/model/associations/reflections/reference_reflection.rb +0 -45
- data/spec/lib/active_data/model/nested_attributes.rb +0 -202
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: df62766321d027161f359a1101e7e8a94842386b72188a02d7a3686f679f466e
|
|
4
|
+
data.tar.gz: 0331643a7e25c573a79269df28096971189efc52730c41b58e3922a1ab0c50af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: de160dbcd218f15a9203e29bf6e1fdde66b028fb97cee58b8f2816e6191202909c173da2d8db6ea8fd2db02e91314510b501c358852d3e2fe9b13876d093794c
|
|
7
|
+
data.tar.gz: 20b7783fc647ce010928847da8e5d8c4bd9cfeb0fcb0618114b72186cbcd76e6efa723f79f0f2856f41cd7cb766a5cfa3e6eddd3c34d02b5ce949754a5e83b0b
|
data/.codeclimate.yml
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
3
|
+
Lint/AmbiguousBlockAssociation:
|
|
4
|
+
Enabled: false
|
|
5
|
+
|
|
6
|
+
Lint/EndAlignment:
|
|
7
|
+
EnforcedStyleAlignWith: variable
|
|
8
|
+
|
|
9
|
+
Layout/AccessModifierIndentation:
|
|
10
|
+
EnforcedStyle: outdent
|
|
11
|
+
|
|
12
|
+
Layout/AlignHash:
|
|
13
|
+
EnforcedLastArgumentHashStyle: always_ignore
|
|
14
|
+
|
|
15
|
+
Layout/AlignParameters:
|
|
16
|
+
EnforcedStyle: with_fixed_indentation
|
|
17
|
+
|
|
18
|
+
Layout/CaseIndentation:
|
|
19
|
+
EnforcedStyle: end
|
|
20
|
+
|
|
21
|
+
Layout/IndentArray:
|
|
22
|
+
EnforcedStyle: consistent
|
|
23
|
+
|
|
24
|
+
Layout/IndentHash:
|
|
25
|
+
EnforcedStyle: consistent
|
|
26
|
+
|
|
27
|
+
Layout/IndentHeredoc:
|
|
28
|
+
Enabled: false
|
|
29
|
+
|
|
30
|
+
Layout/MultilineMethodCallIndentation:
|
|
31
|
+
EnforcedStyle: indented
|
|
32
|
+
|
|
33
|
+
Layout/MultilineOperationIndentation:
|
|
34
|
+
EnforcedStyle: indented
|
|
35
|
+
|
|
36
|
+
Layout/SpaceInsideHashLiteralBraces:
|
|
37
|
+
EnforcedStyle: no_space
|
|
38
|
+
|
|
39
|
+
Metrics/BlockLength:
|
|
40
|
+
Exclude:
|
|
41
|
+
- '**/*_spec.rb'
|
|
42
|
+
- '**/*_examples.rb'
|
|
43
|
+
- '**/*.rake'
|
|
44
|
+
|
|
45
|
+
Metrics/ModuleLength:
|
|
46
|
+
Exclude:
|
|
47
|
+
- '**/*_spec.rb'
|
|
48
|
+
|
|
49
|
+
Style/Alias:
|
|
50
|
+
EnforcedStyle: prefer_alias_method
|
|
51
|
+
|
|
52
|
+
Style/AndOr:
|
|
53
|
+
EnforcedStyle: conditionals
|
|
54
|
+
|
|
55
|
+
Style/DoubleNegation:
|
|
56
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config`
|
|
3
|
+
# on 2016-10-14 10:15:31 +0700 using RuboCop version 0.44.1.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 19
|
|
10
|
+
Metrics/AbcSize:
|
|
11
|
+
Max: 54
|
|
12
|
+
|
|
13
|
+
# Offense count: 1
|
|
14
|
+
# Configuration parameters: CountComments.
|
|
15
|
+
Metrics/BlockLength:
|
|
16
|
+
Max: 26
|
|
17
|
+
|
|
18
|
+
# Offense count: 2
|
|
19
|
+
# Configuration parameters: CountComments.
|
|
20
|
+
Metrics/ClassLength:
|
|
21
|
+
Max: 128
|
|
22
|
+
|
|
23
|
+
# Offense count: 4
|
|
24
|
+
Metrics/CyclomaticComplexity:
|
|
25
|
+
Max: 13
|
|
26
|
+
|
|
27
|
+
# Offense count: 904
|
|
28
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives.
|
|
29
|
+
# URISchemes: http, https
|
|
30
|
+
Metrics/LineLength:
|
|
31
|
+
Max: 187
|
|
32
|
+
|
|
33
|
+
# Offense count: 21
|
|
34
|
+
# Configuration parameters: CountComments.
|
|
35
|
+
Metrics/MethodLength:
|
|
36
|
+
Max: 43
|
|
37
|
+
|
|
38
|
+
# Offense count: 2
|
|
39
|
+
# Configuration parameters: CountComments.
|
|
40
|
+
Metrics/ModuleLength:
|
|
41
|
+
Max: 114
|
|
42
|
+
|
|
43
|
+
# Offense count: 2
|
|
44
|
+
Metrics/BlockLength:
|
|
45
|
+
Max: 27
|
|
46
|
+
|
|
47
|
+
# Offense count: 4
|
|
48
|
+
Metrics/PerceivedComplexity:
|
|
49
|
+
Max: 16
|
|
50
|
+
|
|
51
|
+
# Offense count: 75
|
|
52
|
+
Style/Documentation:
|
|
53
|
+
Enabled: false
|
data/.rvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
rvm use --create 2.
|
|
1
|
+
rvm use --create 2.4@active_data
|
data/.travis.yml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
sudo: false
|
|
2
2
|
|
|
3
3
|
rvm:
|
|
4
|
-
- 2.2.
|
|
5
|
-
- 2.3.
|
|
4
|
+
- 2.2.8
|
|
5
|
+
- 2.3.5
|
|
6
6
|
- rbx
|
|
7
7
|
|
|
8
8
|
gemfile:
|
|
@@ -10,7 +10,20 @@ gemfile:
|
|
|
10
10
|
- gemfiles/rails.4.1.gemfile
|
|
11
11
|
- gemfiles/rails.4.2.gemfile
|
|
12
12
|
- gemfiles/rails.5.0.gemfile
|
|
13
|
+
- gemfiles/rails.5.1.gemfile
|
|
13
14
|
|
|
14
15
|
matrix:
|
|
15
16
|
allow_failures:
|
|
16
17
|
- rvm: rbx
|
|
18
|
+
include:
|
|
19
|
+
- rvm: 2.4.2
|
|
20
|
+
gemfile: gemfiles/rails.5.0.gemfile
|
|
21
|
+
- rvm: 2.4.2
|
|
22
|
+
gemfile: gemfiles/rails.5.1.gemfile
|
|
23
|
+
|
|
24
|
+
before_install:
|
|
25
|
+
- gem update --system --no-doc
|
|
26
|
+
|
|
27
|
+
script:
|
|
28
|
+
- bundle exec rspec
|
|
29
|
+
- bundle exec rubocop
|
data/Appraisals
CHANGED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# master
|
|
2
|
+
|
|
3
|
+
# Version 1.1.0
|
|
4
|
+
|
|
5
|
+
## Incompatible changes
|
|
6
|
+
|
|
7
|
+
* Represented attributes are not provided by default, to add them, `include ActiveData::Model::Representation` (#46)
|
|
8
|
+
|
|
9
|
+
* `include ActiveData::Model::Associations::Validations` is not included by default anymore, to get `validate_ancestry!`, `valid_ancestry?` and `invalid_ancestry?` methods back you need to include this module manually
|
|
10
|
+
|
|
11
|
+
## Changes
|
|
12
|
+
|
|
13
|
+
* Introduce persistence adapters for associations (#24, #51)
|
|
14
|
+
|
|
15
|
+
* `ActionController::Parameters` support (#43)
|
|
16
|
+
|
|
17
|
+
* Nested attributes simple method overriding (#41)
|
|
18
|
+
|
|
19
|
+
* Persistence for `references` associations (#28, #32)
|
|
20
|
+
|
|
21
|
+
* Support `update_only` option on collection nested attributes (#30)
|
|
22
|
+
|
|
23
|
+
* `embedder` accessor for embedded associations
|
|
24
|
+
|
|
25
|
+
* Dynamic scopes for `references` associations (#27)
|
|
26
|
+
|
|
27
|
+
## Bugfixes
|
|
28
|
+
|
|
29
|
+
* Fixed multiple validations on represented attributes and associations (#44)
|
|
30
|
+
|
|
31
|
+
* Proper boolean attributes defaults (#31, #33)
|
data/Guardfile
CHANGED
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
|
33
33
|
# * 'just' rspec: 'rspec'
|
|
34
34
|
|
|
35
|
-
guard :rspec, cmd:
|
|
36
|
-
require
|
|
35
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
|
36
|
+
require 'guard/rspec/dsl'
|
|
37
37
|
dsl = Guard::RSpec::Dsl.new(self)
|
|
38
38
|
|
|
39
39
|
# Feel free to open issues for suggestions and improvements
|
|
@@ -49,15 +49,15 @@ guard :rspec, cmd: "bundle exec rspec" do
|
|
|
49
49
|
dsl.watch_spec_files_for(ruby.lib_files)
|
|
50
50
|
|
|
51
51
|
# Rails files
|
|
52
|
-
rails = dsl.rails(view_extensions: %w
|
|
52
|
+
rails = dsl.rails(view_extensions: %w[erb haml slim])
|
|
53
53
|
dsl.watch_spec_files_for(rails.app_files)
|
|
54
54
|
dsl.watch_spec_files_for(rails.views)
|
|
55
55
|
|
|
56
56
|
watch(rails.controllers) do |m|
|
|
57
57
|
[
|
|
58
|
-
rspec.spec.("routing/#{m[1]}_routing"),
|
|
59
|
-
rspec.spec.("controllers/#{m[1]}_controller"),
|
|
60
|
-
rspec.spec.("acceptance/#{m[1]}")
|
|
58
|
+
rspec.spec.call("routing/#{m[1]}_routing"),
|
|
59
|
+
rspec.spec.call("controllers/#{m[1]}_controller"),
|
|
60
|
+
rspec.spec.call("acceptance/#{m[1]}")
|
|
61
61
|
]
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -67,11 +67,11 @@ guard :rspec, cmd: "bundle exec rspec" do
|
|
|
67
67
|
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
|
68
68
|
|
|
69
69
|
# Capybara features specs
|
|
70
|
-
watch(rails.view_dirs)
|
|
70
|
+
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
|
71
71
|
|
|
72
72
|
# Turnip features and steps
|
|
73
73
|
watch(%r{^spec/acceptance/(.+)\.feature$})
|
|
74
74
|
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
|
75
|
-
Dir[File.join("**/#{m[1]}.feature")][0] ||
|
|
75
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance'
|
|
76
76
|
end
|
|
77
77
|
end
|
data/README.md
CHANGED
|
@@ -134,22 +134,278 @@ ActiveData has modular architecture, so it is required to include modules to obt
|
|
|
134
134
|
|
|
135
135
|
### Attributes
|
|
136
136
|
|
|
137
|
+
ActiveData provides several types of attributes and typecasts each attribute to its defined type upon initialization.
|
|
137
138
|
|
|
139
|
+
```ruby
|
|
140
|
+
class Book
|
|
141
|
+
include ActiveData::Model
|
|
142
|
+
|
|
143
|
+
attribute :title, String
|
|
144
|
+
collection :author_ids, Integer
|
|
145
|
+
end
|
|
146
|
+
```
|
|
138
147
|
|
|
139
148
|
#### Attribute
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
attribute :full_name, String, default: 'John Talbot'
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
By default, if type for attribute is not set, it is defined with `Object` type, so it would be a great idea to specify type for every attribute explicitly.
|
|
155
|
+
|
|
156
|
+
Type is necessary for attribute typecasting. Here is the list of pre-defined basic typecasters:
|
|
157
|
+
|
|
158
|
+
```irb
|
|
159
|
+
[1] pry(main)> ActiveData._typecasters.keys
|
|
160
|
+
=> ["Object", "String", "Array", "Hash", "Date", "DateTime", "Time", "ActiveSupport::TimeZone", "BigDecimal", "Float", "Integer", "Boolean", "ActiveData::UUID"]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
In addition, you can provide any class type when defining the attribute, but in that case you will be able to only assign instances of that specific class or value nil:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
attribute :template, MyCustomTemplateType
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
##### Defaults
|
|
170
|
+
|
|
171
|
+
It is possible to provide default values for attributes and they will act in the same way as AR or Mongoid default values:
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
attribute :check, Boolean, default: false # Simply false by default
|
|
175
|
+
attribute :today, Date, default: ->{ Time.zone.now.to_date } # Dynamic default value
|
|
176
|
+
attribute :today_wday, Integer, default: ->{ today.wday } # Default is evaluated in instance context
|
|
177
|
+
attribute :today_wday, Integer, default: ->(instance) { instance.today.wday } # The same as previous, but instance provided explicitly
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
##### Enums
|
|
181
|
+
|
|
182
|
+
Enums restrict the scope of possible values for attribute. If assigned value is not included in provided list - then it turns to nil:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
attribute :direction, String, enum: %w[north south east west]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
##### Normalizers
|
|
189
|
+
|
|
190
|
+
Normalizers are applied last, modifying typecast value. It is possible to provide a list of normalizers, they will be applied in the order. It is possible to pre-define normalizers to DRY code:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
ActiveData.normalizer(:trim) do |value, options, _attribute|
|
|
194
|
+
value.first(options[:length] || 2)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
attribute :title, String, normalizers: [->(value) { value.strip }, trim: {length: 80}]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
##### Readonly
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
attribute :name, String, readonly: true # Readonly forever
|
|
204
|
+
attribute :name, String, readonly: ->{ true } # Conditionally readonly
|
|
205
|
+
attribute :name, String, readonly: ->(instance) { instance.subject.present? } # Explicit instance
|
|
206
|
+
```
|
|
207
|
+
|
|
140
208
|
#### Collection
|
|
209
|
+
|
|
210
|
+
Collection is simply an array of equally-typed values:
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
class Panda
|
|
214
|
+
include ActiveData::Model
|
|
215
|
+
|
|
216
|
+
collection :ids, Integer
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Collection typecasts each value to specified type and also no matter what are you going to pass - it will be an array.
|
|
221
|
+
|
|
222
|
+
```irb
|
|
223
|
+
[1] pry(main)> Panda.new
|
|
224
|
+
=> #<Panda ids: []>
|
|
225
|
+
[2] pry(main)> Panda.new(ids: 42)
|
|
226
|
+
=> #<Panda ids: [42]>
|
|
227
|
+
[3] pry(main)> Panda.new(ids: [42, '33'])
|
|
228
|
+
=> #<Panda ids: [42, 33]>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Default and enum modifiers are applied to every value, normalizer will be applied to the whole array.
|
|
232
|
+
|
|
141
233
|
#### Dictionary
|
|
234
|
+
|
|
235
|
+
Dictionary field is a hash of specified type values with string keys:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
class Foo
|
|
239
|
+
include ActiveData::Model
|
|
240
|
+
|
|
241
|
+
dictionary :ordering, String
|
|
242
|
+
end
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```irb
|
|
246
|
+
[1] pry(main)> Foo.new
|
|
247
|
+
=> #<Foo ordering: {}>
|
|
248
|
+
[2] pry(main)> Foo.new(ordering: {name: :desc})
|
|
249
|
+
=> #<Foo ordering: {"name"=>"desc"}>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Keys list might be restricted with `:keys` option, defaults and enums are applied to every value, normalizers are applied to the whole hash.
|
|
253
|
+
|
|
142
254
|
#### Localized
|
|
255
|
+
|
|
256
|
+
Localized is similar to how Globalize 3 attributes work.
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
localized :title, String
|
|
260
|
+
```
|
|
261
|
+
|
|
143
262
|
#### Represents
|
|
144
263
|
|
|
264
|
+
Represents provides an easy way to expose model attributes through an interface.
|
|
265
|
+
It will automatically set passed value to the represented object **before validation**.
|
|
266
|
+
You can use any ActiveRecord, ActiveModel or ActiveData object as a target of representation.
|
|
267
|
+
A type of an attribute will be taken from it.
|
|
268
|
+
If there is no type, it will be `Object` by default. You can set the type explicitly by passing the `type: TypeClass` option.
|
|
269
|
+
Represents will also add automatic validation of the target object.
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
class Person
|
|
273
|
+
include ActiveData::Model
|
|
274
|
+
|
|
275
|
+
attribute :name, String
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
class Doctor
|
|
279
|
+
include ActiveData::Model
|
|
280
|
+
include ActiveData::Model::Representation
|
|
281
|
+
|
|
282
|
+
attribute :person, Object
|
|
283
|
+
represents :name, of: :person
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
person = Person.new(name: 'Walter Bishop')
|
|
287
|
+
# => #<Person name: "Walter Bishop">
|
|
288
|
+
Doctor.new(person: person).name
|
|
289
|
+
# => "Walter Bishop"
|
|
290
|
+
Doctor.new(person: person, name: 'Dr. Walter Bishop').name
|
|
291
|
+
# => "Dr. Walter Bishop"
|
|
292
|
+
person.name
|
|
293
|
+
# => "Dr. Walter Bishop"
|
|
294
|
+
```
|
|
295
|
+
|
|
145
296
|
### Associations
|
|
146
297
|
|
|
298
|
+
ActiveData provides a set of associations. There are two types of them: referenced and embedded. The closest example of referenced association is AR `belongs_to` and as for embedded ones - Mongoid's embedded. Also these associations support `accepts_nested_attributes` call.
|
|
299
|
+
|
|
147
300
|
#### EmbedsOne
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
embeds_one :profile
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Defines singular embedded object. Might be defined inline:
|
|
307
|
+
|
|
308
|
+
```ruby
|
|
309
|
+
embeds_one :profile do
|
|
310
|
+
attribute :first_name, String
|
|
311
|
+
attribute :last_name, String
|
|
312
|
+
end
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Possible options:
|
|
316
|
+
|
|
317
|
+
* `:class_name` - association class name
|
|
318
|
+
* `:validate` - true or false
|
|
319
|
+
* `:default` - default value for association: attributes hash or instance of defined class
|
|
320
|
+
|
|
148
321
|
#### EmbedsMany
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
embeds_many :tags
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Defines collection of embedded objects. Might be defined inline:
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
embeds_many :tags do
|
|
331
|
+
attribute :identifier, String
|
|
332
|
+
end
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
* `:class_name` - association class name
|
|
336
|
+
* `:validate` - true or false
|
|
337
|
+
* `:default` - default value for association: attributes hash collection or instances of defined class
|
|
338
|
+
|
|
149
339
|
#### ReferencesOne
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
references_one :user
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
This will provide several methods to the object: `#user`, `#user=`, `#user_id` and `#user_id=`, just as would occur with an ActiveRecord association.
|
|
346
|
+
|
|
347
|
+
Possible options:
|
|
348
|
+
|
|
349
|
+
* `:class_name` - association class name
|
|
350
|
+
* `:primary_key` - associated object primary key (`:id` by default):
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
references_one :user, primary_key: :name
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
This will create the following methods: `#user`, `#user=`, `#user_name` and `#user_name=`
|
|
357
|
+
|
|
358
|
+
* `:reference_key` - redefines `#user_id` and `#user_id=` method names completely.
|
|
359
|
+
* `:validate` - true or false
|
|
360
|
+
* `:default` - default value for association: reference or object itself
|
|
361
|
+
|
|
150
362
|
#### ReferencesMany
|
|
363
|
+
|
|
364
|
+
```ruby
|
|
365
|
+
references_many :users
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
This will provide several methods to the object: `#users`, `#users=`, `#user_ids` and `#user_ids=` just as an ActiveRecord relation does.
|
|
369
|
+
|
|
370
|
+
Possible options:
|
|
371
|
+
|
|
372
|
+
* `:class_name` - association class name
|
|
373
|
+
* `:primary_key` - associated object primary key (`:id` by default):
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
references_many :users, primary_key: :name
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This will create the following methods: `#users`, `#users=`, `#user_names` and `#user_names=`
|
|
380
|
+
|
|
381
|
+
* `:reference_key` - redefines `#user_ids` and `#user_ids=` method names completely.
|
|
382
|
+
* `:validate` - true or false
|
|
383
|
+
* `:default` - default value for association: reference collection or objects themselves
|
|
384
|
+
|
|
151
385
|
#### Interacting with ActiveRecord
|
|
152
386
|
|
|
387
|
+
### Persistence Adapters
|
|
388
|
+
|
|
389
|
+
Adapter definition syntax:
|
|
390
|
+
```ruby
|
|
391
|
+
class Mongoid::Document
|
|
392
|
+
# anything that have similar interface to
|
|
393
|
+
# ActiveData::Model::Associations::PersistenceAdapters::Base
|
|
394
|
+
def self.active_data_persistence_adapter
|
|
395
|
+
MongoidAdapter
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
```
|
|
399
|
+
Where
|
|
400
|
+
`ClassName` - name of model class or one of ancestors
|
|
401
|
+
`data_source` - name of data source class
|
|
402
|
+
`primary_key` - key to search data
|
|
403
|
+
`scope_proc` - additional proc for filtering
|
|
404
|
+
|
|
405
|
+
All required interface for adapters described in `ActiveData::Model::Associations::PersistenceAdapters::Base`.
|
|
406
|
+
|
|
407
|
+
Adapter for ActiveRecord is `ActiveData::Model::Associations::PersistenceAdapters::ActiveRecord`. So, all AR models will use `PersistenceAdapters::ActiveRecord` by default.
|
|
408
|
+
|
|
153
409
|
### Primary
|
|
154
410
|
|
|
155
411
|
### Persistence
|
data/Rakefile
CHANGED
data/active_data.gemspec
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
require File.expand_path('../lib/active_data/version', __FILE__)
|
|
3
2
|
|
|
4
3
|
Gem::Specification.new do |gem|
|
|
@@ -8,23 +7,25 @@ Gem::Specification.new do |gem|
|
|
|
8
7
|
gem.summary = 'Working with hashes in AR style'
|
|
9
8
|
gem.homepage = ''
|
|
10
9
|
|
|
11
|
-
gem.files = `git ls-files`.split(
|
|
12
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
10
|
+
gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
|
11
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
13
12
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
14
13
|
gem.name = 'active_data'
|
|
15
14
|
gem.require_paths = ['lib']
|
|
16
15
|
gem.version = ActiveData::VERSION
|
|
17
16
|
|
|
18
|
-
gem.add_development_dependency '
|
|
17
|
+
gem.add_development_dependency 'activerecord', '>= 4.0'
|
|
19
18
|
gem.add_development_dependency 'appraisal'
|
|
19
|
+
gem.add_development_dependency 'database_cleaner'
|
|
20
|
+
gem.add_development_dependency 'rake'
|
|
20
21
|
gem.add_development_dependency 'rspec'
|
|
21
22
|
gem.add_development_dependency 'rspec-its'
|
|
23
|
+
gem.add_development_dependency 'rubocop'
|
|
22
24
|
gem.add_development_dependency 'rubysl', '~> 2.0' if RUBY_ENGINE == 'rbx'
|
|
23
25
|
gem.add_development_dependency 'sqlite3'
|
|
24
|
-
gem.add_development_dependency 'database_cleaner'
|
|
25
|
-
gem.add_development_dependency 'activerecord', '>= 4.0'
|
|
26
26
|
gem.add_development_dependency 'uuidtools'
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
gem.add_runtime_dependency 'activemodel', '>= 4.0'
|
|
29
|
+
gem.add_runtime_dependency 'activesupport', '>= 4.0'
|
|
29
30
|
gem.add_runtime_dependency 'tzinfo'
|
|
30
31
|
end
|
data/gemfiles/rails.4.0.gemfile
CHANGED
data/gemfiles/rails.4.1.gemfile
CHANGED
data/gemfiles/rails.4.2.gemfile
CHANGED
data/gemfiles/rails.5.0.gemfile
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "activesupport", "~> 5.1.0"
|
|
6
|
+
gem "activemodel", "~> 5.1.0"
|
|
7
|
+
gem "activerecord", "~> 5.1.0"
|
|
8
|
+
|
|
9
|
+
group :test do
|
|
10
|
+
gem "guard"
|
|
11
|
+
gem "guard-rspec"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
gemspec path: "../"
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
module ActiveData
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module Associations
|
|
4
|
+
READER = lambda do |ref, object|
|
|
5
|
+
value = object.read_attribute(ref.name)
|
|
6
|
+
if value.present?
|
|
7
|
+
value.is_a?(String) ? JSON.parse(value) : value
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
WRITER = lambda do |ref, object, value|
|
|
12
|
+
object.send(:write_attribute, ref.name, value ? value.to_json : nil)
|
|
13
|
+
end
|
|
14
|
+
|
|
4
15
|
module Reflections
|
|
5
16
|
class EmbedsOne < ActiveData::Model::Associations::Reflections::EmbedsOne
|
|
6
|
-
def is_a?
|
|
17
|
+
def is_a?(klass)
|
|
7
18
|
super || klass == ::ActiveRecord::Reflection::AssociationReflection
|
|
8
19
|
end
|
|
9
20
|
end
|
|
10
21
|
|
|
11
22
|
class EmbedsMany < ActiveData::Model::Associations::Reflections::EmbedsMany
|
|
12
|
-
def is_a?
|
|
23
|
+
def is_a?(klass)
|
|
13
24
|
super || klass == ::ActiveRecord::Reflection::AssociationReflection
|
|
14
25
|
end
|
|
15
26
|
end
|
|
@@ -18,17 +29,11 @@ module ActiveData
|
|
|
18
29
|
extend ActiveSupport::Concern
|
|
19
30
|
|
|
20
31
|
included do
|
|
21
|
-
{
|
|
22
|
-
define_singleton_method
|
|
23
|
-
reflection = reflection_class.build(self, self, name,
|
|
24
|
-
read:
|
|
25
|
-
|
|
26
|
-
JSON.parse(value) if value.present?
|
|
27
|
-
},
|
|
28
|
-
write: ->(reflection, object, value) {
|
|
29
|
-
object.send(:write_attribute, reflection.name, value ? value.to_json : nil)
|
|
30
|
-
}
|
|
31
|
-
), &block)
|
|
32
|
+
{embeds_many: Reflections::EmbedsMany, embeds_one: Reflections::EmbedsOne}.each do |(method, reflection_class)|
|
|
33
|
+
define_singleton_method method do |name, options = {}, &block|
|
|
34
|
+
reflection = reflection_class.build(self, self, name,
|
|
35
|
+
options.reverse_merge(read: READER, write: WRITER),
|
|
36
|
+
&block)
|
|
32
37
|
if ::ActiveRecord::Reflection.respond_to? :add_reflection
|
|
33
38
|
::ActiveRecord::Reflection.add_reflection self, reflection.name, reflection
|
|
34
39
|
else
|