Skip to content

Commit

Permalink
Add array type generation via Model#to_array_type
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitryTsepelev committed Apr 30, 2019
1 parent f1bd86d commit 2a6d85d
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 56 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ class Product < ApplicationRecord
end
```

## Handling arrays

Should you store an array of models, you can use `#to_array_type` method:

```ruby
class Product < ApplicationRecord
attribute :configurations, Configuration.to_array_type
end
```

After that, your attribute will return array of `Configuration` instances.

## Validations

`StoreModel` supports all the validations shipped with `ActiveModel`. Start with defining validation for the store model:
Expand Down
42 changes: 0 additions & 42 deletions lib/store_model/json_model_type.rb

This file was deleted.

9 changes: 7 additions & 2 deletions lib/store_model/model.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "store_model/json_model_type"
require "store_model/types/json_type"
require "store_model/types/array_type"

module StoreModel
module Model
Expand All @@ -10,7 +11,11 @@ def self.included(base)

base.extend(Module.new do
def to_type
JsonModelType.new(self)
Types::JsonType.new(self)
end

def to_array_type
Types::ArrayType.new(self)
end
end)
end
Expand Down
48 changes: 48 additions & 0 deletions lib/store_model/types/array_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require "active_model"

module StoreModel
module Types
class ArrayType < ActiveModel::Type::Value
def initialize(model_klass)
@model_klass = model_klass
end

def type
:array
end

# rubocop:disable Style/RescueModifier
def cast_value(value)
case value
when String
decoded = ActiveSupport::JSON.decode(value) rescue []
initialize_models(decoded)
when Array
initialize_models(value)
end
end
# rubocop:enable Style/RescueModifier

def serialize(value)
case value
when Array
ActiveSupport::JSON.encode(value)
else
super
end
end

def changed_in_place?(raw_old_value, new_value)
cast_value(raw_old_value) != new_value
end

private

def initialize_models(values)
values.map { |attributes| @model_klass.new(attributes) }
end
end
end
end
44 changes: 44 additions & 0 deletions lib/store_model/types/json_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require "active_model"

module StoreModel
module Types
class JsonType < ActiveModel::Type::Value
def initialize(model_klass)
@model_klass = model_klass
end

def type
:json
end

# rubocop:disable Style/RescueModifier
def cast_value(value)
case value
when String
decoded = ActiveSupport::JSON.decode(value) rescue nil
@model_klass.new(decoded) unless decoded.nil?
when Hash
@model_klass.new(value)
when @model_klass
value
end
end
# rubocop:enable Style/RescueModifier

def serialize(value)
case value
when Hash, @model_klass
ActiveSupport::JSON.encode(value)
else
super
end
end

def changed_in_place?(raw_old_value, new_value)
cast_value(raw_old_value) != new_value
end
end
end
end
16 changes: 15 additions & 1 deletion spec/store_model/model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
end
end

describe ".as_type" do
describe ".to_type" do
subject { custom_product_class.new }

let(:custom_product_class) do
Expand All @@ -82,4 +82,18 @@
expect(subject.configuration).to be_a_kind_of(Configuration)
end
end

describe ".to_array_type" do
subject { custom_product_class.new }

let(:custom_product_class) do
build_custom_product_class do
attribute :configuration, Configuration.to_array_type
end
end

it "configures type using field name" do
expect(subject.configuration).to be_a_kind_of(Array)
end
end
end
80 changes: 80 additions & 0 deletions spec/store_model/types/array_type_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe StoreModel::Types::ArrayType do
let(:type) { described_class.new(Configuration) }

let(:attributes_array) do
[
{
color: "red",
disabled_at: Time.new(2019, 2, 22, 12, 30)
},
{
color: "green",
disabled_at: Time.new(2019, 3, 12, 8, 10)
}
]
end

describe "#type" do
subject { type.type }

it { is_expected.to eq(:array) }
end

describe "#changed_in_place?" do
let(:configurations) do
attributes_array.map { |attributes| Configuration.new(attributes) }
end

it "marks object as changed" do
expect(type.changed_in_place?([], configurations)).to be_truthy
end
end

describe "#cast_value" do
shared_examples "cast examples" do
subject { type.cast_value(value) }

it { is_expected.to be_a(Array) }
it "assigns attributes" do
subject.zip(attributes_array).each do |config, config_attributes|
expect(config).to have_attributes(config_attributes)
end
end
end

context "when String is passed" do
let(:value) { ActiveSupport::JSON.encode(attributes_array) }
include_examples "cast examples"
end

context "when Array is passed" do
let(:value) { attributes_array }
include_examples "cast examples"
end
end

describe "#serialize" do
shared_examples "serialize examples" do
subject { type.serialize(value) }

it { is_expected.to be_a(String) }
it "is equal to attributes" do
expect(subject).to eq(ActiveSupport::JSON.encode(attributes_array))
end
end

context "when Array is passed" do
let(:value) { attributes_array }
include_examples "serialize examples"
end

context "when String is passed" do
let(:value) { ActiveSupport::JSON.encode(attributes_array) }
include_examples "serialize examples"
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@

require "spec_helper"

RSpec.describe StoreModel::JsonModelType do
RSpec.describe StoreModel::Types::JsonType do
let(:type) { described_class.new(Configuration) }

let(:attributes) do
{
color: "red",
disabled_at: Time.new(2019, 2, 22, 12, 30)
}
end

let(:type) { StoreModel::JsonModelType.new(Configuration) }

describe "#type" do
subject { type.type }

it { is_expected.to eq(:json) }
end

describe "#changed_in_place?" do
it "marks object as changed" do
expect(type.changed_in_place?({}, Configuration.new(attributes))).to be_truthy
end
end

describe "#cast_value" do
shared_examples "cast examples" do
subject { type.cast_value(value) }
Expand All @@ -27,7 +33,7 @@
end

context "when String is passed" do
let(:value) { attributes.as_json }
let(:value) { ActiveSupport::JSON.encode(attributes) }
include_examples "cast examples"
end

Expand Down Expand Up @@ -56,7 +62,7 @@
end

context "when String is passed" do
let(:value) { attributes.as_json }
let(:value) { ActiveSupport::JSON.encode(attributes) }
include_examples "serialize examples"
end

Expand All @@ -65,10 +71,4 @@
include_examples "serialize examples"
end
end

describe "#changed_in_place?" do
it "marks object as changed" do
expect(type.changed_in_place?({}, Configuration.new(color: "green"))).to be_truthy
end
end
end

0 comments on commit 2a6d85d

Please sign in to comment.