json_schema_tools 0.1.2 → 0.2.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.
- data/CHANGELOG.md +5 -1
- data/README.md +66 -19
- data/lib/schema_tools.rb +1 -0
- data/lib/schema_tools/klass_factory.rb +12 -8
- data/lib/schema_tools/modules/as_schema.rb +40 -0
- data/lib/schema_tools/modules/attributes.rb +7 -4
- data/lib/schema_tools/modules/hash.rb +18 -9
- data/lib/schema_tools/modules/read.rb +4 -4
- data/lib/schema_tools/version.rb +1 -1
- data/spec/fixtures/client.json +6 -194
- data/spec/schema_tools/cleaner_spec.rb +7 -1
- data/spec/schema_tools/hash_spec.rb +71 -16
- data/spec/schema_tools/klass_factory_spec.rb +2 -4
- data/spec/schema_tools/modules/as_schema_spec.rb +85 -0
- data/spec/schema_tools/modules/attributes_spec.rb +4 -0
- metadata +6 -4
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# Changelog JSON Schema Tools
|
2
2
|
|
3
3
|
|
4
|
+
2013-10
|
5
|
+
|
6
|
+
* add option to exclude_root in to_schema hash method
|
7
|
+
|
4
8
|
2013-06
|
5
9
|
|
6
10
|
* *has_schema_attr :schema=>{schema as ruby hash}* allow pass a schema as hash
|
@@ -8,10 +12,10 @@
|
|
8
12
|
* rails 4 (ActiveModel) compatibility
|
9
13
|
* Testing with different ActiveModel Versions
|
10
14
|
|
11
|
-
|
12
15
|
2013-02
|
13
16
|
|
14
17
|
* add validations for classes generated by KlassFactory
|
15
18
|
|
16
19
|
2012-12
|
20
|
+
|
17
21
|
* initial version with reader, hasher, params cleaner, attributes module
|
data/README.md
CHANGED
@@ -17,9 +17,12 @@ Hook the gem into your app
|
|
17
17
|
|
18
18
|
## Read Schema
|
19
19
|
|
20
|
-
Before the fun begins, with any of the tools, one or multiple JSON schema
|
21
|
-
must be available
|
22
|
-
|
20
|
+
Before the fun begins, with any of the tools, one or multiple JSON schema(files)
|
21
|
+
must be available. A schema is converted into a ruby hash and for convenience is
|
22
|
+
cached into a registry (global or local). So you only need to initialize them
|
23
|
+
once e.g on program start.
|
24
|
+
|
25
|
+
Provide a base path where the schema json files are located.
|
23
26
|
|
24
27
|
```ruby
|
25
28
|
SchemaTools.schema_path = '/path/to/schema-json-files'
|
@@ -64,29 +67,58 @@ reader.read :client, 'from/path'
|
|
64
67
|
reader.registry
|
65
68
|
```
|
66
69
|
|
67
|
-
## Object to Schema
|
70
|
+
## Object to JSON - from Schema
|
71
|
+
|
72
|
+
As you probably know such is done e.g in rails via object.to_json. While using
|
73
|
+
this might be simple, it has a damn big drawback: There is no transparent
|
74
|
+
contract about the data-structure, as rails simply uses all fields defined in the
|
75
|
+
database(ActiveRecord model). One side-effect: With each migration you are f***ed
|
68
76
|
|
69
|
-
A schema provides a
|
70
|
-
internal object is converted to it's schema version on delivery(API access).
|
77
|
+
A schema provides a public contract about an object definition. Therefore an
|
78
|
+
internal object is converted to it's public(schema) version on delivery(API access).
|
71
79
|
First the object is converted to a hash containing only the properties(keys)
|
72
80
|
from its schema definition. Afterwards it is a breeze to convert this hash into
|
73
81
|
JSON, with your favorite generator.
|
74
82
|
|
75
|
-
Following uses client.json schema
|
76
|
-
schema_path and adds properties to the clients_hash simply calling
|
83
|
+
Following uses client.json schema, detected from peter.class name.underscore => "client",
|
84
|
+
inside the global schema_path and adds properties to the clients_hash by simply calling
|
77
85
|
client.send('property-name'):
|
78
86
|
|
79
87
|
```ruby
|
88
|
+
class Client < ActiveRecord::Base
|
89
|
+
include SchemaTools::Modules::AsSchema
|
90
|
+
end
|
91
|
+
|
80
92
|
peter = Client.new name: 'Peter'
|
81
|
-
|
82
|
-
#=> "client"
|
93
|
+
peter.as_schema_json
|
94
|
+
#=> "client":{"id":12, "name": "Peter", "email":"",..}
|
95
|
+
|
96
|
+
peter.as_schema_hash
|
97
|
+
#=> "client"=>{"id"=>12, "name"=> "Peter", "email"=>"",..}
|
98
|
+
```
|
99
|
+
|
100
|
+
The AsSchema module is a tiny wrapper for following low level method:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
paul = Contact.new name: 'Peter'
|
104
|
+
contact_hash = SchemaTools::Hash.from_schema(paul)
|
105
|
+
#=> "contact"=>{"id"=>12, "name"=> "Peter", "email"=>"",..}
|
83
106
|
# to_json is up to you .. or your rails controller
|
84
107
|
```
|
85
108
|
|
86
|
-
### Customise
|
109
|
+
### Customise Output JSON / Hash
|
110
|
+
|
111
|
+
Following examples show options to customize the resulting json or hash. Of
|
112
|
+
course they can be combined.
|
87
113
|
|
88
114
|
Only use some fields e.g. to save bandwidth
|
89
115
|
|
116
|
+
```ruby
|
117
|
+
peter.as_schema_json(fields:['id', 'name'])
|
118
|
+
#=> "client":{"id":12, "name": "Peter"}
|
119
|
+
```
|
120
|
+
Of course the low level hash method also supports all of these options:
|
121
|
+
|
90
122
|
```ruby
|
91
123
|
client_hash = SchemaTools::Hash.from_schema(peter, fields:['id', 'name'])
|
92
124
|
#=> "client"=>{"id"=>12, "name"=> "Peter"}
|
@@ -96,15 +128,29 @@ Use a custom schema name e.g. to represent a client as contact. Assumes you also
|
|
96
128
|
have a schema named contact.json
|
97
129
|
|
98
130
|
```ruby
|
99
|
-
|
100
|
-
#=> "contact"=>{"id"=>12, "name"=> "Peter"}
|
131
|
+
peter.as_schema_json(class_name: 'contact')
|
101
132
|
```
|
102
133
|
|
103
|
-
|
134
|
+
Set a custom schema path
|
104
135
|
|
105
136
|
```ruby
|
106
|
-
|
107
|
-
|
137
|
+
peter.as_schema_json( path: 'path-to/json-files/')
|
138
|
+
```
|
139
|
+
|
140
|
+
By default the object hash has the class name (client) and the link-section on
|
141
|
+
root level. This divides the data from the available methods and makes a clear
|
142
|
+
statement about the object type(it's class).
|
143
|
+
If you don't want to traverse that one extra level you can exclude the root
|
144
|
+
and move the data one level up. See how class name and links are available
|
145
|
+
inline:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
|
149
|
+
peter.as_schema_json( exclude_root: true )
|
150
|
+
|
151
|
+
client_hash = SchemaTools::Hash.from_schema(peter, exclude_root: true)
|
152
|
+
#=> {"id"=>12, "name"=> "Peter",
|
153
|
+
# "_class_name":"client", "_links":[ .. are inlined .. ]}
|
108
154
|
```
|
109
155
|
|
110
156
|
## Parameter cleaning
|
@@ -125,7 +171,7 @@ end
|
|
125
171
|
## Object attributes from Schema
|
126
172
|
|
127
173
|
Add methods, defined in schema properties, to an existing class.
|
128
|
-
Very
|
174
|
+
Very useful if you are building a API client and don't want to manually add
|
129
175
|
methods to you local classes .. like people NOT using JSON schema
|
130
176
|
|
131
177
|
```ruby
|
@@ -138,6 +184,8 @@ contact = Client.new
|
|
138
184
|
contact.last_name = 'Rambo'
|
139
185
|
# raw access
|
140
186
|
contact.schema_attrs
|
187
|
+
# to json
|
188
|
+
contact.as_schema_json
|
141
189
|
```
|
142
190
|
|
143
191
|
## Classes from Schema - KlassFactory
|
@@ -194,8 +242,7 @@ Testing with different ActiveModel / ActiveSupport Versions:
|
|
194
242
|
RAILS_VERSION=4 rake spec
|
195
243
|
|
196
244
|
The RAILS_VERSION switch sets the version of the gems in the Gemfile and is only
|
197
|
-
|
198
|
-
|
245
|
+
useful in test env.
|
199
246
|
|
200
247
|
# Credits
|
201
248
|
|
data/lib/schema_tools.rb
CHANGED
@@ -2,6 +2,7 @@ require 'json'
|
|
2
2
|
require 'schema_tools/version'
|
3
3
|
require 'schema_tools/modules/read'
|
4
4
|
require 'schema_tools/modules/hash'
|
5
|
+
require 'schema_tools/modules/as_schema'
|
5
6
|
require 'schema_tools/modules/attributes'
|
6
7
|
require 'schema_tools/modules/validations'
|
7
8
|
require 'schema_tools/reader'
|
@@ -5,25 +5,29 @@ module SchemaTools
|
|
5
5
|
|
6
6
|
class << self
|
7
7
|
|
8
|
-
# Build classes from schema
|
9
|
-
# found in
|
8
|
+
# Build ruby classes from a schema with getter/setter methods for all
|
9
|
+
# properties and validation. Uses all classes(json files) found in global
|
10
|
+
# schema path or you can add a custom path.
|
11
|
+
# A namespace can be given under which the classes will be created.
|
10
12
|
# @example
|
11
13
|
#
|
12
|
-
# First set a
|
14
|
+
# First set a(global) schema path:
|
13
15
|
# SchemaTools.schema_path = File.expand_path('../fixtures', __FILE__)
|
14
16
|
#
|
15
|
-
#
|
17
|
+
# Build classes from all json files in schema path
|
16
18
|
# SchemaTools::KlassFactory.build
|
17
19
|
#
|
18
|
-
# Go use
|
20
|
+
# Go use them
|
19
21
|
# client = Client.new organisation: 'SalesKing'
|
20
22
|
# client.valid?
|
21
23
|
# client.errors.should be_blank
|
22
|
-
#
|
24
|
+
# With custom options:
|
25
|
+
# SchemaTools::KlassFactory.build namespace: MyModule,
|
26
|
+
# path: 'custom/path/to_json_schema'
|
23
27
|
# @param [Hash] opts
|
24
28
|
# @options opts [SchemaTools::Reader] :reader to use instead of global one
|
25
|
-
# @options opts [
|
26
|
-
# @options opts [
|
29
|
+
# @options opts [String] :path to schema files instead of global one
|
30
|
+
# @options opts [String|Symbol|Module] :namespace of the new classes e.g. MyCustomNamespace::MySchemaClass
|
27
31
|
def build(opts={})
|
28
32
|
reader = opts[:reader] || SchemaTools::Reader
|
29
33
|
schemata = reader.read_all( opts[:path] || SchemaTools.schema_path )
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
module SchemaTools
|
3
|
+
module Modules
|
4
|
+
# Extend a class so it can be rendered as json from a named schema
|
5
|
+
module AsSchema
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# convert this class to a schema markup.
|
9
|
+
# @param [Hash{Symbol=>Mixed}] opts passed on to #SchemaTools::Hash.from_schema
|
10
|
+
# @return [String] json
|
11
|
+
def as_schema_json(opts={})
|
12
|
+
ActiveSupport::JSON.encode(as_schema_hash(opts))
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get the object as hash with fields detected from its schema.
|
16
|
+
# The schema is derived from
|
17
|
+
# * from options[:class_name]
|
18
|
+
# * has_schema_attrs definition(if used)
|
19
|
+
# * the underscored class name
|
20
|
+
# @param [Hash{Symbol=>Mixed}] opts passed on to #SchemaTools::Hash.from_schema
|
21
|
+
# @return [Hash]
|
22
|
+
def as_schema_hash(opts={})
|
23
|
+
# detect schema name from class method, else class name or opts is used.
|
24
|
+
if self.class.schema_name
|
25
|
+
opts[:class_name] ||= self.class.schema_name
|
26
|
+
end
|
27
|
+
SchemaTools::Hash.from_schema(self, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
# Get or set the schema name used
|
32
|
+
def schema_name(name=nil)
|
33
|
+
@schema_name = name if name
|
34
|
+
@schema_name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'active_support/concern'
|
2
2
|
module SchemaTools
|
3
3
|
module Modules
|
4
|
-
# Add schema properties to a class by
|
4
|
+
# Add schema properties to a class by using has_schema_attrs to define from
|
5
5
|
# which schema to inherit attributes.
|
6
6
|
module Attributes
|
7
7
|
extend ActiveSupport::Concern
|
8
|
+
include SchemaTools::Modules::AsSchema
|
8
9
|
|
9
10
|
def schema_attrs
|
10
11
|
@schema_attrs ||= {}
|
@@ -16,10 +17,12 @@ module SchemaTools
|
|
16
17
|
# @param [Hash<Symbol|String>] opts
|
17
18
|
# @options opts [String] :path schema path
|
18
19
|
# @options opts [SchemaTools::Reader] :reader instance, instead of global reader/registry
|
19
|
-
def has_schema_attrs(
|
20
|
+
def has_schema_attrs(schema_name, opts={})
|
20
21
|
reader = opts[:reader] || SchemaTools::Reader
|
21
22
|
schema_location = opts[:path] || opts[:schema]
|
22
|
-
schema = reader.read(
|
23
|
+
schema = reader.read(schema_name, schema_location)
|
24
|
+
# remember name on class level
|
25
|
+
self.schema_name(schema_name)
|
23
26
|
# make getter / setter
|
24
27
|
schema[:properties].each do |key, val|
|
25
28
|
# getter
|
@@ -36,7 +39,7 @@ module SchemaTools
|
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
|
-
end
|
42
|
+
end
|
40
43
|
|
41
44
|
end
|
42
45
|
end
|
@@ -24,7 +24,7 @@ module SchemaTools
|
|
24
24
|
# obj_hash = Schema.to_hash_from_schema(obj, class_name: :document)
|
25
25
|
# => { 'document' =>{'title'=>'hello world' } }
|
26
26
|
#
|
27
|
-
# @param [Object] obj
|
27
|
+
# @param [Object] obj returned as hash
|
28
28
|
# @param [Hash{Symbol=>Mixed}] opts additional options
|
29
29
|
# @options opts [String|Symbol] :class_name used as hash key. Should be
|
30
30
|
# a lowercase underscored name and it MUST have an existing schema file.
|
@@ -32,6 +32,8 @@ module SchemaTools
|
|
32
32
|
# @options opts [Array<String>] :fields to return. If not set all schema
|
33
33
|
# properties are used.
|
34
34
|
# @options opts [String] :path of the schema files overriding global one
|
35
|
+
# @options opts [Boolean] :exclude_root if set objects are not nested under
|
36
|
+
# their class name and the object hash gets _links and _class_name inline.
|
35
37
|
#
|
36
38
|
# @return [Hash{String=>{String=>Mixed}}] The object as hash:
|
37
39
|
# { 'invoice' => {'title'=>'hello world', 'number'=>'4711' } }
|
@@ -56,10 +58,17 @@ module SchemaTools
|
|
56
58
|
data[field] = obj.send(field) if obj.respond_to?(field)
|
57
59
|
end
|
58
60
|
end
|
59
|
-
|
60
|
-
#add links if present
|
61
|
+
#get links if present
|
61
62
|
links = parse_links(obj, schema)
|
62
|
-
|
63
|
+
|
64
|
+
if opts[:exclude_root]
|
65
|
+
hsh = data
|
66
|
+
hsh['_class_name'] = "#{class_name}"
|
67
|
+
links && hsh['_links'] = links
|
68
|
+
else
|
69
|
+
hsh = { "#{class_name}" => data }
|
70
|
+
links && hsh['links'] = links
|
71
|
+
end
|
63
72
|
hsh
|
64
73
|
end
|
65
74
|
|
@@ -88,11 +97,11 @@ module SchemaTools
|
|
88
97
|
if obj.respond_to?( field ) && rel_objects = obj.send( field )
|
89
98
|
rel_objects.each do |rel_obj|
|
90
99
|
res << if prop['properties'] && prop['properties']['$ref']
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
100
|
+
#got schema describing the objects
|
101
|
+
from_schema(rel_obj, opts)
|
102
|
+
else
|
103
|
+
rel_obj
|
104
|
+
end
|
96
105
|
end
|
97
106
|
end
|
98
107
|
res
|
@@ -25,8 +25,8 @@ module SchemaTools
|
|
25
25
|
|
26
26
|
# Read a schema and return it as hash. You can supply a path or the
|
27
27
|
# global path defined in #SchemaTools.schema_path is used.
|
28
|
-
# Schemata returned from cache
|
29
|
-
# round-trips. The cache can be
|
28
|
+
# Schemata are returned from cache(#registry) if present to prevent
|
29
|
+
# filesystem round-trips. The cache can be reset with #registry_reset
|
30
30
|
#
|
31
31
|
# @param [String|Symbol] schema name to be read from schema path directory
|
32
32
|
# @param [String|Hash] either the path to retrieve schema_name from,
|
@@ -36,14 +36,14 @@ module SchemaTools
|
|
36
36
|
schema_name = schema_name.to_sym
|
37
37
|
return registry[schema_name] if registry[schema_name]
|
38
38
|
|
39
|
-
if path_or_schema.is_a?
|
39
|
+
if path_or_schema.is_a?(::Hash)
|
40
40
|
path = nil
|
41
41
|
plain_data = path_or_schema.to_json
|
42
42
|
elsif path_or_schema.is_a?(::String) || path_or_schema.nil?
|
43
43
|
path = path_or_schema
|
44
44
|
file_path = File.join(path || SchemaTools.schema_path, "#{schema_name}.json")
|
45
45
|
else
|
46
|
-
raise ArgumentError,
|
46
|
+
raise ArgumentError, 'Second parameter must be a path or a schema!'
|
47
47
|
end
|
48
48
|
|
49
49
|
plain_data ||= File.open(file_path, 'r'){|f| f.read}
|
data/lib/schema_tools/version.rb
CHANGED
data/spec/fixtures/client.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{"type":"object",
|
2
2
|
"title": "client",
|
3
3
|
"name": "client",
|
4
|
-
"description": "A client
|
4
|
+
"description": "A example client.",
|
5
5
|
"properties":{
|
6
6
|
"id":{
|
7
7
|
"description":"Unique identifier - UUID",
|
@@ -12,7 +12,7 @@
|
|
12
12
|
"minLength":22
|
13
13
|
},
|
14
14
|
"number":{
|
15
|
-
"description": "Unique number, auto-created
|
15
|
+
"description": "Unique number, auto-created for new client without number.",
|
16
16
|
"type":"string",
|
17
17
|
"maxLength": 50
|
18
18
|
},
|
@@ -32,54 +32,11 @@
|
|
32
32
|
"type":"string",
|
33
33
|
"maxLength": 50
|
34
34
|
},
|
35
|
-
"gender":{
|
36
|
-
"description": "Can be empty for a company. Is used in salutation",
|
37
|
-
"enum":["male", "female"],
|
38
|
-
"type":"string"
|
39
|
-
},
|
40
|
-
"notes":{
|
41
|
-
"description": "Notes for a contact. For day to day information you should use comments instead.",
|
42
|
-
"type":"string",
|
43
|
-
"format": "text"
|
44
|
-
},
|
45
|
-
"position":{
|
46
|
-
"description": "Position of a person in a company.",
|
47
|
-
"type":"string",
|
48
|
-
"maxLength": 50
|
49
|
-
},
|
50
|
-
"title":{
|
51
|
-
"description": "Academical title of a person e.g. Dr., Prof",
|
52
|
-
"type":"string",
|
53
|
-
"maxLength": 50
|
54
|
-
},
|
55
|
-
"tax_number":{
|
56
|
-
"description": "Tax number, normally applies to a private person",
|
57
|
-
"type":"string",
|
58
|
-
"maxLength": 30
|
59
|
-
},
|
60
|
-
"vat_number":{
|
61
|
-
"description": "VAT number, for a company or person paying value added taxes.",
|
62
|
-
"type":"string",
|
63
|
-
"maxLength": 30
|
64
|
-
},
|
65
35
|
"email":{
|
66
36
|
"description": "Email address of the contact.",
|
67
37
|
"type":"string",
|
68
38
|
"maxLength": 100
|
69
39
|
},
|
70
|
-
"url":{
|
71
|
-
"description": "An url associated with the person, e.g its company website.",
|
72
|
-
"type":"string",
|
73
|
-
"maxLength": 255
|
74
|
-
},
|
75
|
-
"birthday":{
|
76
|
-
"format":"date",
|
77
|
-
"type":"string"
|
78
|
-
},
|
79
|
-
"tag_list":{
|
80
|
-
"description": "Space separated list of tags. Are split and saved as Tag objects on create, update.",
|
81
|
-
"type":"string"
|
82
|
-
},
|
83
40
|
"created_at":{
|
84
41
|
"description": "Date the record was created in SK. Never changes afterwards.",
|
85
42
|
"format":"date-time",
|
@@ -92,101 +49,26 @@
|
|
92
49
|
"readonly":true,
|
93
50
|
"type":"string"
|
94
51
|
},
|
95
|
-
"language":{
|
96
|
-
"description": "Should be a valid language short-code: de-DE, fr, en-GB; like defined in your account language menu. When the client is emailed, a localized version of a multi-language template(email, pdf) will be used if available. The language will also be set for new documents.",
|
97
|
-
"type":"string",
|
98
|
-
"maxLength": 10
|
99
|
-
},
|
100
|
-
"currency":{
|
101
|
-
"description": "Currency code as defined by the ISO 4217 standard(3-letter UPCASE: EUR, USD). If set the currency is taken for new documents.",
|
102
|
-
"type":"string",
|
103
|
-
"maxLength": 3,
|
104
|
-
"minLength": 3
|
105
|
-
},
|
106
|
-
"payment_method":{
|
107
|
-
"description": "Default payment method for used for new documemts",
|
108
|
-
"enum":["cash","bank_transfer","credit_card","paypal","direct_debit","cheque", "moneybookers", "premium_sms"],
|
109
|
-
"type":"string"
|
110
|
-
},
|
111
|
-
"bank_name":{
|
112
|
-
"description": "Bank name",
|
113
|
-
"type":"string",
|
114
|
-
"maxLength": 70
|
115
|
-
},
|
116
|
-
"bank_number":{
|
117
|
-
"description": "Bank number",
|
118
|
-
"type":"string",
|
119
|
-
"maxLength": 35
|
120
|
-
},
|
121
|
-
"bank_account_number":{
|
122
|
-
"description": "Bank account number.",
|
123
|
-
"type":"string",
|
124
|
-
"maxLength": 35
|
125
|
-
},
|
126
|
-
"bank_iban":{
|
127
|
-
"description": "IBAN Number of the bank account. Is validated",
|
128
|
-
"type":"string",
|
129
|
-
"maxLength": 35
|
130
|
-
},
|
131
|
-
"bank_swift":{
|
132
|
-
"description": "SWIFT BIC- Bank Identifier Code",
|
133
|
-
"type":"string",
|
134
|
-
"maxLength": 11
|
135
|
-
},
|
136
|
-
"bank_owner":{
|
137
|
-
"description": "Bank account owner",
|
138
|
-
"type":"string",
|
139
|
-
"maxLength": 70
|
140
|
-
},
|
141
|
-
"phone_fax":{
|
142
|
-
"description": "Fax number",
|
143
|
-
"type":"string",
|
144
|
-
"maxLength": 30
|
145
|
-
},
|
146
|
-
"phone_office":{
|
147
|
-
"description": "Office phone number",
|
148
|
-
"type":"string",
|
149
|
-
"maxLength": 30
|
150
|
-
},
|
151
|
-
"phone_home":{
|
152
|
-
"description": "Private phone number",
|
153
|
-
"type":"string",
|
154
|
-
"maxLength": 30
|
155
|
-
},
|
156
52
|
"phone_mobile":{
|
157
53
|
"description": "Mobile phone number",
|
158
54
|
"type":"string",
|
159
55
|
"maxLength": 30
|
160
56
|
},
|
161
|
-
"lock_version":{
|
162
|
-
"description": "Increased on every edit, so SK can detect/prevent a concurrent edit by another user. First save wins.",
|
163
|
-
"type":"integer"
|
164
|
-
},
|
165
57
|
"cash_discount":{
|
166
58
|
"description": "Default cash discount for new invoices.",
|
167
59
|
"maximum": 100,
|
168
60
|
"minimum": 0,
|
169
61
|
"type":"number"
|
170
62
|
},
|
171
|
-
"due_days":{
|
172
|
-
"description": "Default due days for new invoices.",
|
173
|
-
"type":"integer"
|
174
|
-
},
|
175
|
-
"address_field":{
|
176
|
-
"description": "Returns the address field used on new docs. Consist of Organisation name and default(first) address",
|
177
|
-
"readonly":true,
|
178
|
-
"type":"string"
|
179
|
-
},
|
180
63
|
"addresses":{
|
181
64
|
"description": "A client can have many addresses, sorted by date descending(new first). Default address is the most recent one.",
|
182
65
|
"type":"array",
|
183
66
|
"properties" : {"$ref":"./address.json#properties"}
|
184
67
|
},
|
185
|
-
"
|
186
|
-
"description": "
|
187
|
-
"type":"
|
188
|
-
"
|
189
|
-
"minLength":22
|
68
|
+
"work_address":{
|
69
|
+
"description": "The work address as an example for a single nested/related object",
|
70
|
+
"type":"object",
|
71
|
+
"properties" : {"$ref":"./address.json#properties"}
|
190
72
|
}
|
191
73
|
},
|
192
74
|
"links":[
|
@@ -211,55 +93,6 @@
|
|
211
93
|
"description": "Wildcard search in first, last_name, organisation, email, number",
|
212
94
|
"type":"string"
|
213
95
|
},
|
214
|
-
"filter[tags]":{
|
215
|
-
"title" : "Tags",
|
216
|
-
"description": "Filter by a space delimited list of tags",
|
217
|
-
"type":"string"
|
218
|
-
},
|
219
|
-
"filter[ids]":{
|
220
|
-
"title" : "Clients",
|
221
|
-
"description": "A single or a list of client uuids, comma separated",
|
222
|
-
"type" : "string"
|
223
|
-
},
|
224
|
-
"filter[created_at_from]":{
|
225
|
-
"title" : "From date",
|
226
|
-
"description": "Objects with a creation date after the date, including given datetime. ISO 8601 format YYY-MM-DDThh:mm:ss+z",
|
227
|
-
"format" : "date-time",
|
228
|
-
"type" : "string"
|
229
|
-
},
|
230
|
-
"filter[created_at_to]":{
|
231
|
-
"title" : "To date",
|
232
|
-
"description": "Objects with a creation date before the date, including given datetime. ISO 8601 format YYY-MM-DDThh:mm:ss+z",
|
233
|
-
"format" : "date-time",
|
234
|
-
"type" : "string"
|
235
|
-
},
|
236
|
-
"filter[birthday_from]":{
|
237
|
-
"title" : "From birthday date",
|
238
|
-
"description": "Contacts with a birthday after and on the date. Leave the birthday-to date blank to only search on this day.",
|
239
|
-
"format" : "date",
|
240
|
-
"type" : "string"
|
241
|
-
},
|
242
|
-
"filter[birthday_to]":{
|
243
|
-
"title" : "To birthday date",
|
244
|
-
"description": "Contacts with a birthday date before and on the date.",
|
245
|
-
"format" : "date",
|
246
|
-
"type" : "string"
|
247
|
-
},
|
248
|
-
"filter[creator_ids]":{
|
249
|
-
"title" : "Creator",
|
250
|
-
"description": "Objects created by the given users uuids, comma separated",
|
251
|
-
"type" : "string"
|
252
|
-
},
|
253
|
-
"filter[number]":{
|
254
|
-
"title" : "By number",
|
255
|
-
"description": "Search by number where the number is matched from the start: number%",
|
256
|
-
"type" : "string"
|
257
|
-
},
|
258
|
-
"filter[languages]":{
|
259
|
-
"title" : "Languages",
|
260
|
-
"description": "A single or a list of language codes, comma separated",
|
261
|
-
"type" : "string"
|
262
|
-
},
|
263
96
|
"sort_by":{
|
264
97
|
"title" : "Sort by",
|
265
98
|
"description": "Sort the results by the given field => number",
|
@@ -293,27 +126,6 @@
|
|
293
126
|
},
|
294
127
|
{ "rel": "invoices",
|
295
128
|
"href": "clients/{id}/invoices"
|
296
|
-
},
|
297
|
-
{ "rel": "estimates",
|
298
|
-
"href": "clients/{id}/estimates"
|
299
|
-
},
|
300
|
-
{ "rel": "orders",
|
301
|
-
"href": "clients/{id}/orders"
|
302
|
-
},
|
303
|
-
{ "rel": "credit_notes",
|
304
|
-
"href": "clients/{id}/credit_notes"
|
305
|
-
},
|
306
|
-
{ "rel": "recurrings",
|
307
|
-
"href": "clients/{id}/recurrings"
|
308
|
-
},
|
309
|
-
{ "rel": "payment_reminders",
|
310
|
-
"href": "clients/{id}/payment_reminders"
|
311
|
-
},
|
312
|
-
{ "rel": "comments",
|
313
|
-
"href": "clients/{id}/comments"
|
314
|
-
},
|
315
|
-
{ "rel": "emails",
|
316
|
-
"href": "clients/{id}/emails"
|
317
129
|
}
|
318
130
|
]
|
319
131
|
}
|
@@ -6,7 +6,8 @@ describe SchemaTools::Cleaner do
|
|
6
6
|
let(:params){
|
7
7
|
{ id: 'some id',
|
8
8
|
last_name: 'Clean',
|
9
|
-
first_name: 'Paul'
|
9
|
+
first_name: 'Paul',
|
10
|
+
phone_mobile: 110
|
10
11
|
}
|
11
12
|
}
|
12
13
|
|
@@ -19,6 +20,11 @@ describe SchemaTools::Cleaner do
|
|
19
20
|
params[:last_name].should == 'Clean'
|
20
21
|
params[:id].should be_nil
|
21
22
|
end
|
23
|
+
|
24
|
+
it 'should convert values for string fields' do
|
25
|
+
SchemaTools::Cleaner.clean_params!(:client, params)
|
26
|
+
params[:phone_mobile].should == '110'
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
@@ -1,12 +1,34 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
################################################################################
|
4
|
+
# classes used in tests their naming is important because the respecting
|
5
|
+
# json schema is derived from it
|
6
|
+
################################################################################
|
7
|
+
class Client
|
8
|
+
attr_accessor :first_name, :id, :addresses, :work_address
|
9
|
+
end
|
10
|
+
class Address
|
11
|
+
attr_accessor :city, :zip
|
12
|
+
end
|
13
|
+
|
3
14
|
class Contact
|
4
15
|
attr_accessor :first_name, :last_name, :addresses, :id
|
5
16
|
end
|
6
17
|
|
18
|
+
# see fixtures/lead.json
|
19
|
+
class Lead < Contact
|
20
|
+
attr_accessor :links_clicked, :conversion
|
21
|
+
end
|
22
|
+
|
23
|
+
class Conversion
|
24
|
+
attr_accessor :from, :to
|
25
|
+
end
|
26
|
+
|
27
|
+
|
7
28
|
describe SchemaTools::Hash do
|
8
29
|
|
9
|
-
context 'from_schema' do
|
30
|
+
context 'from_schema to hash conversion' do
|
31
|
+
|
10
32
|
let(:contact){Contact.new}
|
11
33
|
before :each do
|
12
34
|
contact.first_name = 'Peter'
|
@@ -40,23 +62,38 @@ describe SchemaTools::Hash do
|
|
40
62
|
hash['contact']['id'].should == contact.id
|
41
63
|
hash['contact']['first_name'].should be_nil
|
42
64
|
end
|
43
|
-
end
|
44
65
|
|
45
|
-
|
46
|
-
|
47
|
-
|
66
|
+
it 'should exclude root' do
|
67
|
+
hash = SchemaTools::Hash.from_schema(contact, exclude_root: true)
|
68
|
+
hash['last_name'].should == 'Paul'
|
69
|
+
hash['_class_name'].should == 'contact'
|
48
70
|
end
|
49
|
-
|
50
|
-
|
71
|
+
|
72
|
+
it 'should have _links on object if exclude root' do
|
73
|
+
hash = SchemaTools::Hash.from_schema(contact, exclude_root: true, class_name: :client)
|
74
|
+
hash['_links'].length.should == 8
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should have _class_name on object if exclude root' do
|
78
|
+
hash = SchemaTools::Hash.from_schema(contact, exclude_root: true, class_name: :client)
|
79
|
+
hash['_class_name'].should == 'client'
|
51
80
|
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'with nested values referencing a schema' do
|
52
84
|
|
53
85
|
let(:client){Client.new}
|
54
86
|
|
55
|
-
it 'should have empty
|
87
|
+
it 'should have an empty array if values are missing' do
|
56
88
|
hash = SchemaTools::Hash.from_schema(client)
|
57
89
|
hash['client']['addresses'].should == []
|
58
90
|
end
|
59
91
|
|
92
|
+
it 'should have nil if nested object is missing' do
|
93
|
+
hash = SchemaTools::Hash.from_schema(client)
|
94
|
+
hash['client']['work_address'].should be_nil
|
95
|
+
end
|
96
|
+
|
60
97
|
it 'should have nested array values' do
|
61
98
|
a1 = Address.new
|
62
99
|
a1.city = 'Cologne'
|
@@ -66,19 +103,37 @@ describe SchemaTools::Hash do
|
|
66
103
|
hash['client']['addresses'].should == [{"address"=>{"city"=>"Cologne", "zip"=>50733}}]
|
67
104
|
end
|
68
105
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
106
|
+
it 'should have nested array values without root' do
|
107
|
+
a1 = Address.new
|
108
|
+
a1.city = 'Cologne'
|
109
|
+
a1.zip = 50733
|
110
|
+
client.addresses = [a1]
|
111
|
+
hash = SchemaTools::Hash.from_schema(client, exclude_root: true)
|
112
|
+
hash['addresses'].should == [{"city"=>"Cologne", "zip"=>50733, "_class_name"=>"address"}]
|
113
|
+
end
|
73
114
|
|
74
|
-
|
75
|
-
|
115
|
+
it 'should have nested object value' do
|
116
|
+
a1 = Address.new
|
117
|
+
a1.city = 'Cologne'
|
118
|
+
a1.zip = 50733
|
119
|
+
client.work_address = a1
|
120
|
+
hash = SchemaTools::Hash.from_schema(client)
|
121
|
+
hash['client']['work_address'].should == {"address"=>{"city"=>"Cologne", "zip"=>50733}}
|
76
122
|
end
|
77
123
|
|
78
|
-
|
79
|
-
|
124
|
+
it 'should have nested object value without root' do
|
125
|
+
a1 = Address.new
|
126
|
+
a1.city = 'Cologne'
|
127
|
+
a1.zip = 50733
|
128
|
+
client.work_address = a1
|
129
|
+
hash = SchemaTools::Hash.from_schema(client, exclude_root: true)
|
130
|
+
hash['work_address'].should == {"city"=>"Cologne", "zip"=>50733, "_class_name"=>"address"}
|
80
131
|
end
|
81
132
|
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'with plain nested values' do
|
136
|
+
|
82
137
|
let(:lead){Lead.new}
|
83
138
|
before :each do
|
84
139
|
lead.links_clicked = ['2012-12-12', '2012-12-15', '2012-12-16']
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ClassWithSchemaAttrs
|
4
|
+
include SchemaTools::Modules::Attributes
|
5
|
+
has_schema_attrs :client
|
6
|
+
end
|
7
|
+
|
8
|
+
class ClassWithSchemaName
|
9
|
+
include SchemaTools::Modules::AsSchema
|
10
|
+
schema_name :lead
|
11
|
+
end
|
12
|
+
|
13
|
+
# namespaced to not interfere with classes used in other tests
|
14
|
+
module Test
|
15
|
+
class Address
|
16
|
+
include SchemaTools::Modules::AsSchema
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
describe SchemaTools::Modules::AsSchema do
|
22
|
+
|
23
|
+
describe 'included' do
|
24
|
+
subject { ClassWithSchemaAttrs.new }
|
25
|
+
|
26
|
+
it 'should add as_schema_hash method' do
|
27
|
+
subject.should respond_to(:as_schema_hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should add as_schema_json method' do
|
31
|
+
subject.should respond_to(:as_schema_json)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should return hash' do
|
35
|
+
subject.last_name = 'Hogan'
|
36
|
+
hsh = subject.as_schema_hash
|
37
|
+
hsh['client']['last_name'].should == 'Hogan'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should return json' do
|
41
|
+
subject.last_name = 'Hogan'
|
42
|
+
json_str = subject.as_schema_json
|
43
|
+
hsh = ActiveSupport::JSON.decode(json_str)
|
44
|
+
hsh['client']['last_name'].should == 'Hogan'
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'schema name detection' do
|
50
|
+
|
51
|
+
it 'should use name from has_schema_attrs' do
|
52
|
+
ClassWithSchemaAttrs.new.as_schema_hash.keys.should include('client', 'links')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should use schema_name defined in class' do
|
56
|
+
ClassWithSchemaName.new.as_schema_hash.keys.should include('lead')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should use class name ' do
|
60
|
+
Test::Address.new.as_schema_hash.keys.should include('address')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'schema options' do
|
65
|
+
subject { ClassWithSchemaAttrs.new }
|
66
|
+
|
67
|
+
it 'should override schema name from' do
|
68
|
+
subject.as_schema_hash(class_name:'contact').keys.should include('contact')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should use fields' do
|
72
|
+
subject.as_schema_hash(fields:['id'])['client'].keys.should == ['id']
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should exclude root' do
|
76
|
+
subject.as_schema_hash(exclude_root: true).keys.should include('id')
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should use class name ' do
|
80
|
+
Test::Address.new.as_schema_hash.keys.should include('address')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
@@ -27,6 +27,10 @@ describe SchemaTools::Modules::Attributes do
|
|
27
27
|
subject.should_not respond_to('id=')
|
28
28
|
subject.should_not respond_to('created_at=')
|
29
29
|
end
|
30
|
+
|
31
|
+
it 'should add schema_name to class' do
|
32
|
+
subject.class.schema_name.should == :client
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
context 'attributes from dynamic schema' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_schema_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-10-
|
12
|
+
date: 2013-10-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
@@ -96,6 +96,7 @@ files:
|
|
96
96
|
- lib/schema_tools/cleaner.rb
|
97
97
|
- lib/schema_tools/hash.rb
|
98
98
|
- lib/schema_tools/klass_factory.rb
|
99
|
+
- lib/schema_tools/modules/as_schema.rb
|
99
100
|
- lib/schema_tools/modules/attributes.rb
|
100
101
|
- lib/schema_tools/modules/hash.rb
|
101
102
|
- lib/schema_tools/modules/read.rb
|
@@ -110,6 +111,7 @@ files:
|
|
110
111
|
- spec/schema_tools/cleaner_spec.rb
|
111
112
|
- spec/schema_tools/hash_spec.rb
|
112
113
|
- spec/schema_tools/klass_factory_spec.rb
|
114
|
+
- spec/schema_tools/modules/as_schema_spec.rb
|
113
115
|
- spec/schema_tools/modules/attributes_spec.rb
|
114
116
|
- spec/schema_tools/reader_spec.rb
|
115
117
|
- spec/spec_helper.rb
|
@@ -128,7 +130,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
130
|
version: '0'
|
129
131
|
segments:
|
130
132
|
- 0
|
131
|
-
hash:
|
133
|
+
hash: -1056530391654598731
|
132
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
135
|
none: false
|
134
136
|
requirements:
|
@@ -137,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
139
|
version: '0'
|
138
140
|
segments:
|
139
141
|
- 0
|
140
|
-
hash:
|
142
|
+
hash: -1056530391654598731
|
141
143
|
requirements: []
|
142
144
|
rubyforge_project:
|
143
145
|
rubygems_version: 1.8.24
|