active_type 1.10.1 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69b64cf59cc14b34fae5a6ce26172b11e4877f763e5b2aef834004e66d697f8c
4
- data.tar.gz: b446e1c689241ff6f4b96494a29e7b05347e6962322c3eda37c4cf01647a125e
3
+ metadata.gz: b0d4c00a8c504822572083cb1a051ff4582c5e0638bc4eca0f064df38f175b2e
4
+ data.tar.gz: b38c8570c9ecf88d267ae7f6e46bd1773c5658494a329b6ae00de114ce274d01
5
5
  SHA512:
6
- metadata.gz: c9a257cb54508547e77592691c58193cf287866e32f51243a09b6300b58f78fe559ed8d6410d550906753cb850220178c23d35a5727850f23f7dad10f77e8b4f
7
- data.tar.gz: ac3a81f9655a783fa15e928ee42c2d4a01ff04ff576f5709a7e862a99a2fdb2db1ca3107f50f1882ec57392f28cf23a25dfae57b0ecc3944d9da015edc7b4d3b
6
+ metadata.gz: 925a22fc4566431fc6fa4c3a22cdb86d2889e2c6066ece8a652b98fc8b2a81267c3c4221dc3429bbae7cc34bf9a14534d75b6f0e286c78b95909e581a27e840b
7
+ data.tar.gz: '097eafa44f1c006d76185d0e697bcca2072e820e019c84327432e18d4bf767925f69b988c468a82978fc5cfcfe2a21751b714dd2914adbcc94065ca70aba4a09'
data/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## Unreleased changes
6
6
 
7
+ ## 2.0.0 (2021-11-24)
8
+
9
+ * Added: Casting is prevented when the base record has changes in its already loaded associations, because those would be lost. Option `force: true` can be used to override this and still do the cast (this is not recommended).
10
+ * Added: After casting, the base record will not be usable any more. The base record and the newly created casted record share state which is unexpected. Option `force: true` can be used to override this, so the base record is still usable (this is not recommended).
11
+
7
12
  ## 1.10.1 (2021-10-19)
8
13
 
9
14
  * Removed: When casting an unsaved record, the new record will no longer have the same associations as the base record. This was introduced with version 1.8.0 and lead to issues (#147 and #148). Therefore the change was rolled back.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.0)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
data/Gemfile.4.2.pg.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.0)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.0)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.1)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
data/Gemfile.5.2.pg.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.1)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.1)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.1)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
data/Gemfile.6.1.pg.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.1)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_type (1.10.1)
4
+ active_type (2.0)
5
5
  activerecord (>= 3.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -364,8 +364,10 @@ sign_up.is_a?(SignUp) # => true
364
364
  This is basically like [`ActiveRecord#becomes`](http://apidock.com/rails/v4.2.1/ActiveRecord/Persistence/becomes), but with less bugs and more consistent behavior.
365
365
 
366
366
  **Note that `cast` is destructive.** The originally casted record (`user`) and the returned record (`sign_up`)
367
- share internal state (such as attributes). To avoid unexpected behavior, do not use the original record
368
- after casting it.
367
+ share internal state (such as attributes). To avoid unexpected behavior, the original record will raise an error when trying to change or persist it. Also, casting of a record that has changes in its loaded associations is prevented, because those changes would be lost.
368
+ If you know what you are doing and absolutely want that, you may use the option `force: true` to allow this potentially problematic behaviour, e.g. `sign_up = ActiveType.cast(user, SignUp, force: true)`
369
+
370
+
369
371
 
370
372
  You can also cast an entire relation (scope) to a relation of an `ActiveType::Record`:
371
373
 
@@ -0,0 +1,6 @@
1
+ module ActiveType
2
+
3
+ class MutationAfterCastError < StandardError
4
+ end
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ module ActiveType
2
+
3
+ class NotCastableError < StandardError
4
+ end
5
+
6
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_type/mutation_after_cast_error'
2
+
3
+ module ActiveType
4
+ module Util
5
+
6
+ # This object is used as a substitute for a record's @attributes.
7
+ # Reading from the original @attributes is still allowed, to enable
8
+ # `#inspect` and similar functions.
9
+ # But the @attributes can no longer be mutated and will raise instead.
10
+ class UnmutableAttributes
11
+
12
+ attr_reader :original_attributes
13
+
14
+ def initialize(attributes)
15
+ @original_attributes = attributes
16
+ end
17
+
18
+ def fetch_value(key)
19
+ original_attributes.fetch_value(key)
20
+ end
21
+
22
+ def key?(key)
23
+ original_attributes.key?(key)
24
+ end
25
+
26
+ def method_missing(*args)
27
+ raise MutationAfterCastError, 'Changing a record that has been used to create an ActiveType::Record could have unexpected side effects!'
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -1,11 +1,14 @@
1
+ require 'active_type/not_castable_error'
2
+ require 'active_type/util/unmutable_attributes'
3
+
1
4
  module ActiveType
2
5
  module Util
3
6
 
4
- def cast(object, klass)
7
+ def cast(object, klass, force: false)
5
8
  if object.is_a?(ActiveRecord::Relation)
6
9
  cast_relation(object, klass)
7
10
  elsif object.is_a?(ActiveRecord::Base)
8
- cast_record(object, klass)
11
+ cast_record(object, klass, force: force)
9
12
  else
10
13
  raise ArgumentError, "Don't know how to cast #{object.inspect}"
11
14
  end
@@ -17,7 +20,11 @@ module ActiveType
17
20
 
18
21
  private
19
22
 
20
- def cast_record(record, klass)
23
+ def cast_record(record, klass, force: false)
24
+ if associations_touched?(record) && !force
25
+ raise NotCastableError, 'Record has changes in its loaded associations!'
26
+ end
27
+
21
28
  # record.becomes(klass).dup
22
29
  klass.new do |casted|
23
30
  using_single_table_inheritance = using_single_table_inheritance?(klass, casted)
@@ -48,6 +55,11 @@ module ActiveType
48
55
  casted.instance_variable_set(:@errors, errors)
49
56
 
50
57
  casted[klass.inheritance_column] = klass.sti_name if using_single_table_inheritance
58
+
59
+ if !force
60
+ make_record_unusable(record)
61
+ end
62
+ casted
51
63
  end
52
64
  end
53
65
 
@@ -61,6 +73,23 @@ module ActiveType
61
73
  scoped(klass).merge(scoped(relation))
62
74
  end
63
75
 
76
+ def associations_touched?(record)
77
+ return false unless record.instance_variable_get(:@association_cache)
78
+
79
+ !!record.instance_variable_get(:@association_cache)[:associated_records]&.target&.any? do |target|
80
+ target.changed?
81
+ end
82
+ end
83
+
84
+ def make_record_unusable(record)
85
+ # Changing and saving the base record may lead to unexpected behaviour,
86
+ # since the casted record may have different changes in its autosave
87
+ # associations and will be saved to the same record in the database as
88
+ # the casted record. Therefore we prevent that.
89
+ original_attributes = record.instance_variable_get(:@attributes)
90
+ record.instance_variable_set(:@attributes, UnmutableAttributes.new(original_attributes) )
91
+ end
92
+
64
93
  extend self
65
94
 
66
95
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveType
2
- VERSION = '1.10.1'
2
+ VERSION = '2.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_type
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.1
4
+ version: '2.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Kraze
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-10-21 00:00:00.000000000 Z
12
+ date: 2021-11-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -90,18 +90,21 @@ files:
90
90
  - active_type.gemspec
91
91
  - lib/active_type.rb
92
92
  - lib/active_type/change_association.rb
93
+ - lib/active_type/mutation_after_cast_error.rb
93
94
  - lib/active_type/nested_attributes.rb
94
95
  - lib/active_type/nested_attributes/association.rb
95
96
  - lib/active_type/nested_attributes/builder.rb
96
97
  - lib/active_type/nested_attributes/nests_many_association.rb
97
98
  - lib/active_type/nested_attributes/nests_one_association.rb
98
99
  - lib/active_type/no_table.rb
100
+ - lib/active_type/not_castable_error.rb
99
101
  - lib/active_type/object.rb
100
102
  - lib/active_type/record.rb
101
103
  - lib/active_type/record_extension.rb
102
104
  - lib/active_type/record_extension/inheritance.rb
103
105
  - lib/active_type/type_caster.rb
104
106
  - lib/active_type/util.rb
107
+ - lib/active_type/util/unmutable_attributes.rb
105
108
  - lib/active_type/version.rb
106
109
  - lib/active_type/virtual_attributes.rb
107
110
  homepage: https://github.com/makandra/active_type
@@ -123,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
126
  - !ruby/object:Gem::Version
124
127
  version: '0'
125
128
  requirements: []
126
- rubygems_version: 3.1.4
129
+ rubygems_version: 3.2.15
127
130
  signing_key:
128
131
  specification_version: 4
129
132
  summary: Make any Ruby object quack like ActiveRecord