json_schema_tools 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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 files
21
- must be available(read into a hash). So first provide a base path where the
22
- schema.json files are located.
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 JSON
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 (public) contract about an object definition. Therefore an
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(same as peter.class name) inside the global
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
- client_hash = SchemaTools::Hash.from_schema(peter)
82
- #=> "client"=>{"id"=>12, "name"=> "Peter", "email"=>"",..} # whatever else you have as properties
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 Schema Hash
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
- client_hash = SchemaTools::Hash.from_schema(peter, class_name: 'contact')
100
- #=> "contact"=>{"id"=>12, "name"=> "Peter"}
131
+ peter.as_schema_json(class_name: 'contact')
101
132
  ```
102
133
 
103
- Use a custom schema path
134
+ Set a custom schema path
104
135
 
105
136
  ```ruby
106
- client_hash = SchemaTools::Hash.from_schema(peter, path: 'path-to/json-files/')
107
- #=> "client"=>{"id"=>12, "name"=> "Peter"}
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 usefull if you are building a API client and don't want to manually add
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
- usefull in test env.
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 inside the given namespace. Uses all classes
9
- # found in schema path:
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 global schema path:
14
+ # First set a(global) schema path:
13
15
  # SchemaTools.schema_path = File.expand_path('../fixtures', __FILE__)
14
16
  #
15
- # Bake classes from all schema.json found
17
+ # Build classes from all json files in schema path
16
18
  # SchemaTools::KlassFactory.build
17
19
  #
18
- # Go use it
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 [SchemaTools::Reader] :path to schema files instead of global one
26
- # @options opts [SchemaTools::Reader] :namespace of the new classes e.g. MyCustomNamespace::MySchemaClass
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 including this module and defining from
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(schema, opts={})
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(schema, schema_location)
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 # ClassMethods
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 which is returned as hash
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
- hsh = { "#{class_name}" => data }
60
- #add links if present
61
+ #get links if present
61
62
  links = parse_links(obj, schema)
62
- links && hsh['links'] = links
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
- #got schema describing the objects
92
- from_schema(rel_obj, opts)
93
- else
94
- rel_obj
95
- end
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 in #registry to prevent filesystem
29
- # round-trips. The cache can be resented with #registry_reset
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? ::Hash
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, "Second parameter must be a path or a schema!"
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}
@@ -1,3 +1,3 @@
1
1
  module SchemaTools
2
- VERSION = '0.1.2'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  {"type":"object",
2
2
  "title": "client",
3
3
  "name": "client",
4
- "description": "A client in SalesKing",
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 by SK for new client without number.",
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
- "team_id":{
186
- "description": "A team uuid. If set only the team and its parent teams can see the record.",
187
- "type":"string",
188
- "maxLength": 22,
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
- context 'with nested object values' do
46
- class Client
47
- attr_accessor :first_name, :last_name, :addresses, :id
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
- class Address
50
- attr_accessor :city, :zip
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 nested array values' do
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
- end
70
-
71
-
72
- context 'with plain nested values' do
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
- class Lead < Contact
75
- attr_accessor :links_clicked, :conversion
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
- class Conversion
79
- attr_accessor :from, :to
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']
@@ -1,12 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class TestNamespaceKlass
4
- # namepsace for schema classes
4
+ # namespace for schema classes
5
5
  end
6
6
 
7
- module TestNamespaceModule
8
-
9
- end
7
+ module TestNamespaceModule; end
10
8
 
11
9
  describe SchemaTools::KlassFactory do
12
10
 
@@ -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.1.2
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-11 00:00:00.000000000 Z
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: 2491825896483527088
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: 2491825896483527088
142
+ hash: -1056530391654598731
141
143
  requirements: []
142
144
  rubyforge_project:
143
145
  rubygems_version: 1.8.24