granite-form 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) 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 +14 -0
  8. data/README.md +0 -2
  9. data/Rakefile +4 -0
  10. data/docker-compose.yml +14 -0
  11. data/gemfiles/rails.5.0.gemfile +0 -1
  12. data/gemfiles/rails.5.1.gemfile +0 -1
  13. data/gemfiles/rails.5.2.gemfile +0 -1
  14. data/granite-form.gemspec +16 -15
  15. data/lib/granite/form/active_record/associations.rb +1 -1
  16. data/lib/granite/form/base.rb +1 -2
  17. data/lib/granite/form/config.rb +10 -10
  18. data/lib/granite/form/errors.rb +0 -15
  19. data/lib/granite/form/model/associations/base.rb +0 -4
  20. data/lib/granite/form/model/associations/collection/embedded.rb +2 -1
  21. data/lib/granite/form/model/associations/collection/proxy.rb +1 -1
  22. data/lib/granite/form/model/associations/embeds_any.rb +7 -0
  23. data/lib/granite/form/model/associations/embeds_many.rb +9 -58
  24. data/lib/granite/form/model/associations/embeds_one.rb +7 -36
  25. data/lib/granite/form/model/associations/nested_attributes.rb +8 -8
  26. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +0 -4
  27. data/lib/granite/form/model/associations/persistence_adapters/base.rb +0 -4
  28. data/lib/granite/form/model/associations/references_many.rb +0 -32
  29. data/lib/granite/form/model/associations/references_one.rb +0 -28
  30. data/lib/granite/form/model/associations/reflections/embeds_any.rb +1 -1
  31. data/lib/granite/form/model/associations/reflections/references_any.rb +0 -4
  32. data/lib/granite/form/model/associations/reflections/references_many.rb +3 -1
  33. data/lib/granite/form/model/associations/reflections/references_one.rb +3 -3
  34. data/lib/granite/form/model/associations/reflections/singular.rb +0 -8
  35. data/lib/granite/form/model/associations.rb +0 -6
  36. data/lib/granite/form/model/attributes/attribute.rb +1 -1
  37. data/lib/granite/form/model/attributes/base.rb +14 -17
  38. data/lib/granite/form/model/attributes/collection.rb +1 -1
  39. data/lib/granite/form/model/attributes/dictionary.rb +1 -1
  40. data/lib/granite/form/model/attributes/localized.rb +1 -1
  41. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  42. data/lib/granite/form/model/attributes/reference_one.rb +1 -9
  43. data/lib/granite/form/model/attributes/reflections/attribute.rb +0 -6
  44. data/lib/granite/form/model/attributes/reflections/base.rb +9 -12
  45. data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -10
  46. data/lib/granite/form/model/persistence.rb +1 -19
  47. data/lib/granite/form/model/validations/nested.rb +1 -1
  48. data/lib/granite/form/model.rb +0 -2
  49. data/lib/granite/form/types/active_support/time_zone.rb +22 -0
  50. data/lib/granite/form/types/array.rb +17 -0
  51. data/lib/granite/form/types/big_decimal.rb +15 -0
  52. data/lib/granite/form/types/boolean.rb +38 -0
  53. data/lib/granite/form/types/date.rb +15 -0
  54. data/lib/granite/form/types/date_time.rb +15 -0
  55. data/lib/granite/form/types/float.rb +15 -0
  56. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +18 -0
  57. data/lib/granite/form/types/integer.rb +13 -0
  58. data/lib/granite/form/types/object.rb +30 -0
  59. data/lib/granite/form/types/string.rb +13 -0
  60. data/lib/granite/form/types/time.rb +15 -0
  61. data/lib/granite/form/types/uuid.rb +22 -0
  62. data/lib/granite/form/types.rb +15 -0
  63. data/lib/granite/form/version.rb +1 -1
  64. data/lib/granite/form.rb +19 -118
  65. data/spec/{lib/granite → granite}/form/active_record/associations_spec.rb +16 -18
  66. data/spec/{lib/granite → granite}/form/active_record/nested_attributes_spec.rb +0 -1
  67. data/spec/{lib/granite → granite}/form/config_spec.rb +22 -10
  68. data/spec/granite/form/extensions_spec.rb +12 -0
  69. data/spec/{lib/granite → granite}/form/model/associations/embeds_many_spec.rb +29 -305
  70. data/spec/{lib/granite → granite}/form/model/associations/embeds_one_spec.rb +27 -212
  71. data/spec/granite/form/model/associations/nested_attributes_spec.rb +23 -0
  72. data/spec/{lib/granite → granite}/form/model/associations/persistence_adapters/active_record_spec.rb +0 -0
  73. data/spec/granite/form/model/associations/references_many_spec.rb +251 -0
  74. data/spec/granite/form/model/associations/references_one_spec.rb +173 -0
  75. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_any_spec.rb +1 -2
  76. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_many_spec.rb +18 -26
  77. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_one_spec.rb +16 -23
  78. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_many_spec.rb +1 -1
  79. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_one_spec.rb +1 -22
  80. data/spec/{lib/granite → granite}/form/model/associations/validations_spec.rb +0 -3
  81. data/spec/{lib/granite → granite}/form/model/associations_spec.rb +3 -24
  82. data/spec/{lib/granite → granite}/form/model/attributes/attribute_spec.rb +4 -46
  83. data/spec/{lib/granite → granite}/form/model/attributes/base_spec.rb +11 -2
  84. data/spec/{lib/granite → granite}/form/model/attributes/collection_spec.rb +0 -0
  85. data/spec/{lib/granite → granite}/form/model/attributes/dictionary_spec.rb +0 -0
  86. data/spec/{lib/granite → granite}/form/model/attributes/localized_spec.rb +1 -1
  87. data/spec/{lib/granite → granite}/form/model/attributes/reflections/attribute_spec.rb +0 -12
  88. data/spec/{lib/granite → granite}/form/model/attributes/reflections/base_spec.rb +1 -1
  89. data/spec/{lib/granite → granite}/form/model/attributes/reflections/collection_spec.rb +0 -0
  90. data/spec/{lib/granite → granite}/form/model/attributes/reflections/dictionary_spec.rb +0 -0
  91. data/spec/{lib/granite → granite}/form/model/attributes/reflections/localized_spec.rb +0 -0
  92. data/spec/{lib/granite → granite}/form/model/attributes/reflections/represents_spec.rb +0 -0
  93. data/spec/{lib/granite → granite}/form/model/attributes/represents_spec.rb +0 -0
  94. data/spec/{lib/granite → granite}/form/model/attributes_spec.rb +0 -0
  95. data/spec/{lib/granite → granite}/form/model/conventions_spec.rb +0 -0
  96. data/spec/{lib/granite → granite}/form/model/dirty_spec.rb +1 -1
  97. data/spec/{lib/granite → granite}/form/model/persistence_spec.rb +0 -2
  98. data/spec/{lib/granite → granite}/form/model/primary_spec.rb +1 -1
  99. data/spec/{lib/granite → granite}/form/model/representation_spec.rb +0 -0
  100. data/spec/{lib/granite → granite}/form/model/scopes_spec.rb +0 -0
  101. data/spec/{lib/granite → granite}/form/model/validations/associated_spec.rb +2 -4
  102. data/spec/{lib/granite → granite}/form/model/validations/nested_spec.rb +57 -15
  103. data/spec/{lib/granite → granite}/form/model/validations_spec.rb +0 -0
  104. data/spec/{lib/granite → granite}/form/model_spec.rb +0 -0
  105. data/spec/granite/form/types/active_support/time_zone_spec.rb +24 -0
  106. data/spec/granite/form/types/array_spec.rb +13 -0
  107. data/spec/granite/form/types/big_decimal_spec.rb +19 -0
  108. data/spec/granite/form/types/boolean_spec.rb +21 -0
  109. data/spec/granite/form/types/date_spec.rb +18 -0
  110. data/spec/granite/form/types/date_time_spec.rb +20 -0
  111. data/spec/granite/form/types/float_spec.rb +19 -0
  112. data/spec/granite/form/types/hash_with_action_controller_parameters_spec.rb +22 -0
  113. data/spec/granite/form/types/integer_spec.rb +18 -0
  114. data/spec/granite/form/types/object_spec.rb +40 -0
  115. data/spec/granite/form/types/string_spec.rb +13 -0
  116. data/spec/granite/form/types/time_spec.rb +31 -0
  117. data/spec/granite/form/types/uuid_spec.rb +21 -0
  118. data/spec/{lib/granite → granite}/form_spec.rb +0 -0
  119. data/spec/spec_helper.rb +0 -15
  120. data/spec/support/active_record.rb +20 -0
  121. data/spec/{shared → support/shared}/nested_attribute_examples.rb +3 -21
  122. data/spec/support/shared/type_examples.rb +7 -0
  123. metadata +173 -123
  124. data/.github/workflows/main.yml +0 -29
  125. data/gemfiles/rails.4.2.gemfile +0 -15
  126. data/lib/granite/form/model/callbacks.rb +0 -72
  127. data/lib/granite/form/model/lifecycle.rb +0 -309
  128. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +0 -119
  129. data/spec/lib/granite/form/model/associations/references_many_spec.rb +0 -572
  130. data/spec/lib/granite/form/model/associations/references_one_spec.rb +0 -445
  131. data/spec/lib/granite/form/model/callbacks_spec.rb +0 -337
  132. data/spec/lib/granite/form/model/lifecycle_spec.rb +0 -356
  133. data/spec/lib/granite/form/model/typecasting_spec.rb +0 -193
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df27378689a1bdcdc95593c32c65046ecaab452cc3303839979ace6f1a12ef0f
4
- data.tar.gz: 41b0ee8c8b22f1cf992e7b46c91528b4596ad6b362930c5299b6fde58a9bd2b8
3
+ metadata.gz: 848ee19f938a86a22a506585a7a4960bbb192b977fdc61173b05057c0381ec79
4
+ data.tar.gz: 9c024906d4e6c7c8a93db7bcfe005dbffa956e4239a0078d642b3604c4226c0a
5
5
  SHA512:
6
- metadata.gz: aef5479a5c58afc85c8e68635fbd8382039c69b8235beaeda74d5caf320540c2fd8aac330f70f4c031abfd37d6a214c3acab0086c9c0a1d3843aedd7aab5a1db
7
- data.tar.gz: 5fcc7f498c8685167096ec9d835c6cc80efec7915b17c32aa27f8e912776d2ef25e215ed24c076e7c8e4cb32790eba060d8a7aac827ff1c2cd8d93819b7ae7e2
6
+ metadata.gz: e35f2d05bd0adaabdb00df1799b9997d6f01818f00271ee24cc628b9e5edf859ab4f15e1ee3e56f697485d89a2f1c05ebeff42816f53b4293c82854e3f4e247e
7
+ data.tar.gz: df23355e592e78a1d998ace8a8010e188360ce99173fe69623bf3b03da18286f3476d509620b89f91fcaa5df299a20b32906cb3c0ae95353b75eb2c694afe409
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/coresmiths-team
@@ -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: 54
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,5 +1,19 @@
1
1
  # master
2
2
 
3
+ ## Next
4
+ ## v0.3.0
5
+
6
+ - [BREAKING] Stop automatically saving `references_one`/`references_many` when applying changes.
7
+ - [BREAKING] Removed Lifecycle module. `embeds_many`/`embeds_one` objects can no longer be created/saved/updated/destroyed.
8
+ - [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.
9
+ - Drop support for ruby 2.3 and rails 4.2
10
+
11
+ ## v0.2.0
12
+
13
+ - Replace typecasters with proper type definitions.
14
+ - Instead of `typecaster(type) { |value, _| ... }` you'll have to use `typecaster(type) { |value| ... }`.
15
+ - Consequently you can access type definition in typecaster, e.g. `typecaster('Object') { |value| value if value.class < type }`, here `type` comes from type definition.
16
+
3
17
  ## v0.1.1
4
18
 
5
19
  - Fixed represented error message copying when represented model uses symbols for `message`.
data/README.md CHANGED
@@ -384,8 +384,6 @@ Options:
384
384
  * `:validate` - true or false
385
385
  * `:default` - default value for association: reference collection or objects themselves
386
386
 
387
- #### Interacting with ActiveRecord
388
-
389
387
  ### Persistence Adapters
390
388
 
391
389
  Adapter definition syntax:
data/Rakefile CHANGED
@@ -1,6 +1,10 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'bump/tasks'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
6
7
  task default: :spec
8
+
9
+ Bump.tag_by_default = true
10
+ Bump.changelog = true
@@ -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,31 +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
+ gem.add_development_dependency 'bump'
20
21
  gem.add_development_dependency 'database_cleaner'
22
+ gem.add_development_dependency 'pg'
21
23
  gem.add_development_dependency 'rake'
22
24
  gem.add_development_dependency 'rspec', '~> 3.7.0'
23
25
  gem.add_development_dependency 'rspec-its'
24
26
  gem.add_development_dependency 'rubocop', '0.52.1'
25
- gem.add_development_dependency 'sqlite3'
26
27
  gem.add_development_dependency 'uuidtools'
27
28
 
28
- gem.add_runtime_dependency 'activemodel', '>= 4.0'
29
- gem.add_runtime_dependency 'activesupport', '>= 4.0'
29
+ gem.add_runtime_dependency 'activemodel', '>= 5.0'
30
+ gem.add_runtime_dependency 'activesupport', '>= 5.0'
30
31
  gem.add_runtime_dependency 'tzinfo'
31
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
@@ -4,7 +4,7 @@ module Granite
4
4
  include Singleton
5
5
 
6
6
  attr_accessor :include_root_in_json, :i18n_scope, :logger, :primary_attribute, :base_class, :base_concern,
7
- :_normalizers, :_typecasters
7
+ :_normalizers, :types
8
8
 
9
9
  def self.delegated
10
10
  public_instance_methods - superclass.public_instance_methods - Singleton.public_instance_methods
@@ -16,7 +16,7 @@ module Granite
16
16
  @logger = Logger.new(STDERR)
17
17
  @primary_attribute = :id
18
18
  @_normalizers = {}
19
- @_typecasters = {}
19
+ @types = {}
20
20
  end
21
21
 
22
22
  def normalizer(name, &block)
@@ -27,16 +27,16 @@ module Granite
27
27
  end
28
28
  end
29
29
 
30
- def typecaster(*classes, &block)
31
- classes = classes.flatten
32
- if block
33
- _typecasters[classes.first.to_s.camelize] = block
34
- else
35
- _typecasters[classes.detect do |klass|
36
- _typecasters[klass.to_s.camelize]
37
- end.to_s.camelize] or raise TypecasterMissing, classes
30
+ def typecaster(class_name, &block)
31
+ types[class_name.to_s.camelize] = Class.new(Types::Object) do
32
+ define_method(:typecast, &block)
38
33
  end
39
34
  end
35
+
36
+ def type_for(klass)
37
+ key = klass.ancestors.grep(Class).map(&:to_s).find(&types) or raise TypecasterMissing, klass
38
+ types.fetch(key)
39
+ end
40
40
  end
41
41
  end
42
42
  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.typecast(attributes[primary_attribute_name]) if primary_attribute
77
+ primary_attribute_value = primary_attribute.type_definition.ensure_type(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
- .typecast(attributes[primary_attribute_name])
127
+ .type_definition.ensure_type(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,13 +158,12 @@ 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)
165
- Granite::Form.typecaster(Boolean).call(hash[DESTROY_ATTRIBUTE])
166
+ Types::Boolean.typecast(hash[DESTROY_ATTRIBUTE])
166
167
  end
167
168
 
168
169
  def self.reject_new_object?(object, association_name, attributes, options)
@@ -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