json_schema_tools 0.0.7 → 0.0.8

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 CHANGED
@@ -1,6 +1,9 @@
1
1
  # Changelog JSON Schema Tools
2
2
 
3
3
 
4
- 12-2012
4
+ 2013-02
5
5
 
6
+ * add validations for classes generated by KlassFactory
7
+
8
+ 2012-12
6
9
  * initial version with reader, hasher, params cleaner, attributes module
data/README.md CHANGED
@@ -97,7 +97,7 @@ For example in a client controller
97
97
 
98
98
  ## Object attributes from Schema
99
99
 
100
- The use-case here is to add methods, defined in schema properties, to an object.
100
+ Add methods, defined in schema properties, to an existing class.
101
101
  Very usefull if you are building a API client and don't want to manually add
102
102
  methods to you local classes .. like people NOT using JSON schema
103
103
 
@@ -111,31 +111,31 @@ methods to you local classes .. like people NOT using JSON schema
111
111
  # raw access
112
112
  contact.schema_attrs
113
113
 
114
- ## Objects from Schema - KlassFactory
114
+ ## Classes from Schema - KlassFactory
115
115
 
116
116
  Use the KlassFactory to directly create classes, with all attributes from a
117
- schema. The classes are named after each schema[name] found in from global path.
117
+ schema. Instead of adding attributes to an existing class like in above example.
118
+ The classes are named after each schema's [name] (in global path).
118
119
  So lets assume you have a 'client.json' schema with a name attribute in it, for
119
120
  the following examples:
120
121
 
121
122
  SchemaTools::KlassFactory.build
122
- client = Client.new
123
- client.name = 'Mändy'
123
+ client = Client.new first_name: 'Heinz'
124
+ client.name = 'Schultz'
125
+ client.valid?
126
+ client.errors.full_messages
124
127
 
125
128
 
126
- Rather like a namespace? Good idea, but the class or module must be defined.
129
+ Rather like a namespace? Good idea, but don't forget the class or module must
130
+ be defined.
127
131
 
128
- module SalesKing; end
129
132
  SchemaTools::KlassFactory.build namespace: SalesKing
130
133
  client = SalesKing::Client.new
131
134
 
132
- Add a custom schema reader
135
+ Add a custom schema reader most likely usefull in conjunction with a custom path
133
136
 
134
137
  reader = SchemaTools::Reader.new
135
- reader.path = HappyPdf::Schema.path
136
- SchemaTools::KlassFactory.build reader: reader
137
-
138
-
138
+ SchemaTools::KlassFactory.build reader: reader, path: HappyPdf::Schema.path
139
139
 
140
140
  ## Real world examples
141
141
 
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
 
19
19
  s.add_dependency 'json'
20
20
  s.add_dependency 'activesupport'
21
+ s.add_dependency 'activemodel'
21
22
 
22
23
  s.add_development_dependency 'rake'
23
24
  s.add_development_dependency 'simplecov'
@@ -6,11 +6,24 @@ module SchemaTools
6
6
  class << self
7
7
 
8
8
  # Build classes from schema inside the given namespace. Uses all classes
9
- # found in schema path
9
+ # found in schema path:
10
+ # @example
11
+ #
12
+ # First set a global schema path:
13
+ # SchemaTools.schema_path = File.expand_path('../fixtures', __FILE__)
14
+ #
15
+ # Bake classes from all schema.json found
16
+ # SchemaTools::KlassFactory.build
17
+ #
18
+ # Go use it
19
+ # client = Client.new organisation: 'SalesKing'
20
+ # client.valid?
21
+ # client.errors.should be_blank
10
22
  #
11
- # @param [String|Symbol|Module] namespace of the new classes e.g. MyCustomNamespace::MySchemaClass
12
23
  # @param [Hash] opts
13
24
  # @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
14
27
  def build(opts={})
15
28
  reader = opts[:reader] || SchemaTools::Reader
16
29
  schemata = reader.read_all( opts[:path] || SchemaTools.schema_path )
@@ -18,14 +31,27 @@ module SchemaTools
18
31
  if namespace.is_a?(String) || namespace.is_a?(Symbol)
19
32
  namespace = "#{namespace}".constantize
20
33
  end
21
-
34
+ # bake classes
22
35
  schemata.each do |schema|
23
36
  klass_name = schema['name'].classify
24
37
  next if namespace.const_defined?(klass_name, false)
25
38
  klass = namespace.const_set(klass_name, Class.new)
26
39
  klass.class_eval do
27
40
  include SchemaTools::Modules::Attributes
41
+ include ActiveModel::Conversion
42
+ include SchemaTools::Modules::Validations # +naming + transl + conversion
28
43
  has_schema_attrs schema['name'], reader: reader
44
+ validate_with schema['name'], reader:reader
45
+ getter_names = schema['properties'].select{|name,prop| !prop['readonly'] }.keys.map { |name| name.to_sym}
46
+ attr_accessor *getter_names
47
+
48
+ def initialize(attributes = {})
49
+ attributes.each do |name, value|
50
+ send("#{name}=", value)
51
+ end
52
+ end
53
+
54
+ def persisted?; false end
29
55
  end
30
56
  end
31
57
  end
@@ -0,0 +1,78 @@
1
+ require 'active_support/concern'
2
+ require 'active_model/validations'
3
+ require 'active_model/naming'
4
+ require 'active_model/translation'
5
+ require 'active_model/conversion'
6
+ module SchemaTools
7
+ module Modules
8
+ # Add schema properties to a class by including this module and defining from
9
+ # which schema to inherit attributes.
10
+ module Validations
11
+ extend ActiveSupport::Concern
12
+ include ActiveModel::Conversion
13
+ include ActiveModel::Validations
14
+
15
+ #included do
16
+ # validate_with_schema :schema_name
17
+ #end
18
+ # Runs all the validations within the specified context. Returns true if no errors are found,
19
+ # false otherwise.
20
+ #
21
+ # If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if
22
+ # <tt>new_record?</tt> is true, and to <tt>:update</tt> if it is not.
23
+ #
24
+ # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
25
+ # some <tt>:on</tt> option will only run in the specified context.
26
+ def valid?(context = nil)
27
+ #context ||= (new_record? ? :create : :update)
28
+ output = super(context)
29
+ errors.empty? && output
30
+ end
31
+
32
+ module ClassMethods
33
+
34
+ # @param [Symbol|String] schema name
35
+ # @param [Hash<Symbol|String>] opts
36
+ # @options opts [String] :path schema path
37
+ # @options opts [SchemaTools::Reader] :reader instance, instead of global reader/registry
38
+ def validate_with(schema, opts={})
39
+ reader = opts[:reader] || SchemaTools::Reader
40
+ schema = reader.read(schema, opts[:path])
41
+ # make getter / setter
42
+ schema[:properties].each do |key, val|
43
+ validates_length_of key, validate_length_opts(val) if val['maxLength'] || val['minLength']
44
+ validates_presence_of key if val['required'] || val['required'] == 'true'
45
+ validates_numericality_of key, validate_number_opts(val) if val['type'] == 'number'
46
+ #TODO array minItems, max unique, null, string
47
+ # format: date-time, regex color style, email,uri, ..
48
+ validates_numericality_of key, validate_number_opts(val) if val['type'] == 'number'
49
+ #end
50
+ end
51
+ end
52
+
53
+ def validate_length_opts(attr)
54
+ opts = {}
55
+ opts[:within] = attr['minLength']..attr['maxLength'] if attr['minLength'] && attr['maxLength']
56
+ opts[:maximum] = attr['maxLength'] if attr['maxLength'] && !attr['minLength']
57
+ opts[:minimum] = attr['minLength'] if attr['minLength'] && !attr['maxLength']
58
+ opts[:allow_blank] = true if !attr['required']
59
+ opts
60
+ end
61
+
62
+ # @param [Hash<String>] attr property values
63
+ def validate_number_opts(attr)
64
+ opts = {}
65
+ opts[:allow_blank] = true
66
+ # those vals should not be set both in one property
67
+ opts[:greater_than_or_equal_to] = attr['minimum'] if attr['minimum'].present?
68
+ opts[:less_than_or_equal_to] = attr['maximum'] if attr['maximum'].present?
69
+ opts[:less_than] = attr['exclusiveMinimum'] if attr['exclusiveMinimum'].present?
70
+ opts[:greater_than] = attr['exclusiveMaximum'] if attr['exclusiveMaximum'].present?
71
+ opts
72
+ end
73
+
74
+ end # ClassMethods
75
+
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,3 @@
1
1
  module SchemaTools
2
- VERSION = '0.0.7'
2
+ VERSION = '0.0.8'
3
3
  end
data/lib/schema_tools.rb CHANGED
@@ -3,6 +3,7 @@ require 'schema_tools/version'
3
3
  require 'schema_tools/modules/read'
4
4
  require 'schema_tools/modules/hash'
5
5
  require 'schema_tools/modules/attributes'
6
+ require 'schema_tools/modules/validations'
6
7
  require 'schema_tools/reader'
7
8
  require 'schema_tools/cleaner'
8
9
  require 'schema_tools/hash'
@@ -1,7 +1,7 @@
1
1
  {"type":"object",
2
2
  "title": "client",
3
3
  "name": "client",
4
- "description": "A client as seen by SalesKing",
4
+ "description": "A client in SalesKing",
5
5
  "properties":{
6
6
  "id":{
7
7
  "description":"Unique identifier - UUID",
@@ -164,6 +164,8 @@
164
164
  },
165
165
  "cash_discount":{
166
166
  "description": "Default cash discount for new invoices.",
167
+ "maximum": 100,
168
+ "minimum": 0,
167
169
  "type":"number"
168
170
  },
169
171
  "due_days":{
@@ -10,14 +10,14 @@
10
10
  "maxLength": 255
11
11
  },
12
12
  "links_clicked":{
13
- "description": "Timestamps of clicks. Just an example to test nested array values without object reference",
13
+ "description": "Timestamps of clicks. Test nested array values without object reference",
14
14
  "type":"array",
15
15
  "properties":{
16
16
  "type": "string"
17
17
  }
18
18
  },
19
19
  "conversion":{
20
- "description": "Just an example to test nested object value without object reference",
20
+ "description": "Test nested object value without object reference",
21
21
  "type":"object",
22
22
  "properties":{
23
23
  "from": {
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class TestNamespaceKlass
4
- # for schema classes
4
+ # namepsace for schema classes
5
5
  end
6
6
 
7
7
  module TestNamespaceModule
@@ -28,6 +28,42 @@ describe SchemaTools::KlassFactory do
28
28
 
29
29
  end
30
30
 
31
+ context 'with validations' do
32
+ it 'should add validations' do
33
+ SchemaTools::KlassFactory.build
34
+ client = Client.new
35
+ client.valid?
36
+ client.errors[:organisation][0].should include 'blank'
37
+ end
38
+
39
+ it 'should build with params' do
40
+ SchemaTools::KlassFactory.build
41
+ client = Client.new organisation: 'SalesKing'
42
+ client.valid?
43
+ client.errors.should be_blank
44
+ end
45
+
46
+ it 'should validate number maximum and minimum' do
47
+ SchemaTools::KlassFactory.build
48
+ client = Client.new cash_discount: 100, organisation: 'SalesKing'
49
+ client.should be_valid
50
+ #to big
51
+ client.cash_discount = 101
52
+ client.valid?
53
+ client.errors.full_messages[0].should include('less_than_or_equal_to')
54
+ # to small
55
+ client.cash_discount = -1
56
+ client.valid?
57
+ client.errors.full_messages[0].should include('greater_than_or_equal_to')
58
+ end
59
+
60
+ it 'should raise with invalid params' do
61
+ SchemaTools::KlassFactory.build
62
+ expect { Client.new id: 'SalesKing' }.to raise_error NoMethodError
63
+ end
64
+
65
+ end
66
+
31
67
  context 'class building with namespace' do
32
68
 
33
69
  after :each 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.0.7
4
+ version: 0.0.8
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-01-01 00:00:00.000000000 Z
12
+ date: 2013-02-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activemodel
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
46
62
  - !ruby/object:Gem::Dependency
47
63
  name: rake
48
64
  requirement: !ruby/object:Gem::Requirement
@@ -115,6 +131,7 @@ files:
115
131
  - lib/schema_tools/modules/attributes.rb
116
132
  - lib/schema_tools/modules/hash.rb
117
133
  - lib/schema_tools/modules/read.rb
134
+ - lib/schema_tools/modules/validations.rb
118
135
  - lib/schema_tools/reader.rb
119
136
  - lib/schema_tools/version.rb
120
137
  - spec/fixtures/address.json
@@ -142,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
159
  version: '0'
143
160
  segments:
144
161
  - 0
145
- hash: -3983792296207531443
162
+ hash: 4078782522717715484
146
163
  required_rubygems_version: !ruby/object:Gem::Requirement
147
164
  none: false
148
165
  requirements:
@@ -151,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
168
  version: '0'
152
169
  segments:
153
170
  - 0
154
- hash: -3983792296207531443
171
+ hash: 4078782522717715484
155
172
  requirements: []
156
173
  rubyforge_project:
157
174
  rubygems_version: 1.8.24