active_type 1.10.1 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
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