Skip to content

Commit d301e30

Browse files
authored
Merge pull request #228 from Fivell/type_casting_refactoring
allow schema types to be pluggable
2 parents 72c1a04 + 724875d commit d301e30

File tree

3 files changed

+150
-21
lines changed

3 files changed

+150
-21
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,36 @@ class MyApi::Base < JsonApiClient::Resource
458458
end
459459
```
460460
461+
462+
### Type Casting
463+
464+
You can define your own types and its casting mechanism for schema.
465+
466+
```ruby
467+
require 'money'
468+
class MyMoneyCaster
469+
def self.cast(value, default)
470+
begin
471+
Money.new(1000, "USD")
472+
rescue ArgumentError
473+
default
474+
end
475+
end
476+
end
477+
478+
JsonApiClient::Schema.register money: MyMoneyCaster
479+
480+
```
481+
and finally
482+
483+
```ruby
484+
class Order < JsonApiClient::Resource
485+
property :total_amount, type: :money
486+
end
487+
488+
```
489+
490+
461491
## Changelog
462492
463493
See [changelog](https://github.com/chingor13/json_api_client/blob/master/CHANGELOG.md)

lib/json_api_client/schema.rb

Lines changed: 102 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,108 @@
11
require 'bigdecimal'
22
module JsonApiClient
33
class Schema
4-
Property = Struct.new(:name, :type, :default) do
5-
def cast(value)
6-
return nil if value.nil?
7-
return value if type.nil?
4+
module Types
85

9-
case type.to_sym
10-
when :int, :integer
6+
class Integer
7+
def self.cast(value, _)
118
value.to_i
12-
when :string
9+
end
10+
end
11+
12+
class String
13+
def self.cast(value, _)
1314
value.to_s
14-
when :float
15+
end
16+
end
17+
18+
class Float
19+
def self.cast(value, _)
1520
value.to_f
16-
when :time
17-
value.is_a?(Time) ? value : Time.parse(value.to_s)
18-
when :decimal
21+
end
22+
end
23+
24+
class Time
25+
def self.cast(value, _)
26+
value.is_a?(::Time) ? value : ::Time.parse(value)
27+
end
28+
end
29+
30+
class Decimal
31+
def self.cast(value, _)
1932
BigDecimal.new(value)
20-
when :boolean
33+
end
34+
end
35+
36+
class Boolean
37+
def self.cast(value, default)
2138
case value
22-
when "false", "0", 0, false
23-
false
24-
when "true", "1", 1, true
25-
true
26-
else
27-
# if it's unknown, use the default value
28-
default
39+
when "false", "0", 0, false
40+
false
41+
when "true", "1", 1, true
42+
true
43+
else
44+
# if it's unknown, use the default value
45+
default
2946
end
30-
else
31-
value
3247
end
3348
end
49+
50+
end
51+
52+
class TypeFactory
53+
@@types = {}
54+
# Register a new type key or keys with appropriate classes
55+
#
56+
# eg:
57+
#
58+
# require 'money'
59+
#
60+
# class MyMoneyCaster
61+
# def self.cast(value, default)
62+
# begin
63+
# Money.new(1000, "USD")
64+
# rescue ArgumentError
65+
# default
66+
# end
67+
# end
68+
# end
69+
#
70+
# JsonApiClient::Schema::Types.register money: MyMoneyCaster
71+
#
72+
# You can setup several at once:
73+
#
74+
# JsonApiClient::Schema::Types.register money: MyMoneyCaster,
75+
# date: MyJsonDateTypeCaster
76+
#
77+
#
78+
#
79+
#
80+
def self.register(type_hash)
81+
@@types.merge!(type_hash)
82+
end
83+
84+
def self.type_for(type)
85+
@@types[type]
86+
end
87+
88+
self.register int: Types::Integer,
89+
integer: Types::Integer,
90+
string: Types::String,
91+
float: Types::Float,
92+
time: Types::Time,
93+
decimal: Types::Decimal,
94+
boolean: Types::Boolean
95+
96+
end
97+
98+
Property = Struct.new(:name, :type, :default) do
99+
def cast(value)
100+
return nil if value.nil?
101+
return value if type.nil?
102+
type_caster = TypeFactory.type_for(type)
103+
return value if type_caster.nil?
104+
type_caster.cast(value, default)
105+
end
34106
end
35107

36108
def initialize
@@ -54,11 +126,13 @@ def add(name, options)
54126
def size
55127
@properties.size
56128
end
129+
57130
alias_method :length, :size
58131

59132
def each_property(&block)
60133
@properties.values.each(&block)
61134
end
135+
62136
alias_method :each, :each_property
63137

64138
# Look up a property by name
@@ -68,6 +142,13 @@ def each_property(&block)
68142
def find(property_name)
69143
@properties[property_name.to_sym]
70144
end
145+
71146
alias_method :[], :find
147+
148+
class << self
149+
def register(type_hash)
150+
TypeFactory.register(type_hash)
151+
end
152+
end
72153
end
73154
end

test/unit/schemable_test.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
require 'test_helper'
22

3+
class CustomTypeCaster
4+
def self.cast(*)
5+
:mock
6+
end
7+
end
8+
9+
JsonApiClient::Schema.register custom: CustomTypeCaster
10+
311
class SchemaResource < TestResource
412
property :a, type: :string, default: 'foo'
513
property :b, type: :boolean, default: false
@@ -11,6 +19,10 @@ class SchemaResource2 < TestResource
1119
property :a, type: :float
1220
end
1321

22+
class SchemaResource3 < TestResource
23+
property :a, type: :custom
24+
end
25+
1426
class MultipleSchema < TestResource
1527
properties :name, :short_name, :long_name, type: :string
1628
end
@@ -131,4 +143,10 @@ def test_boolean_defaults_to_default
131143
assert_equal false, resource.b
132144
end
133145

146+
def test_custom_types
147+
resource = SchemaResource3.new(a: 'anything')
148+
assert_equal :mock, resource.a
149+
assert_equal :mock, resource['a']
150+
end
151+
134152
end

0 commit comments

Comments
 (0)