granite-form 0.2.0 → 0.4.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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -2
  3. data/.github/workflows/{ci.yml → ruby.yml} +22 -4
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +3 -3
  6. data/Appraisals +1 -2
  7. data/CHANGELOG.md +15 -0
  8. data/README.md +6 -15
  9. data/docker-compose.yml +14 -0
  10. data/gemfiles/rails.5.0.gemfile +0 -1
  11. data/gemfiles/rails.5.1.gemfile +0 -1
  12. data/gemfiles/rails.5.2.gemfile +0 -1
  13. data/granite-form.gemspec +15 -15
  14. data/lib/granite/form/active_record/associations.rb +1 -1
  15. data/lib/granite/form/base.rb +1 -2
  16. data/lib/granite/form/errors.rb +0 -15
  17. data/lib/granite/form/model/associations/base.rb +0 -4
  18. data/lib/granite/form/model/associations/collection/embedded.rb +2 -1
  19. data/lib/granite/form/model/associations/collection/proxy.rb +1 -1
  20. data/lib/granite/form/model/associations/embeds_any.rb +7 -0
  21. data/lib/granite/form/model/associations/embeds_many.rb +9 -58
  22. data/lib/granite/form/model/associations/embeds_one.rb +7 -36
  23. data/lib/granite/form/model/associations/nested_attributes.rb +7 -7
  24. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +0 -4
  25. data/lib/granite/form/model/associations/persistence_adapters/base.rb +0 -4
  26. data/lib/granite/form/model/associations/references_many.rb +0 -32
  27. data/lib/granite/form/model/associations/references_one.rb +0 -28
  28. data/lib/granite/form/model/associations/reflections/embeds_any.rb +1 -1
  29. data/lib/granite/form/model/associations/reflections/embeds_many.rb +1 -1
  30. data/lib/granite/form/model/associations/reflections/embeds_one.rb +11 -1
  31. data/lib/granite/form/model/associations/reflections/references_any.rb +0 -4
  32. data/lib/granite/form/model/associations/reflections/singular.rb +0 -22
  33. data/lib/granite/form/model/associations.rb +0 -6
  34. data/lib/granite/form/model/attributes/attribute.rb +3 -21
  35. data/lib/granite/form/model/attributes/base.rb +5 -23
  36. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  37. data/lib/granite/form/model/attributes/reference_one.rb +1 -1
  38. data/lib/granite/form/model/attributes/reflections/attribute.rb +2 -8
  39. data/lib/granite/form/model/attributes/reflections/base/build_type_definition.rb +38 -0
  40. data/lib/granite/form/model/attributes/reflections/base.rb +20 -17
  41. data/lib/granite/form/model/attributes/reflections/collection/build_type_definition.rb +19 -0
  42. data/lib/granite/form/model/attributes/reflections/dictionary/build_type_definition.rb +19 -0
  43. data/lib/granite/form/model/attributes/reflections/dictionary.rb +0 -3
  44. data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -6
  45. data/lib/granite/form/model/attributes/reflections/represents/build_type_definition.rb +73 -0
  46. data/lib/granite/form/model/attributes/reflections/represents.rb +10 -2
  47. data/lib/granite/form/model/attributes/represents.rb +22 -37
  48. data/lib/granite/form/model/attributes.rb +10 -2
  49. data/lib/granite/form/model/persistence.rb +1 -19
  50. data/lib/granite/form/model/representation.rb +1 -0
  51. data/lib/granite/form/model/validations.rb +6 -0
  52. data/lib/granite/form/model.rb +1 -3
  53. data/lib/granite/form/types/active_support/time_zone.rb +2 -0
  54. data/lib/granite/form/types/array.rb +2 -0
  55. data/lib/granite/form/types/big_decimal.rb +2 -0
  56. data/lib/granite/form/types/boolean.rb +2 -0
  57. data/lib/granite/form/types/collection.rb +11 -0
  58. data/lib/granite/form/types/date.rb +2 -0
  59. data/lib/granite/form/types/date_time.rb +2 -0
  60. data/lib/granite/form/types/dictionary.rb +23 -0
  61. data/lib/granite/form/types/float.rb +2 -0
  62. data/lib/granite/form/types/has_subtype.rb +18 -0
  63. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +2 -0
  64. data/lib/granite/form/types/integer.rb +2 -0
  65. data/lib/granite/form/types/object.rb +28 -0
  66. data/lib/granite/form/types/string.rb +2 -0
  67. data/lib/granite/form/types/time.rb +2 -0
  68. data/lib/granite/form/types/uuid.rb +2 -0
  69. data/lib/granite/form/types.rb +3 -0
  70. data/lib/granite/form/util.rb +55 -0
  71. data/lib/granite/form/version.rb +1 -1
  72. data/lib/granite/form.rb +1 -0
  73. data/spec/granite/form/active_record/associations_spec.rb +16 -18
  74. data/spec/granite/form/model/associations/embeds_many_spec.rb +29 -305
  75. data/spec/granite/form/model/associations/embeds_one_spec.rb +27 -212
  76. data/spec/granite/form/model/associations/nested_attributes_spec.rb +0 -95
  77. data/spec/granite/form/model/associations/references_many_spec.rb +5 -326
  78. data/spec/granite/form/model/associations/references_one_spec.rb +7 -279
  79. data/spec/granite/form/model/associations/reflections/embeds_any_spec.rb +1 -2
  80. data/spec/granite/form/model/associations/reflections/embeds_many_spec.rb +18 -26
  81. data/spec/granite/form/model/associations/reflections/embeds_one_spec.rb +16 -23
  82. data/spec/granite/form/model/associations/reflections/references_many_spec.rb +1 -1
  83. data/spec/granite/form/model/associations/reflections/references_one_spec.rb +1 -22
  84. data/spec/granite/form/model/associations/validations_spec.rb +0 -3
  85. data/spec/granite/form/model/associations_spec.rb +3 -24
  86. data/spec/granite/form/model/attributes/attribute_spec.rb +0 -29
  87. data/spec/granite/form/model/attributes/reflections/attribute_spec.rb +0 -9
  88. data/spec/granite/form/model/attributes/reflections/base/build_type_definition_spec.rb +27 -0
  89. data/spec/granite/form/model/attributes/reflections/base_spec.rb +16 -10
  90. data/spec/granite/form/model/attributes/reflections/collection/build_type_definition_spec.rb +24 -0
  91. data/spec/granite/form/model/attributes/reflections/dictionary/build_type_definition_spec.rb +24 -0
  92. data/spec/granite/form/model/attributes/reflections/dictionary_spec.rb +0 -6
  93. data/spec/granite/form/model/attributes/reflections/represents/build_type_definition_spec.rb +129 -0
  94. data/spec/granite/form/model/attributes/reflections/represents_spec.rb +43 -20
  95. data/spec/granite/form/model/attributes/represents_spec.rb +78 -55
  96. data/spec/granite/form/model/attributes_spec.rb +84 -23
  97. data/spec/granite/form/model/dirty_spec.rb +1 -7
  98. data/spec/granite/form/model/persistence_spec.rb +0 -2
  99. data/spec/granite/form/model/representation_spec.rb +4 -7
  100. data/spec/granite/form/model/validations/associated_spec.rb +2 -4
  101. data/spec/granite/form/model/validations/nested_spec.rb +2 -4
  102. data/spec/granite/form/model/validations_spec.rb +28 -1
  103. data/spec/granite/form/types/collection_spec.rb +22 -0
  104. data/spec/granite/form/types/dictionary_spec.rb +32 -0
  105. data/spec/granite/form/types/has_subtype_spec.rb +20 -0
  106. data/spec/granite/form/types/object_spec.rb +50 -4
  107. data/spec/granite/form/util_spec.rb +108 -0
  108. data/spec/spec_helper.rb +0 -15
  109. data/spec/support/active_record.rb +23 -0
  110. data/spec/support/shared/nested_attribute_examples.rb +3 -21
  111. metadata +56 -51
  112. data/.github/workflows/main.yml +0 -29
  113. data/gemfiles/rails.4.2.gemfile +0 -15
  114. data/lib/granite/form/model/attributes/collection.rb +0 -19
  115. data/lib/granite/form/model/attributes/dictionary.rb +0 -28
  116. data/lib/granite/form/model/attributes/localized.rb +0 -44
  117. data/lib/granite/form/model/attributes/reflections/localized.rb +0 -45
  118. data/lib/granite/form/model/callbacks.rb +0 -72
  119. data/lib/granite/form/model/lifecycle.rb +0 -309
  120. data/lib/granite/form/model/localization.rb +0 -26
  121. data/spec/granite/form/model/attributes/collection_spec.rb +0 -72
  122. data/spec/granite/form/model/attributes/dictionary_spec.rb +0 -100
  123. data/spec/granite/form/model/attributes/localized_spec.rb +0 -103
  124. data/spec/granite/form/model/attributes/reflections/localized_spec.rb +0 -37
  125. data/spec/granite/form/model/callbacks_spec.rb +0 -337
  126. data/spec/granite/form/model/lifecycle_spec.rb +0 -356
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebac9979a786e2a3638b1dd7b21648320c779770c10f7fe63707b38a58c0164c
4
- data.tar.gz: 604dcf0abda92863e1503b0d977a53ca1de7a39823bfc8a4aabab8d9c6a27045
3
+ metadata.gz: 908865a58fa79739294c85bb506d878596a28782c02a9a2825ca1fb7fd128972
4
+ data.tar.gz: ff9b6cbed34774e3dedb0b33c28b75694b1575051adb989248ca5695254e9586
5
5
  SHA512:
6
- metadata.gz: cfc2c624834fe5d0ad4fadb903e6b9a38eed17778cea3c0cf9430437fb095436429c1a0941fd418e143805b064c1a74c0c959fb57493d96fb9602cdca625f36d
7
- data.tar.gz: 4937e3d02a378edf48daa770799206b3ecdb91ee71ed1025169e6e3bf4a25bc157c01aea9db54dcb70645728eeb0055ca42be7db23258f0b7f2744d8f7415dd9
6
+ metadata.gz: 92d055b121d30a9644521227782908a0b84ef0847c16ce60fafec47cb17c5c2988e8a071e199ed5506dd0783bba335b6a399a519c095867658e310680dd96f10
7
+ data.tar.gz: 4b4cf5f23530ad12408f498df6eed11e53438dd0a6f110ae04c10568934f041dbb64b946ee42352b7bfec83119a5827b652c8fddd62e745fa0f2835448674ce6
data/.github/CODEOWNERS CHANGED
@@ -1,2 +1 @@
1
- .github/workflows/ci.yml @toptal/coresmiths-team
2
- .github/workflows/main.yml @toptal/coresmiths-team
1
+ * @toptal/portals-experience-be
@@ -1,21 +1,39 @@
1
- name: CI
2
- on: [push, pull_request]
1
+ name: Ruby
2
+ on:
3
+ push:
4
+ branches: [ master ]
5
+ pull_request:
6
+ branches: [ master ]
3
7
  jobs:
4
8
  rspec:
5
9
  strategy:
6
10
  fail-fast: false
7
11
  matrix:
8
12
  include:
9
- - { ruby: '2.3', rails: '4.2' }
10
13
  - { ruby: '2.4', rails: '5.0' }
11
14
  - { ruby: '2.5', rails: '5.1' }
12
15
  - { ruby: '2.6', rails: '5.2' }
13
16
  - { ruby: '2.7', rails: '6.0' }
14
17
  - { ruby: '3.0', rails: '6.1' }
15
- - { ruby: '3.0', rails: '7.0' }
18
+ - { ruby: '3.1', rails: '7.0' }
16
19
  runs-on: ubuntu-latest
17
20
  env:
18
21
  BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails.${{ matrix.rails }}.gemfile
22
+ services:
23
+ postgres:
24
+ image: postgres
25
+ env:
26
+ POSTGRES_USER: granite
27
+ POSTGRES_PASSWORD: granite
28
+ # Set health checks to wait until postgres has started
29
+ options: >-
30
+ --health-cmd pg_isready
31
+ --health-interval 10s
32
+ --health-timeout 5s
33
+ --health-retries 5
34
+ ports:
35
+ # Maps tcp port 5432 on service container to the host
36
+ - 5432:5432
19
37
  steps:
20
38
  - uses: actions/checkout@v2
21
39
  - uses: ruby/setup-ruby@v1
data/.rubocop.yml CHANGED
@@ -2,7 +2,7 @@ inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
4
  DisplayCopNames: true
5
- TargetRubyVersion: 2.2.10
5
+ TargetRubyVersion: 2.4.0
6
6
 
7
7
  Lint/AmbiguousBlockAssociation:
8
8
  Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  # Offense count: 19
10
10
  Metrics/AbcSize:
11
- Max: 55
11
+ Max: 59
12
12
 
13
13
  # Offense count: 2
14
14
  # Configuration parameters: CountComments.
@@ -17,7 +17,7 @@ Metrics/ClassLength:
17
17
 
18
18
  # Offense count: 4
19
19
  Metrics/CyclomaticComplexity:
20
- Max: 13
20
+ Max: 15
21
21
 
22
22
  # Offense count: 904
23
23
  # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives.
@@ -41,7 +41,7 @@ Metrics/BlockLength:
41
41
 
42
42
  # Offense count: 4
43
43
  Metrics/PerceivedComplexity:
44
- Max: 16
44
+ Max: 18
45
45
 
46
46
  # Offense count: 75
47
47
  Style/Documentation:
data/Appraisals CHANGED
@@ -1,8 +1,7 @@
1
- %w[4.2 5.0 5.1 5.2 6.0 6.1 7.0].each do |version|
1
+ %w[5.0 5.1 5.2 6.0 6.1 7.0].each do |version|
2
2
  appraise "rails.#{version}" do
3
3
  gem 'activesupport', "~> #{version}.0"
4
4
  gem 'activemodel', "~> #{version}.0"
5
5
  gem 'activerecord', "~> #{version}.0"
6
- gem 'sqlite3', '~> 1.3.6' if version < '6.0'
7
6
  end
8
7
  end
data/CHANGELOG.md CHANGED
@@ -1,6 +1,21 @@
1
1
  # master
2
2
 
3
3
  ## Next
4
+ ## v0.4.0
5
+
6
+ * [BREAKING] Drop support for taking `model` as first argument in default/readonly/enum/normalize. This means that `default: -> (model) { model.other_field}` is no longer supported and should be replaced with `default: -> { other_field }`.
7
+ * Add support for evaluating `Symbol` for readonly/enum/normalize. If symbol is passed in one of those options, method with that name will be called when evaluating the value.
8
+ * [BREAKING] Remove `localized` attribute type.
9
+ * [BREAKING] Change the behavior of `default` and `normalize` for `collection` & `dictionary`. Instead of acting per element they will now act on the attribute as a whole.
10
+ * E.g. `collection :numbers, default: [1, 2, 3]` will not set the default for the whole collection of `numbers` rather than each element in `numbers`.
11
+
12
+ ## v0.3.0
13
+
14
+ - [BREAKING] Stop automatically saving `references_one`/`references_many` when applying changes.
15
+ - [BREAKING] Removed Lifecycle module. `embeds_many`/`embeds_one` objects can no longer be created/saved/updated/destroyed.
16
+ - [BREAKING] Due to changes above `accepts_nested_attributes_for` for `embeds_many`/`embeds_one` associations no longer marks objects for destruction but simply removes them, making changes instantly.
17
+ - Drop support for ruby 2.3 and rails 4.2
18
+
4
19
  ## v0.2.0
5
20
 
6
21
  - Replace typecasters with proper type definitions.
data/README.md CHANGED
@@ -172,9 +172,10 @@ It is possible to provide default values for attributes and they will act in the
172
172
 
173
173
  ```ruby
174
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
175
+ attribute :wday, Integer, default: ->{ today.wday } # Default evaluated in instance context
176
+ def calculate_today
177
+ Time.zone.now.today
178
+ end
178
179
  ```
179
180
 
180
181
  ##### Enums
@@ -201,8 +202,8 @@ attribute :title, String, normalizers: [->(value) { value.strip }, trim: {length
201
202
 
202
203
  ```ruby
203
204
  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
205
+ attribute :name, String, readonly: :name_changed? # Conditional with calling method
206
+ attribute :name, String, readonly: -> { subject.present? } # Conditional with lambda
206
207
  ```
207
208
 
208
209
  #### Collection
@@ -251,14 +252,6 @@ end
251
252
 
252
253
  The keys list might be restricted with the `:keys` option. Default and enum modifiers are applied on each value, normalizers are applied on the hash.
253
254
 
254
- #### Localized
255
-
256
- `localized` is similar to how `Globalize 3` attributes work.
257
-
258
- ```ruby
259
- localized :title, String
260
- ```
261
-
262
255
  #### Represents
263
256
 
264
257
  `represents` provides an easy way to expose model attributes through an interface.
@@ -384,8 +377,6 @@ Options:
384
377
  * `:validate` - true or false
385
378
  * `:default` - default value for association: reference collection or objects themselves
386
379
 
387
- #### Interacting with ActiveRecord
388
-
389
380
  ### Persistence Adapters
390
381
 
391
382
  Adapter definition syntax:
@@ -0,0 +1,14 @@
1
+ version: '3.7'
2
+ services:
3
+ postgresql:
4
+ image: 'postgres:12.4'
5
+ environment:
6
+ POSTGRES_USER: granite
7
+ POSTGRES_PASSWORD: granite
8
+ volumes:
9
+ - granite_dbdata:/var/lib/postgresql/data
10
+ ports:
11
+ - '5432:5432'
12
+
13
+ volumes:
14
+ granite_dbdata:
@@ -5,7 +5,6 @@ source "https://rubygems.org"
5
5
  gem "activesupport", "~> 5.0.0"
6
6
  gem "activemodel", "~> 5.0.0"
7
7
  gem "activerecord", "~> 5.0.0"
8
- gem "sqlite3", "~> 1.3.6"
9
8
 
10
9
  group :test do
11
10
  gem "guard"
@@ -5,7 +5,6 @@ source "https://rubygems.org"
5
5
  gem "activesupport", "~> 5.1.0"
6
6
  gem "activemodel", "~> 5.1.0"
7
7
  gem "activerecord", "~> 5.1.0"
8
- gem "sqlite3", "~> 1.3.6"
9
8
 
10
9
  group :test do
11
10
  gem "guard"
@@ -5,7 +5,6 @@ source "https://rubygems.org"
5
5
  gem "activesupport", "~> 5.2.0"
6
6
  gem "activemodel", "~> 5.2.0"
7
7
  gem "activerecord", "~> 5.2.0"
8
- gem "sqlite3", "~> 1.3.6"
9
8
 
10
9
  group :test do
11
10
  gem "guard"
data/granite-form.gemspec CHANGED
@@ -1,32 +1,32 @@
1
1
  require File.expand_path('../lib/granite/form/version', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |gem|
4
- gem.authors = ['pyromaniac']
5
- gem.email = ['kinwizard@gmail.com']
6
- gem.description = 'Making object from any hash or hash array'
7
- gem.summary = 'Working with hashes in AR style'
8
- gem.homepage = ''
4
+ gem.authors = ['Toptal Engineering']
5
+ gem.description = 'Making object from any hash or hash array'
6
+ gem.summary = 'Working with hashes in AR style'
7
+ gem.homepage = 'https://github.com/toptal/granite-form'
9
8
 
10
- gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
11
- gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
12
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
- gem.name = 'granite-form'
9
+ gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
10
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
11
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
12
+ gem.name = 'granite-form'
14
13
  gem.require_paths = ['lib']
15
- gem.version = Granite::Form::VERSION
14
+ gem.required_ruby_version = '>= 2.4.0'
15
+ gem.version = Granite::Form::VERSION
16
16
 
17
- gem.add_development_dependency 'actionpack', '>= 4.0'
18
- gem.add_development_dependency 'activerecord', '>= 4.0'
17
+ gem.add_development_dependency 'actionpack', '>= 5.0'
18
+ gem.add_development_dependency 'activerecord', '>= 5.0'
19
19
  gem.add_development_dependency 'appraisal'
20
20
  gem.add_development_dependency 'bump'
21
21
  gem.add_development_dependency 'database_cleaner'
22
+ gem.add_development_dependency 'pg'
22
23
  gem.add_development_dependency 'rake'
23
24
  gem.add_development_dependency 'rspec', '~> 3.7.0'
24
25
  gem.add_development_dependency 'rspec-its'
25
26
  gem.add_development_dependency 'rubocop', '0.52.1'
26
- gem.add_development_dependency 'sqlite3'
27
27
  gem.add_development_dependency 'uuidtools'
28
28
 
29
- gem.add_runtime_dependency 'activemodel', '>= 4.0'
30
- gem.add_runtime_dependency 'activesupport', '>= 4.0'
29
+ gem.add_runtime_dependency 'activemodel', '>= 5.0'
30
+ gem.add_runtime_dependency 'activesupport', '>= 5.0'
31
31
  gem.add_runtime_dependency 'tzinfo'
32
32
  end
@@ -45,7 +45,7 @@ module Granite
45
45
  before_save callback_name
46
46
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
47
47
  def #{callback_name}
48
- association(:#{reflection.name}).apply_changes!
48
+ association(:#{reflection.name}).sync
49
49
  end
50
50
  METHOD
51
51
  end
@@ -1,6 +1,5 @@
1
1
  require 'granite/form/model'
2
2
  require 'granite/form/model/primary'
3
- require 'granite/form/model/lifecycle'
4
3
  require 'granite/form/model/associations'
5
4
 
6
5
  module Granite
@@ -8,7 +7,7 @@ module Granite
8
7
  class Base
9
8
  include Granite::Form::Model
10
9
  include Granite::Form::Model::Primary
11
- include Granite::Form::Model::Lifecycle
10
+ include Granite::Form::Model::Persistence
12
11
  include Granite::Form::Model::Associations
13
12
  end
14
13
  end
@@ -17,21 +17,6 @@ module Granite
17
17
  end
18
18
  end
19
19
 
20
- class UnsavableObject < Error
21
- end
22
-
23
- class UndestroyableObject < Error
24
- end
25
-
26
- class ObjectNotSaved < Error
27
- end
28
-
29
- class ObjectNotDestroyed < Error
30
- end
31
-
32
- class AssociationChangesNotApplied < Error
33
- end
34
-
35
20
  class AssociationTypeMismatch < Error
36
21
  def initialize(expected, got)
37
22
  super "Expected `#{expected}` (##{expected.object_id}), but got `#{got}` (##{got.object_id})"
@@ -41,10 +41,6 @@ module Granite
41
41
  target
42
42
  end
43
43
 
44
- def apply_changes!
45
- apply_changes or raise Granite::Form::AssociationChangesNotApplied
46
- end
47
-
48
44
  def callback(name, object)
49
45
  evaluator = reflection.options[name]
50
46
  return true unless evaluator
@@ -4,7 +4,8 @@ module Granite
4
4
  module Associations
5
5
  module Collection
6
6
  class Embedded < Proxy
7
- delegate :build, :create, :create!, to: :@association
7
+ delegate :build, to: :@association
8
+ delegate :delete, to: :target
8
9
  alias_method :new, :build
9
10
  end
10
11
  end
@@ -6,7 +6,7 @@ module Granite
6
6
  class Proxy
7
7
  include Enumerable
8
8
 
9
- delegate :target, :save, :save!, :loaded?, :reload, :clear, :concat, to: :@association
9
+ delegate :target, :loaded?, :reload, :clear, :concat, to: :@association
10
10
  delegate :each, :size, :length, :first, :last, :empty?, :many?, :==, :dup, to: :target
11
11
  alias_method :<<, :concat
12
12
  alias_method :push, :concat
@@ -12,6 +12,13 @@ module Granite
12
12
  def embed_object(object)
13
13
  object.instance_variable_set(:@embedder, owner)
14
14
  end
15
+
16
+ def model_data(model)
17
+ return unless model
18
+
19
+ model.association_names.each { |assoc_name| model.association(assoc_name).sync }
20
+ model.attributes
21
+ end
15
22
  end
16
23
  end
17
24
  end
@@ -7,27 +7,6 @@ module Granite
7
7
  push_object(build_object(attributes))
8
8
  end
9
9
 
10
- def create(attributes = {})
11
- build(attributes).tap(&:save)
12
- end
13
-
14
- def create!(attributes = {})
15
- build(attributes).tap(&:save!)
16
- end
17
-
18
- def destroyed
19
- @destroyed ||= []
20
- end
21
-
22
- def apply_changes
23
- result = target.map do |object|
24
- object.destroyed? || object.marked_for_destruction? ? object.destroy : object.save
25
- end.all?
26
- @destroyed = target.select(&:destroyed?)
27
- target.delete_if(&:destroyed?)
28
- result
29
- end
30
-
31
10
  def target=(objects)
32
11
  objects.each { |object| setup_performers! object }
33
12
  loaded!
@@ -63,13 +42,14 @@ module Granite
63
42
  @target = []
64
43
  end
65
44
 
45
+ def sync
46
+ write_source(target.map { |model| model_data(model) })
47
+ end
48
+
66
49
  def clear
67
- begin
68
- transaction { target.all?(&:destroy!) }
69
- rescue Granite::Form::ObjectNotDestroyed
70
- nil
71
- end
72
- reload.empty?
50
+ target
51
+ @target = []
52
+ true
73
53
  end
74
54
 
75
55
  def reader(force_reload = false)
@@ -80,7 +60,7 @@ module Granite
80
60
  def replace(objects)
81
61
  transaction do
82
62
  clear
83
- append(objects) or raise Granite::Form::AssociationChangesNotApplied
63
+ append(objects)
84
64
  end
85
65
  end
86
66
 
@@ -101,8 +81,7 @@ module Granite
101
81
  raise AssociationTypeMismatch.new(reflection.klass, object.class) unless object && object.is_a?(reflection.klass)
102
82
  push_object object
103
83
  end
104
- result = owner.persisted? ? apply_changes : true
105
- result && target
84
+ target
106
85
  end
107
86
 
108
87
  def push_object(object)
@@ -115,34 +94,6 @@ module Granite
115
94
  embed_object(object)
116
95
  callback(:before_add, object)
117
96
 
118
- association = self
119
-
120
- object.define_create do
121
- source = association.send(:read_source)
122
- index = association.target
123
- .select { |one| one.persisted? || one.equal?(self) }
124
- .index { |one| one.equal?(self) }
125
-
126
- source.insert(index, attributes)
127
- association.send(:write_source, source)
128
- end
129
-
130
- object.define_update do
131
- source = association.send(:read_source)
132
- index = association.target.select(&:persisted?).index { |one| one.equal?(self) }
133
-
134
- source[index] = attributes
135
- association.send(:write_source, source)
136
- end
137
-
138
- object.define_destroy do
139
- source = association.send(:read_source)
140
- index = association.target.select(&:persisted?).index { |one| one.equal?(self) }
141
-
142
- source.delete_at(index) if index
143
- association.send(:write_source, source)
144
- end
145
-
146
97
  callback(:after_add, object)
147
98
  end
148
99
  end
@@ -3,33 +3,10 @@ module Granite
3
3
  module Model
4
4
  module Associations
5
5
  class EmbedsOne < EmbedsAny
6
- attr_reader :destroyed
7
-
8
6
  def build(attributes = {})
9
7
  self.target = build_object(attributes)
10
8
  end
11
9
 
12
- def create(attributes = {})
13
- build(attributes).tap(&:save)
14
- end
15
-
16
- def create!(attributes = {})
17
- build(attributes).tap(&:save!)
18
- end
19
-
20
- def apply_changes
21
- if target
22
- if target.destroyed? || target.marked_for_destruction?
23
- @destroyed = target
24
- clear
25
- else
26
- target.save
27
- end
28
- else
29
- true
30
- end
31
- end
32
-
33
10
  def target=(object)
34
11
  if object
35
12
  callback(:before_add, object)
@@ -63,9 +40,14 @@ module Granite
63
40
  object
64
41
  end
65
42
 
43
+ def sync
44
+ write_source(model_data(target))
45
+ end
46
+
66
47
  def clear
67
- target.try(:destroy)
68
- reload.nil?
48
+ target
49
+ @target = nil
50
+ true
69
51
  end
70
52
 
71
53
  def reader(force_reload = false)
@@ -79,7 +61,6 @@ module Granite
79
61
  transaction do
80
62
  clear
81
63
  self.target = object
82
- apply_changes! if owner.persisted?
83
64
  end
84
65
  else
85
66
  clear
@@ -94,16 +75,6 @@ module Granite
94
75
 
95
76
  def setup_performers!(object)
96
77
  embed_object(object)
97
- association = self
98
-
99
- object.define_save do
100
- association.send(:write_source, attributes)
101
- end
102
-
103
- object.define_destroy do
104
- association.send(:write_source, nil)
105
- true
106
- end
107
78
  end
108
79
  end
109
80
  end
@@ -74,11 +74,12 @@ module Granite
74
74
  primary_attribute_name = primary_name_for(association.reflection.klass)
75
75
  if existing_record
76
76
  primary_attribute = existing_record.attribute(primary_attribute_name)
77
- primary_attribute_value = primary_attribute.type_definition.ensure_type(attributes[primary_attribute_name]) if primary_attribute
77
+ primary_attribute_value = primary_attribute.type_definition.prepare(attributes[primary_attribute_name]) if primary_attribute
78
78
  end
79
79
 
80
80
  if existing_record && (!primary_attribute || options[:update_only] || existing_record.primary_attribute == primary_attribute_value)
81
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(object, association_name, attributes)
81
+ assign_to(existing_record, attributes) unless call_reject_if(object, association_name, attributes)
82
+ association.clear if destroy_flag?(attributes) && options[:allow_destroy]
82
83
  elsif attributes[primary_attribute_name].present?
83
84
  raise Granite::Form::ObjectNotFound.new(object, association_name, attributes[primary_attribute_name])
84
85
  elsif !reject_new_object?(object, association_name, attributes, options)
@@ -123,11 +124,12 @@ module Granite
123
124
  else
124
125
  existing_record = association.target.detect do |record|
125
126
  primary_attribute_value = record.attribute(primary_attribute_name)
126
- .type_definition.ensure_type(attributes[primary_attribute_name])
127
+ .type_definition.prepare(attributes[primary_attribute_name])
127
128
  record.primary_attribute == primary_attribute_value
128
129
  end
129
130
  if existing_record
130
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(object, association_name, attributes)
131
+ assign_to(existing_record, attributes) unless call_reject_if(object, association_name, attributes)
132
+ association.target.delete(existing_record) if destroy_flag?(attributes) && options[:allow_destroy]
131
133
  elsif association.reflection.embedded?
132
134
  unless reject_new_object?(object, association_name, attributes, options)
133
135
  association.reflection.klass.with_sanitize(false) do
@@ -156,9 +158,8 @@ module Granite
156
158
  raise Granite::Form::TooManyObjects.new(limit, attributes_collection.size)
157
159
  end
158
160
 
159
- def self.assign_to_or_mark_for_destruction(object, attributes, allow_destroy)
161
+ def self.assign_to(object, attributes)
160
162
  object.assign_attributes(attributes.except(*unassignable_keys(object)))
161
- object.mark_for_destruction if destroy_flag?(attributes) && allow_destroy
162
163
  end
163
164
 
164
165
  def self.destroy_flag?(hash)
@@ -170,7 +171,6 @@ module Granite
170
171
  end
171
172
 
172
173
  def self.call_reject_if(object, association_name, attributes)
173
- return false if destroy_flag?(attributes)
174
174
  case callback = object.nested_attributes_options[association_name][:reject_if]
175
175
  when Symbol
176
176
  method(callback).arity.zero? ? send(callback) : send(callback, attributes)
@@ -26,10 +26,6 @@ module Granite
26
26
  data_source.new(attributes)
27
27
  end
28
28
 
29
- def persist(object, raise_error: false)
30
- raise_error ? object.save! : object.save
31
- end
32
-
33
29
  def scope(owner, source)
34
30
  scope = data_source.unscoped
35
31
 
@@ -16,10 +16,6 @@ module Granite
16
16
  raise NotImplementedError, 'Should be implemented in inhereted adapter. Build new instance of data object by attributes'
17
17
  end
18
18
 
19
- def persist(_object, *)
20
- raise NotImplementedError, 'Should be implemented in inhereted adapter. Build new instance of data object by attributes'
21
- end
22
-
23
19
  def scope(_owner, _source)
24
20
  raise NotImplementedError, 'Should be implemented in inhereted adapter. Better to be Enumerable'
25
21
  end
@@ -3,38 +3,6 @@ module Granite
3
3
  module Model
4
4
  module Associations
5
5
  class ReferencesMany < ReferencesAny
6
- def build(attributes = {})
7
- append([build_object(attributes)]).last
8
- end
9
-
10
- def create(attributes = {})
11
- object = build(attributes)
12
- persist_object(object)
13
- object
14
- end
15
-
16
- def create!(attributes = {})
17
- object = build(attributes)
18
- persist_object(object, raise_error: true)
19
- object
20
- end
21
-
22
- def apply_changes
23
- target.all? do |object|
24
- if object
25
- if object.marked_for_destruction? && reflection.autosave?
26
- object.destroy
27
- elsif object.new_record? || (reflection.autosave? && object.changed?)
28
- persist_object(object)
29
- else
30
- true
31
- end
32
- else
33
- true
34
- end
35
- end
36
- end
37
-
38
6
  def target=(object)
39
7
  loaded!
40
8
  @target = object.to_a