Last active
August 2, 2018 07:41
-
-
Save timnew/3091503e51df34d1b2339f32a8a04a6c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module JsonConvertible | |
extend ActiveSupport::Concern | |
included do | |
unless instance_methods(true).include?(:members) # Support Struct | |
def self.members(*attributes) | |
@members = [] if @members.nil? | |
unless attributes.empty? | |
attr_accessor(*attributes) | |
@members.push(*attributes) | |
end | |
@members | |
end | |
def members | |
self.class.members | |
end | |
end | |
trim_nil_member(false) | |
end | |
class_methods do | |
# TODO: Maybe should rename to from_h | |
def from_json(hash, &block) | |
new.load_json(hash, &block) | |
end | |
def trim_nil_member(value = true) | |
if value | |
# TODO: Maybe should rename to to_h | |
alias_method :to_json, :to_json_without_nil_member | |
else | |
# TODO: Maybe should rename to to_h | |
alias_method :to_json, :to_json_with_nil_member | |
end | |
end | |
end | |
# TODO: Maybe should rename to load_h or load_values | |
def load_json(hash) | |
hash.each do |k, v| | |
key = :"#{k.to_s.underscore}=" | |
send(key, v) if respond_to?(key) | |
end | |
yield(self) if block_given? | |
self | |
end | |
# TODO: Maybe should rename to to_h | |
def to_json_with_nil_member | |
members.each_with_object({}) do |member, json| | |
json[member.to_s.camelize(:lower).to_sym] = send(member) | |
end | |
end | |
# TODO: Maybe should rename to to_h_compacted | |
def to_json_without_nil_member | |
to_json_with_nil_member.try(:compact) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
RSpec.describe JsonConvertible do | |
let(:extended_json) do | |
{ | |
fieldNotExist: false, | |
coolField: 'cool_field', | |
anotherField: 'another_field', | |
awesomeField: 'awesome_field' | |
} | |
end | |
let(:json) do | |
{ | |
coolField: 'cool_field', | |
anotherField: 'another_field', | |
awesomeField: 'awesome_field' | |
} | |
end | |
context 'used in Struct' do | |
TestStruct = Struct.new(:cool_field, :another_field, :awesome_field) do | |
include JsonConvertible | |
def not_a_field | |
'not_a_field' | |
end | |
end | |
describe 'extended' do | |
describe 'class behaviours' do | |
subject { TestStruct } | |
it { is_expected.to respond_to(:from_json) } | |
it { is_expected.to respond_to(:members) } | |
its(:members) { is_expected.to eq [:cool_field, :another_field, :awesome_field] } | |
end | |
describe 'instance_behaviours' do | |
subject { TestStruct.new } | |
it { is_expected.to respond_to(:to_json) } | |
it { is_expected.to respond_to(:members) } | |
it { is_expected.to respond_to(:load_json) } | |
its(:members) { is_expected.to eq [:cool_field, :another_field, :awesome_field] } | |
end | |
end | |
describe '.from_json' do | |
subject { TestStruct.from_json(json) } | |
it 'should be instantiated from json' do | |
is_expected.to have_attributes cool_field: 'cool_field', | |
another_field: 'another_field', | |
awesome_field: 'awesome_field' | |
end | |
it 'should serialised to json' do | |
expect(subject.to_json).to eq json | |
end | |
it 'should not complain for unknown field' do | |
TestStruct.from_json(extended_json) | |
end | |
it 'shoudl invoke block if given' do | |
expect { |b| TestStruct.from_json(json, &b) }.to yield_with_args | |
end | |
end | |
describe '#load_json' do | |
subject { TestStruct.new.load_json(json) } | |
it 'should be instantiated from json' do | |
is_expected.to have_attributes cool_field: 'cool_field', | |
another_field: 'another_field', | |
awesome_field: 'awesome_field' | |
end | |
it 'should serialised to json' do | |
expect(subject.to_json).to eq json | |
end | |
it 'should not complain for unknown field' do | |
TestStruct.new.load_json(extended_json) | |
end | |
it 'shoudl invoke block if given' do | |
model = TestStruct.new | |
expect { |b| model.load_json(json, &b) }.to yield_with_args(model) | |
end | |
end | |
end | |
context 'used in Class' do | |
class TestModel | |
include JsonConvertible | |
attr_accessor :cool_field | |
members :cool_field, :another_field, :awesome_field | |
attr_accessor :another_field | |
def not_a_field | |
'not_a_field' | |
end | |
end | |
describe 'extended' do | |
describe 'class behaviours' do | |
subject { TestModel } | |
it { is_expected.to respond_to(:from_json) } | |
it { is_expected.to respond_to(:members) } | |
its(:members) { is_expected.to eq [:cool_field, :another_field, :awesome_field] } | |
end | |
describe 'instance_behaviours' do | |
subject { TestModel.new } | |
it { is_expected.to respond_to(:to_json) } | |
it { is_expected.to respond_to(:members) } | |
it { is_expected.to respond_to(:load_json) } | |
its(:members) { is_expected.to eq [:cool_field, :another_field, :awesome_field] } | |
end | |
end | |
describe '.from_json' do | |
subject { TestModel.from_json(json) } | |
it 'should be instantiated from json' do | |
is_expected.to have_attributes cool_field: 'cool_field', | |
another_field: 'another_field', | |
awesome_field: 'awesome_field' | |
end | |
it 'should serialised to json' do | |
expect(subject.to_json).to eq json | |
end | |
it 'should not complain for unknown field' do | |
TestModel.from_json(extended_json) | |
end | |
it 'shoudl invoke block if given' do | |
expect { |b| TestModel.from_json(json, &b) }.to yield_with_args | |
end | |
end | |
end | |
describe '#load_json' do | |
subject { TestModel.new.load_json(json) } | |
it 'should be instantiated from json' do | |
is_expected.to have_attributes cool_field: 'cool_field', | |
another_field: 'another_field', | |
awesome_field: 'awesome_field' | |
end | |
it 'should serialised to json' do | |
expect(subject.to_json).to eq json | |
end | |
it 'should not complain for unknown field' do | |
TestModel.new.load_json(extended_json) | |
end | |
it 'shoudl invoke block if given' do | |
model = TestModel.new | |
expect { |b| model.load_json(json, &b) }.to yield_with_args(model) | |
end | |
end | |
describe 'nil members handling' do | |
subject { model_class.from_json(field: 'value', empty: nil) } | |
context 'preserve nil members' do | |
let(:model_class) do | |
Struct.new(:field, :empty) do | |
include JsonConvertible | |
end | |
end | |
it { is_expected.to respond_to :to_json_with_nil_member } | |
it { is_expected.to respond_to :to_json_without_nil_member } | |
it { is_expected.to respond_to :to_json } | |
its(:to_json) { is_expected.to eq(field: 'value', empty: nil) } | |
end | |
context 'trim nil members' do | |
let(:model_class) do | |
Struct.new(:field, :empty) do | |
include JsonConvertible | |
trim_nil_member | |
end | |
end | |
it { is_expected.to respond_to :to_json_with_nil_member } | |
it { is_expected.to respond_to :to_json_without_nil_member } | |
it { is_expected.to respond_to :to_json } | |
its(:to_json) { is_expected.to eq(field: 'value') } | |
end | |
context 'reserve original #to_json' do | |
let(:model_class) do | |
Struct.new(:field, :empty) do | |
include JsonConvertible | |
def to_json | |
{ | |
custom: true | |
} | |
end | |
end | |
end | |
it { is_expected.to respond_to :to_json_with_nil_member } | |
it { is_expected.to respond_to :to_json_without_nil_member } | |
it { is_expected.to respond_to :to_json } | |
its(:to_json) { is_expected.to eq(custom: true) } | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment