active_type 2.5.1 → 2.8.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +23 -9
  4. data/lib/active_type/change_association.rb +2 -6
  5. data/lib/active_type/marshalling.rb +62 -0
  6. data/lib/active_type/nested_attributes/association.rb +5 -20
  7. data/lib/active_type/no_table.rb +66 -148
  8. data/lib/active_type/object.rb +2 -0
  9. data/lib/active_type/record.rb +2 -0
  10. data/lib/active_type/record_extension/inheritance.rb +41 -76
  11. data/lib/active_type/type_caster.rb +2 -8
  12. data/lib/active_type/version.rb +3 -1
  13. data/lib/active_type/virtual_attributes.rb +65 -40
  14. data/lib/active_type.rb +0 -7
  15. metadata +7 -41
  16. data/.github/workflows/test.yml +0 -134
  17. data/.gitignore +0 -8
  18. data/.rspec +0 -1
  19. data/.ruby-version +0 -1
  20. data/Gemfile +0 -1
  21. data/Gemfile.5.2.mysql2 +0 -9
  22. data/Gemfile.5.2.mysql2.lock +0 -59
  23. data/Gemfile.5.2.pg +0 -9
  24. data/Gemfile.5.2.pg.lock +0 -59
  25. data/Gemfile.5.2.sqlite3 +0 -9
  26. data/Gemfile.5.2.sqlite3.lock +0 -59
  27. data/Gemfile.6.0.sqlite3 +0 -9
  28. data/Gemfile.6.0.sqlite3.lock +0 -59
  29. data/Gemfile.6.1.pg +0 -9
  30. data/Gemfile.6.1.pg.lock +0 -59
  31. data/Gemfile.6.1.sqlite3 +0 -9
  32. data/Gemfile.6.1.sqlite3.lock +0 -59
  33. data/Gemfile.7.0.pg +0 -9
  34. data/Gemfile.7.0.pg.lock +0 -56
  35. data/Gemfile.7.0.sqlite3 +0 -9
  36. data/Gemfile.7.0.sqlite3.lock +0 -58
  37. data/Gemfile.7.1.pg +0 -9
  38. data/Gemfile.7.1.pg.lock +0 -68
  39. data/Gemfile.7.1.sqlite3 +0 -9
  40. data/Gemfile.7.1.sqlite3.lock +0 -70
  41. data/Gemfile.7.2.pg +0 -9
  42. data/Gemfile.7.2.pg.lock +0 -70
  43. data/Gemfile.7.2.sqlite3 +0 -9
  44. data/Gemfile.7.2.sqlite3.lock +0 -72
  45. data/Gemfile.lock +0 -1
  46. data/Rakefile +0 -57
  47. data/active_type.gemspec +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f243c433c673eb72dc622d9315f8a68a33b788a4f20cb82a5b480dc82ccb73b9
4
- data.tar.gz: f8f6b146776a25cfbcbb3acf8c9fafa46c22e68136ed23a9a004a75af0cf6e05
3
+ metadata.gz: 28986180d23a749310c496d900fc83b79ca4bfe305704f2bb93ec6a8c69cf131
4
+ data.tar.gz: 6647ff958ee1be018e654c7689fa333fa3789c1fcb972b6125a4f9e65c0a2aaa
5
5
  SHA512:
6
- metadata.gz: e4035273c367cb091908f5ad2db1949970e8e323b72629580a11e8309f48aa5ec4eb45e54a7e0c0896cf508c394ac6a0e72b51dc059a1b644751c400964f8319
7
- data.tar.gz: 9b79ccb1b3bbdc6dd0ad6c0cec7254a5ed41f18d41020a5c774a021eb898fe721fe3c7918b73253ac88689c54a1430b74f56babc1f40aaad5a705ccf81e55424
6
+ metadata.gz: 18f57bad0539e91f6725ccfd63ceb6faa32a23d96b9033d0bbc240b62f475dd385d03e395f7ba82646078e64add7bc26241848f0c9177568e29699404355c346
7
+ data.tar.gz: 13de6f297c36eb2e2dc75c719f1e8a858a783576f9aaf322e4c46654ae8ee578a8299f73aee651bd64e395ec2dd3a09a6fc02f0b4cbc2977a2ba26c7556d3592
data/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 2.8.0 (2026-01-22)
6
+ * Added: Add support `attributes_for_inspect` on ActiveType::Object.
7
+ Tanks to @makmic.
8
+
9
+ ## 2.7.1 (2025-11-03)
10
+ * Fixed: Removed some unnecessary files from the packaged gem. Gets rid of symlink warnings during installation.
11
+
12
+ ## 2.7.0 (2025-11-03)
13
+ * Removed: Support for Ruby < 3.1, Rails < 7.
14
+ * Fixed: Exception when using ActiveType::Record with Rails 8.1.
15
+
16
+ ## 2.6.5 (2025-10-16)
17
+
18
+ * Fixed: ActiveType::Object and ActiveType::Record are now serialized/deserialized correctly using Marshal.dump/Marshal.load
19
+
20
+ ## 2.6.4 (2025-09-11)
21
+
22
+ * Fixed: When using nests_many, nested updates, with preloaded records, using string ids will no longer cause additional DB queries.
23
+
24
+ ## 2.6.3 (2025-08-11)
25
+
26
+ * Fixed: Autosave on associations will triggers saves when only virtual attributes have changed.
27
+ Thanks to @RohanM.
28
+
29
+ ## 2.6.2 (2025-02-12)
30
+
31
+ * Fixed: Issue with `ActiveType::Object` when using an optional `belongs_to` on a virtual attribute.
32
+ Thanks to @kyanagi.
33
+
34
+ ## 2.6.1 (2025-01-17)
35
+
36
+ * Fixed: Issue with `ActiveType::Object` when using has_many on Rails 7.2.
37
+ Thanks to @corableus.
38
+
39
+ ## 2.6.0 (2024-12-20)
40
+
41
+ * Drop support for Rails < 5, Ruby < 2.7.
42
+
5
43
  ## 2.5.1 (2024-08-14)
6
44
 
7
45
  * Fixed: Make `ActiveType::Object` work on Rails 7.2.
data/README.md CHANGED
@@ -1,5 +1,21 @@
1
- ActiveType [![Tests](https://github.com/makandra/active_type/workflows/Tests/badge.svg)](https://github.com/makandra/active_type/actions)
2
- ==========
1
+ <p>
2
+ <a href="https://makandra.de/">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: light)" srcset="media/makandra-with-bottom-margin.light.svg">
5
+ <source media="(prefers-color-scheme: dark)" srcset="media/makandra-with-bottom-margin.dark.svg">
6
+ <img align="right" width="25%" alt="makandra" src="media/makandra-with-bottom-margin.light.svg">
7
+ </picture>
8
+ </a>
9
+
10
+ <picture>
11
+ <source media="(prefers-color-scheme: light)" srcset="media/logo.light.shapes.svg">
12
+ <source media="(prefers-color-scheme: dark)" srcset="media/logo.dark.shapes.svg">
13
+ <img width="245" alt="active_type" role="heading" aria-level="1" src="media/logo.light.shapes.svg">
14
+ </picture>
15
+ </p>
16
+
17
+ [![Tests](https://github.com/makandra/active_type/workflows/Tests/badge.svg)](https://github.com/makandra/active_type/actions)
18
+
3
19
 
4
20
  Make any Ruby object quack like ActiveRecord
5
21
  --------------------------------------------
@@ -69,7 +85,7 @@ The following behaviours are different than in vanilla Rails 5:
69
85
 
70
86
  If you need to use `ActiveRecord's` own `.attribute` method, you can still access is as `ar_attribute`:
71
87
 
72
- ```
88
+ ```rb
73
89
  class User < ApplicationRecord
74
90
  # use my custom type to serialize to the database
75
91
  ar_attribute :password, MyPasswordType.new
@@ -312,7 +328,7 @@ Supported options for `nests_many` / `nests_one` are:
312
328
  nests_many :documents, build_scope: proc { Document }, find_scope: proc { Document }
313
329
  ```
314
330
 
315
- All `...scope` options are evaled in the context of the record on first use, and cached.
331
+ All `...scope` options are evaled in the context of the record on first use, and cached.
316
332
 
317
333
  - `allow_destroy`
318
334
 
@@ -364,7 +380,7 @@ sign_up.is_a?(SignUp) # => true
364
380
  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
381
 
366
382
  **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, 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.
383
+ 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
384
  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
385
 
370
386
 
@@ -418,14 +434,14 @@ Now, if you load `credentials`, you will automatically receive records of type `
418
434
  Supported Rails versions
419
435
  ------------------------
420
436
 
421
- ActiveType is tested against ActiveRecord 5.2, 6.0, 6.1, and 7.0.
437
+ ActiveType is tested against ActiveRecord 7.1, 7.2, 8.0, 8.1.
422
438
 
423
439
  Later versions might work, earlier will not.
424
440
 
425
441
  Supported Ruby versions
426
442
  ------------------------
427
443
 
428
- ActiveType is tested against 2.5, 2.7, 3.0, and 3.1.
444
+ ActiveType is tested against Ruby 3.1+.
429
445
 
430
446
 
431
447
  Installation
@@ -462,5 +478,3 @@ Credits
462
478
  Tobias Kraze from [makandra](http://makandra.com/)
463
479
 
464
480
  Henning Koch from [makandra](http://makandra.com/)
465
-
466
-
@@ -13,12 +13,8 @@ module ActiveType
13
13
  new_scope = nil
14
14
  end
15
15
  original_options = existing_association.options
16
- if ActiveRecord::VERSION::MAJOR > 3
17
- new_scope ||= existing_association.scope
18
- public_send(existing_association.macro, association_name, new_scope, **original_options.merge(new_options))
19
- else
20
- public_send(existing_association.macro, association_name, **original_options.merge(new_options))
21
- end
16
+ new_scope ||= existing_association.scope
17
+ public_send(existing_association.macro, association_name, new_scope, **original_options.merge(new_options))
22
18
  else
23
19
  raise ArgumentError, "unrecognized association `#{association_name}`"
24
20
  end
@@ -0,0 +1,62 @@
1
+ module ActiveType
2
+ module Marshalling
3
+ # With 7.1 rails defines its own marshal_dump and marshal_load methods,
4
+ # which selectively only dump and load the record´s attributes and some more stuff, but not our @virtual_attributes.
5
+ # Whether these new methods are actually used, depends on ActiveRecord::Marshalling.format_version
6
+ # For format_version = 6.1 active record uses the default ruby implementation for dumping and loading.
7
+ # For format_version = 7.1 active record uses a custom implementation, which we need to override.
8
+ #
9
+ # format_version can also be dynamically changed during runtime, on change we need to define or undefine our marshal_dump dynamically, because:
10
+ # * We cannot check the format_version at runtime within marshal_dump or marshal_load,
11
+ # as we can´t just super to the default for the wrong version, because there is no method to super to.
12
+ # (The default implementation is a ruby internal, not a real method.)
13
+ # * We cannot override the methods at load time only when format version is 7.1,
14
+ # because format version usually gets set after our initialisation and could change at any time.
15
+ #
16
+ # Two facts about ruby also help us with that (also see https://ruby-doc.org/core-2.6.8/Marshal.html):
17
+ # * A custom marshal_load is only used, when marshal_dump is also defined. So we can keep marshal_dump always defined.
18
+ # (If either is missing, ruby will use _dump and _load)
19
+ # * If a serialized object is dumped using _dump it will be loaded using _load, never marshal_load, so a record
20
+ # serialized with format_version = 6.1 using _dump, will always load using _load, ignoring whether marshal_load is defined or not.
21
+ # This ensures objects will always be deserialized with the method they were serialized with. We don´t need to worry about that.
22
+
23
+ class << self
24
+ attr_reader :format_version
25
+
26
+ def format_version=(version)
27
+ case version
28
+ when 6.1
29
+ Methods.remove_method(:marshal_dump) if Methods.method_defined?(:marshal_dump)
30
+ when 7.1
31
+ Methods.alias_method(:marshal_dump, :_marshal_dump_7_1)
32
+ else
33
+ raise ArgumentError, "Unknown marshalling format: #{version.inspect}"
34
+ end
35
+ @format_version = version
36
+ end
37
+ end
38
+
39
+ module ActiveRecordMarshallingExtension
40
+ def format_version=(version)
41
+ ActiveType::Marshalling.format_version = version
42
+ super(version)
43
+ end
44
+ end
45
+
46
+ module Methods
47
+ def _marshal_dump_7_1
48
+ [super, @virtual_attributes]
49
+ end
50
+
51
+ def marshal_load(state)
52
+ super_attributes, @virtual_attributes = state
53
+ super(super_attributes)
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ ActiveRecord::Marshalling.singleton_class.prepend(ActiveType::Marshalling::ActiveRecordMarshallingExtension)
61
+ # Set ActiveType´s format_version to ActiveRecord´s, in case ActiveRecord uses the default value, which is set before we are loaded.
62
+ ActiveType::Marshalling.format_version = ActiveRecord::Marshalling.format_version
@@ -17,14 +17,7 @@ module ActiveType
17
17
  @reject_if = options.delete(:reject_if)
18
18
  @options = options.dup
19
19
 
20
- @index_errors = case
21
- when ActiveRecord::VERSION::MAJOR < 5
22
- @options[:index_errors]
23
- when ActiveRecord::VERSION::MAJOR < 7
24
- @options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors
25
- else
26
- @options[:index_errors] || ActiveRecord.index_nested_attribute_errors
27
- end
20
+ @index_errors = @options[:index_errors] || ActiveRecord.index_nested_attribute_errors
28
21
  end
29
22
 
30
23
  def assign_attributes(parent, attributes)
@@ -97,7 +90,7 @@ module ActiveType
97
90
  end
98
91
 
99
92
  def fetch_child(parent, id)
100
- assigned = assigned_children(parent).detect { |r| r.id == id }
93
+ assigned = assigned_children(parent).detect { |r| r.id.to_s == id.to_s }
101
94
  return assigned if assigned
102
95
 
103
96
  if child = find_scope(parent).find_by_id(id)
@@ -130,17 +123,9 @@ module ActiveType
130
123
  end
131
124
 
132
125
  def add_errors_to_parent(parent, child, index)
133
- if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("6.1")
134
- child.errors.each do |error|
135
- attribute = translate_error_attribute(error.attribute, index)
136
- parent.errors.add(attribute, error.message)
137
- end
138
- else
139
- child.errors.each do |attribute, message|
140
- attribute = translate_error_attribute(attribute, index)
141
- parent.errors.add(attribute, message)
142
- parent.errors[attribute].uniq!
143
- end
126
+ child.errors.each do |error|
127
+ attribute = translate_error_attribute(error.attribute, index)
128
+ parent.errors.add(attribute, error.message)
144
129
  end
145
130
  end
146
131
 
@@ -1,197 +1,115 @@
1
1
  module ActiveType
2
+ module NoTable
2
3
 
3
- if ActiveRecord::VERSION::MAJOR < 5
4
+ extend ActiveSupport::Concern
4
5
 
5
- module NoTable
6
-
7
- extend ActiveSupport::Concern
8
-
9
- module ClassMethods
10
-
11
- def column_types
12
- {}
13
- end
14
-
15
- def columns
16
- []
17
- end
18
-
19
- def primary_key
20
- nil
21
- end
22
-
23
- def destroy(*)
24
- new
25
- end
26
-
27
- def destroy_all(*)
28
- []
29
- end
30
-
31
- def find_by_sql(*)
32
- []
33
- end
6
+ class DummySchemaCache
34
7
 
8
+ def columns_hash(table_name)
9
+ {}
35
10
  end
36
11
 
37
- def id
38
- nil
12
+ def columns_hash?(table_name)
13
+ return false
39
14
  end
40
15
 
41
- def attribute_names
42
- []
16
+ def data_source_exists?(table_name)
17
+ false
43
18
  end
44
19
 
45
- def transaction(&block)
46
- @_current_transaction_records ||= []
47
- yield
20
+ def clear_data_source_cache!(table_name)
48
21
  end
49
22
 
50
- def destroy
51
- @destroyed = true
52
- freeze
53
- end
23
+ end
54
24
 
55
- def reload
56
- self
25
+ class DummyPool < ActiveRecord::ConnectionAdapters::NullPool
26
+ def with_pool_transaction_isolation_level(*_args)
27
+ yield
57
28
  end
29
+ end
58
30
 
31
+ class DummyConnection < ActiveRecord::ConnectionAdapters::AbstractAdapter
59
32
 
60
- private
33
+ attr_reader :schema_cache
61
34
 
62
- def create(*)
63
- true
35
+ def initialize(*)
36
+ super
37
+ @schema_cache = DummySchemaCache.new
38
+ @pool = DummyPool.new
64
39
  end
65
40
 
66
- def update(*)
67
- true
41
+ def self.quote_column_name(column_name)
42
+ column_name.to_s
68
43
  end
69
44
 
70
- if ActiveRecord::Base.private_method_defined?(:create_record)
71
- def create_record(*)
72
- true
73
- end
74
-
75
- def update_record(*)
76
- true
77
- end
78
- else
79
- def _create_record(*)
80
- @new_record = false
81
- true
82
- end
83
-
84
- def _update_record(*)
85
- true
86
- end
45
+ def pool
46
+ @pool
87
47
  end
88
48
 
89
49
  end
90
50
 
91
- else
92
-
93
- # Rails 5+
94
-
95
- module NoTable
96
-
97
- extend ActiveSupport::Concern
98
-
99
- class DummySchemaCache
100
-
101
- def columns_hash(table_name)
102
- {}
103
- end
104
-
105
- def columns_hash?(table_name)
106
- return false
107
- end
108
-
109
- def data_source_exists?(table_name)
110
- false
111
- end
112
-
113
- def clear_data_source_cache!(table_name)
114
- end
51
+ module ClassMethods
115
52
 
53
+ def connection
54
+ @connection ||= DummyConnection.new(nil)
116
55
  end
117
56
 
118
- class DummyConnection < ActiveRecord::ConnectionAdapters::AbstractAdapter
119
-
120
- attr_reader :schema_cache
121
-
122
- def initialize(*)
123
- super
124
- @schema_cache = DummySchemaCache.new
125
- end
126
-
57
+ def with_connection(**)
58
+ yield(connection)
127
59
  end
128
60
 
129
- module ClassMethods
130
-
131
- def connection
132
- @connection ||= DummyConnection.new(nil)
133
- end
134
-
135
- def with_connection(**)
136
- yield(connection)
137
- end
138
-
139
- def destroy(*)
140
- new
141
- end
142
-
143
- def destroy_all(*)
144
- []
145
- end
146
-
147
- def find_by_sql(*)
148
- []
149
- end
61
+ def destroy(*)
62
+ new
63
+ end
150
64
 
151
- def _query_by_sql(*)
152
- []
153
- end
65
+ def destroy_all(*)
66
+ []
67
+ end
154
68
 
155
- def cached_find_by(*)
156
- nil
157
- end
69
+ def find_by_sql(*)
70
+ []
71
+ end
158
72
 
159
- def schema_cache
160
- DummySchemaCache.new
161
- end
73
+ def _query_by_sql(*)
74
+ []
162
75
  end
163
76
 
164
- def destroy
165
- @destroyed = true
166
- freeze
77
+ def cached_find_by(*)
78
+ nil
167
79
  end
168
80
 
169
- def reload
170
- self
81
+ def schema_cache
82
+ DummySchemaCache.new
171
83
  end
84
+ end
172
85
 
86
+ def destroy
87
+ @destroyed = true
88
+ freeze
89
+ end
173
90
 
174
- private
91
+ def reload
92
+ self
93
+ end
175
94
 
176
- def create(*)
177
- true
178
- end
95
+ private
179
96
 
180
- def update(*)
181
- true
182
- end
97
+ def create(*)
98
+ true
99
+ end
183
100
 
184
- def _create_record(*)
185
- @new_record = false
186
- true
187
- end
101
+ def update(*)
102
+ true
103
+ end
188
104
 
189
- def _update_record(*)
190
- true
191
- end
105
+ def _create_record(*)
106
+ @new_record = false
107
+ true
108
+ end
192
109
 
110
+ def _update_record(*)
111
+ true
193
112
  end
194
113
 
195
114
  end
196
-
197
115
  end
@@ -1,6 +1,7 @@
1
1
  require 'active_type/no_table'
2
2
  require 'active_type/virtual_attributes'
3
3
  require 'active_type/nested_attributes'
4
+ require 'active_type/marshalling'
4
5
 
5
6
  module ActiveType
6
7
 
@@ -9,6 +10,7 @@ module ActiveType
9
10
  include NoTable
10
11
  include VirtualAttributes
11
12
  include NestedAttributes
13
+ include Marshalling::Methods
12
14
 
13
15
  end
14
16
 
@@ -2,6 +2,7 @@ require 'active_type/virtual_attributes'
2
2
  require 'active_type/record_extension'
3
3
  require 'active_type/nested_attributes'
4
4
  require 'active_type/change_association'
5
+ require 'active_type/marshalling'
5
6
 
6
7
  module ActiveType
7
8
 
@@ -13,6 +14,7 @@ module ActiveType
13
14
  include NestedAttributes
14
15
  include RecordExtension
15
16
  include ChangeAssociation
17
+ include Marshalling::Methods
16
18
 
17
19
  end
18
20