Skip to content

Conversation

@seanpdoyle
Copy link
Contributor

Define ActiveResource::Base.dump and ActiveResource::Base.load to support passing classes directly to serialize as the :coder option:

Writing to String columns

Encodes Active Resource instances into a string to be stored in the database. Decodes strings read from the database into Active Resource instances.

class User < ActiveRecord::Base
  serialize :person, coder: Person
end

class Person < ActiveResource::Base
  schema do
    attribute :name, :string
  end
end

user = User.new
user.person = Person.new name: "Matz"
user.person_before_type_cast # => "{\"name\":\"Matz\"}"

Writing string values incorporates the Base.format:

Person.format = :xml

user.person = Person.new name: "Matz"
user.person_before_type_cast # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><person><name>Matz</name></person>"

Instances are loaded as persisted when decoded from data containing a primary key value, and new records when missing a primary key value:

user.person = Person.new
user.person.persisted? # => false

user.person = Person.find(1)
user.person.persisted? # => true

Writing to JSON and JSONB columns

class User < ActiveRecord::Base
  serialize :person, coder: ActiveResource::Coder.new(Person, :serializable_hash)
end

class Person < ActiveResource::Base
  schema do
    attribute :name, :string
  end
end

user = User.new
user.person = Person.new name: "Matz"
user.person_before_type_cast # => {"name"=>"Matz"}

user.person.name # => "Matz"

The ActiveResource::Coder class

By default, #dump serializes the instance to a string value by calling ActiveResource::Base#encode:

user.person_before_type_cast # => "{\"name\":\"Matz\"}"

To customize serialization, pass the method name or a block as the second argument:

person = Person.new name: "Matz"

coder = ActiveResource::Coder.new(Person, :serializable_hash)
coder.dump(person) # => {"name"=>"Matz"}

coder = ActiveResource::Coder.new(Person) { |person| person.serializable_hash }
coder.dump(person) # => {"name"=>"Matz"}

Define `ActiveResource::Base.dump` and `ActiveResource::Base.load` to
support passing classes directly to [serialize][] as the `:coder`
option:

Writing to String columns
---

Encodes Active Resource instances into a string to be stored in the
database. Decodes strings read from the database into Active Resource
instances.

```ruby
class User < ActiveRecord::Base
  serialize :person, coder: Person
end

class Person < ActiveResource::Base
  schema do
    attribute :name, :string
  end
end

user = User.new
user.person = Person.new name: "Matz"
user.person_before_type_cast # => "{\"name\":\"Matz\"}"
```

Writing string values incorporates the Base.format:

```ruby
Person.format = :xml

user.person = Person.new name: "Matz"
user.person_before_type_cast # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><person><name>Matz</name></person>"
```

Instances are loaded as persisted when decoded from data containing a
primary key value, and new records when missing a primary key value:

```ruby
user.person = Person.new
user.person.persisted? # => false

user.person = Person.find(1)
user.person.persisted? # => true
```

Writing to JSON and JSONB columns
---

```ruby
class User < ActiveRecord::Base
  serialize :person, coder: ActiveResource::Coder.new(Person, :serializable_hash)
end

class Person < ActiveResource::Base
  schema do
    attribute :name, :string
  end
end

user = User.new
user.person = Person.new name: "Matz"
user.person_before_type_cast # => {"name"=>"Matz"}

user.person.name # => "Matz"
```

The `ActiveResource::Coder` class
===

By default, `#dump` serializes the instance to a string value by
calling `ActiveResource::Base#encode`:

```ruby
user.person_before_type_cast # => "{\"name\":\"Matz\"}"
```

To customize serialization, pass the method name or a block as the second
argument:

```ruby
person = Person.new name: "Matz"

coder = ActiveResource::Coder.new(Person, :serializable_hash)
coder.dump(person) # => {"name"=>"Matz"}

coder = ActiveResource::Coder.new(Person) { |person| person.serializable_hash }
coder.dump(person) # => {"name"=>"Matz"}
```

[serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
@rafaelfranca rafaelfranca merged commit e677239 into rails:main Sep 11, 2025
19 checks passed
@seanpdoyle seanpdoyle deleted the coders branch September 11, 2025 16:21
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 25, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 25, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 25, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 25, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 25, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`. The intent was to integrate with the Active Record
[.serialize][] method.

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

Improving the test coverage uncovered that the `Coder.load`
implementation did not support inferring the correct `@persisted` value
from payloads with root-level keys. For example, `{ "person": { "id": 1
} }` will not be inferred as persisted in the same way that `{ "id": 1
}` will.

Proposal
---

This commit adds a test-only dependency on `activerecord` and `sqlite3`
so that the serialization test coverage can include database-level reads
and writes. The suite is configured to use the in-memory SQLite adapter
so that there are no need for migrations or external database
configuration.

To resolve the `@persisted` value, call `Format.remove_root` to ensure
that the payload consists of only attributes.

[rails#420]: rails#420
[.serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

The `Coder#load` implementation does not support inferring the correct
`@persisted` value from payloads with root-level keys. For example, `{
"person": { "id": 1 } }` will not be inferred as persisted in the same
way that `{ "id": 1 }` will.

Proposal
---

To resolve the `@persisted` value, call `Format.remove_root` to ensure
that the payload consists of only attributes.

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

The `Coder#load` implementation does not support inferring the correct
`@persisted` value from payloads with root-level keys. For example, `{
"person": { "id": 1 } }` will not be inferred as persisted in the same
way that `{ "id": 1 }` will.

Proposal
---

To resolve the `@persisted` value, call `Format.remove_root` to ensure
that the payload consists of only attributes.

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`. The intent was to integrate with the Active Record
[.serialize][] method.

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

Improving the test coverage uncovered that the `Coder.load`
implementation did not support inferring the correct `@persisted` value
from payloads with root-level keys. For example, `{ "person": { "id": 1
} }` will not be inferred as persisted in the same way that `{ "id": 1
}` will.

Proposal
---

This commit adds a test-only dependency on `activerecord` and `sqlite3`
so that the serialization test coverage can include database-level reads
and writes. The suite is configured to use the in-memory SQLite adapter
so that there are no need for migrations or external database
configuration.

To resolve the `@persisted` value, call `Format.remove_root` to ensure
that the payload consists of only attributes.

[rails#420]: rails#420
[.serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

The `Coder#load` implementation does not support inferring the correct
`@persisted` value from payloads with root-level keys. For example, `{
"person": { "id": 1 } }` will not be inferred as persisted in the same
way that `{ "id": 1 }` will.

Proposal
---

To resolve the `@persisted` value, call `Format.remove_root` to ensure
that the payload consists of only attributes.

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`. The intent was to integrate with the Active Record
[.serialize][] method.

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

Improving the test coverage uncovered that the `Coder.load`
implementation did not support inferring the correct `@persisted` value
from payloads with root-level keys. For example, `{ "person": { "id": 1
} }` will not be inferred as persisted in the same way that `{ "id": 1
}` will.

Proposal
---

This commit adds a test-only dependency on `activerecord` and `sqlite3`
so that the serialization test coverage can include database-level reads
and writes. The suite is configured to use the in-memory SQLite adapter
so that there are no need for migrations or external database
configuration.

To resolve the `@persisted` value, call `Format.remove_root` to ensure
that the payload consists of only attributes.

[rails#420]: rails#420
[.serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`. The intent was to integrate with the Active Record
[.serialize][] method.

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

Proposal
---

This commit adds a test-only dependency on `activerecord` and `sqlite3`
so that the serialization test coverage can include database-level reads
and writes. The suite is configured to use the in-memory SQLite adapter
so that there are no need for migrations or external database
configuration.

[rails#420]: rails#420
[.serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`. The intent was to integrate with the Active Record
[.serialize][] method.

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

Proposal
---

This commit adds a test-only dependency on `activerecord` and `sqlite3`
so that the serialization test coverage can include database-level reads
and writes. The suite is configured to use the in-memory SQLite adapter
so that there are no need for migrations or external database
configuration.

[rails#420]: rails#420
[.serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`. The intent was to integrate with the Active Record
[.serialize][] method.

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

Proposal
---

This commit adds a test-only dependency on `activerecord` and `sqlite3`
so that the serialization test coverage can include database-level reads
and writes. The suite is configured to use the in-memory SQLite adapter
so that there are no need for migrations or external database
configuration.

[rails#420]: rails#420
[.serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`. The intent was to integrate with the Active Record
[.serialize][] method.

Problem
---

While the `Coder` class and `Serialization` module aim to comply with
Active Record, there's still potential for that compatibility to drift
of break without sufficient test coverage.

Proposal
---

This commit adds a test-only dependency on `activerecord` and `sqlite3`
so that the serialization test coverage can include database-level reads
and writes. The suite is configured to use the in-memory SQLite adapter
so that there are no need for migrations or external database
configuration.

[rails#420]: rails#420
[.serialize]: https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Sep 26, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Oct 1, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Oct 1, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Oct 2, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Oct 2, 2025
Follow-up to [rails#420][]

Background
---

The introduction of `ActiveResource::Base.coder` and the
`ActiveResource::Coder` class added support for encoding and decoding
instances of `Base`.

However, collection-focused methods like `Base.where`, `Base.all`, and
`Base.find(:all)` return `ActiveResource::Collection` instances.

Problem
---

While some collection instances are equivalent to `Array` instances,
they are capable of being parsed into `Hash` values that include
additional metadata (for example, pagination URLs, total counts, etc.).
If applications were to dump results, there is a potential loss of that
metadata.

Proposal
---

First, this commit modifies the `ActiveResource::Coder` class to accept
a boolean `:collection` keyword to treat values as
`ActiveResource::Collection` instances.

Next, extend the `ActiveResource::Collection` class to retain the
originally parsed values as a new `#original_parsed` attribute. It also
defines the `#encode` method to rely on the resource class format for
encoding.

Additionally, extend the `ActiveResource::Serialization` module to also
define a `.collection_coder` class attribute to serve as a convenience
method for consumer to pass to `.serialize …, coder: …`:

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  serialize :people, coder: Person.collection_coder
end
```

Like the instance-level coders, collection-level coders constructed with
`collection: true` also accept an encoder proc to transform the value
prior to dumping (for JSON/JSONB columns, for example):

```ruby
class Person < ActiveResource::Base
  # …
end

class Team < ApplicationRecord
  # pass a to_proc-ready method name
  serialize :people, coder: ActiveResource::Coder.new(Person, :original_parsed, collection: true)

  # pass a block
  serialize :people, coder: ActiveResource::Coder.new(Person, collection: true) do |collection|
    collection.original_parsed
  end
end
```

[rails#420]: rails#420
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants