json_schema_tools 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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