strain 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +17 -0
- data/Guardfile +72 -0
- data/LICENSE +20 -0
- data/README.md +74 -0
- data/Rakefile +26 -0
- data/app/assets/javascripts/strain/jsonapi/articles.js +2 -0
- data/app/assets/javascripts/strain/jsonapi/variants.js +2 -0
- data/app/assets/stylesheets/strain/jsonapi/articles.css +4 -0
- data/app/assets/stylesheets/strain/jsonapi/variants.css +4 -0
- data/app/controllers/strain/application_controller.rb +5 -0
- data/app/controllers/strain/jsonapi/articles_controller.rb +6 -0
- data/app/controllers/strain/jsonapi/jsonapi_controller.rb +10 -0
- data/app/controllers/strain/jsonapi/variants_controller.rb +7 -0
- data/app/helpers/strain/jsonapi/articles_helper.rb +4 -0
- data/app/helpers/strain/jsonapi/variants_helper.rb +4 -0
- data/app/models/strain/article.rb +37 -0
- data/app/models/strain/variant.rb +32 -0
- data/app/resources/strain/jsonapi/article_resource.rb +21 -0
- data/app/resources/strain/jsonapi/variant_resource.rb +16 -0
- data/bin/_guard-core +16 -0
- data/bin/guard +16 -0
- data/bin/rails +12 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/config/initializers/jsonapi_resources.rb +4 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20151103060035_enable_uuid.rb +5 -0
- data/db/migrate/20151103060224_create_strain_variants.rb +11 -0
- data/db/migrate/20151104200908_create_strain_articles.rb +14 -0
- data/lib/json_schema/draft_4_meta_schema.json +150 -0
- data/lib/strain/engine.rb +21 -0
- data/lib/strain/schema_tools/defaults.rb +53 -0
- data/lib/strain/version.rb +3 -0
- data/lib/strain.rb +17 -0
- data/lib/tasks/strain_tasks.rake +4 -0
- data/spec/factories/strain_articles.rb +15 -0
- data/spec/factories/strain_variants.rb +34 -0
- data/spec/lib/strain/schema_tools/defaults_spec.rb +234 -0
- data/spec/models/strain/article_spec.rb +72 -0
- data/spec/models/strain/variant_spec.rb +42 -0
- data/spec/rails_helper.rb +59 -0
- data/spec/requests/strain/jsonapi/articles_spec.rb +220 -0
- data/spec/requests/strain/jsonapi/variants_spec.rb +159 -0
- data/spec/spec_helper.rb +99 -0
- data/spec/test_app/README.rdoc +28 -0
- data/spec/test_app/Rakefile +6 -0
- data/spec/test_app/app/assets/javascripts/application.js +13 -0
- data/spec/test_app/app/assets/stylesheets/application.css +15 -0
- data/spec/test_app/app/controllers/application_controller.rb +5 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/models/tweet.rb +4 -0
- data/spec/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/test_app/bin/bundle +3 -0
- data/spec/test_app/bin/rails +4 -0
- data/spec/test_app/bin/rake +4 -0
- data/spec/test_app/bin/setup +29 -0
- data/spec/test_app/config/application.rb +32 -0
- data/spec/test_app/config/boot.rb +5 -0
- data/spec/test_app/config/database.yml +85 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +41 -0
- data/spec/test_app/config/environments/production.rb +79 -0
- data/spec/test_app/config/environments/test.rb +42 -0
- data/spec/test_app/config/initializers/assets.rb +11 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/test_app/config/initializers/inflections.rb +16 -0
- data/spec/test_app/config/initializers/mime_types.rb +4 -0
- data/spec/test_app/config/initializers/session_store.rb +3 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +23 -0
- data/spec/test_app/config/routes.rb +4 -0
- data/spec/test_app/config/secrets.yml +22 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/db/schema.rb +42 -0
- data/spec/test_app/log/development.log +2854 -0
- data/spec/test_app/log/test.log +40082 -0
- data/spec/test_app/public/404.html +67 -0
- data/spec/test_app/public/422.html +67 -0
- data/spec/test_app/public/500.html +66 -0
- data/spec/test_app/public/favicon.ico +0 -0
- metadata +262 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
{
|
2
|
+
"id": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
4
|
+
"description": "Core schema meta-schema",
|
5
|
+
"definitions": {
|
6
|
+
"schemaArray": {
|
7
|
+
"type": "array",
|
8
|
+
"minItems": 1,
|
9
|
+
"items": { "$ref": "#" }
|
10
|
+
},
|
11
|
+
"positiveInteger": {
|
12
|
+
"type": "integer",
|
13
|
+
"minimum": 0
|
14
|
+
},
|
15
|
+
"positiveIntegerDefault0": {
|
16
|
+
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
|
17
|
+
},
|
18
|
+
"simpleTypes": {
|
19
|
+
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
|
20
|
+
},
|
21
|
+
"stringArray": {
|
22
|
+
"type": "array",
|
23
|
+
"items": { "type": "string" },
|
24
|
+
"minItems": 1,
|
25
|
+
"uniqueItems": true
|
26
|
+
}
|
27
|
+
},
|
28
|
+
"type": "object",
|
29
|
+
"properties": {
|
30
|
+
"id": {
|
31
|
+
"type": "string",
|
32
|
+
"format": "uri"
|
33
|
+
},
|
34
|
+
"$schema": {
|
35
|
+
"type": "string",
|
36
|
+
"format": "uri"
|
37
|
+
},
|
38
|
+
"title": {
|
39
|
+
"type": "string"
|
40
|
+
},
|
41
|
+
"description": {
|
42
|
+
"type": "string"
|
43
|
+
},
|
44
|
+
"default": {},
|
45
|
+
"multipleOf": {
|
46
|
+
"type": "number",
|
47
|
+
"minimum": 0,
|
48
|
+
"exclusiveMinimum": true
|
49
|
+
},
|
50
|
+
"maximum": {
|
51
|
+
"type": "number"
|
52
|
+
},
|
53
|
+
"exclusiveMaximum": {
|
54
|
+
"type": "boolean",
|
55
|
+
"default": false
|
56
|
+
},
|
57
|
+
"minimum": {
|
58
|
+
"type": "number"
|
59
|
+
},
|
60
|
+
"exclusiveMinimum": {
|
61
|
+
"type": "boolean",
|
62
|
+
"default": false
|
63
|
+
},
|
64
|
+
"maxLength": { "$ref": "#/definitions/positiveInteger" },
|
65
|
+
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
66
|
+
"pattern": {
|
67
|
+
"type": "string",
|
68
|
+
"format": "regex"
|
69
|
+
},
|
70
|
+
"additionalItems": {
|
71
|
+
"anyOf": [
|
72
|
+
{ "type": "boolean" },
|
73
|
+
{ "$ref": "#" }
|
74
|
+
],
|
75
|
+
"default": {}
|
76
|
+
},
|
77
|
+
"items": {
|
78
|
+
"anyOf": [
|
79
|
+
{ "$ref": "#" },
|
80
|
+
{ "$ref": "#/definitions/schemaArray" }
|
81
|
+
],
|
82
|
+
"default": {}
|
83
|
+
},
|
84
|
+
"maxItems": { "$ref": "#/definitions/positiveInteger" },
|
85
|
+
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
86
|
+
"uniqueItems": {
|
87
|
+
"type": "boolean",
|
88
|
+
"default": false
|
89
|
+
},
|
90
|
+
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
|
91
|
+
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
92
|
+
"required": { "$ref": "#/definitions/stringArray" },
|
93
|
+
"additionalProperties": {
|
94
|
+
"anyOf": [
|
95
|
+
{ "type": "boolean" },
|
96
|
+
{ "$ref": "#" }
|
97
|
+
],
|
98
|
+
"default": {}
|
99
|
+
},
|
100
|
+
"definitions": {
|
101
|
+
"type": "object",
|
102
|
+
"additionalProperties": { "$ref": "#" },
|
103
|
+
"default": {}
|
104
|
+
},
|
105
|
+
"properties": {
|
106
|
+
"type": "object",
|
107
|
+
"additionalProperties": { "$ref": "#" },
|
108
|
+
"default": {}
|
109
|
+
},
|
110
|
+
"patternProperties": {
|
111
|
+
"type": "object",
|
112
|
+
"additionalProperties": { "$ref": "#" },
|
113
|
+
"default": {}
|
114
|
+
},
|
115
|
+
"dependencies": {
|
116
|
+
"type": "object",
|
117
|
+
"additionalProperties": {
|
118
|
+
"anyOf": [
|
119
|
+
{ "$ref": "#" },
|
120
|
+
{ "$ref": "#/definitions/stringArray" }
|
121
|
+
]
|
122
|
+
}
|
123
|
+
},
|
124
|
+
"enum": {
|
125
|
+
"type": "array",
|
126
|
+
"minItems": 1,
|
127
|
+
"uniqueItems": true
|
128
|
+
},
|
129
|
+
"type": {
|
130
|
+
"anyOf": [
|
131
|
+
{ "$ref": "#/definitions/simpleTypes" },
|
132
|
+
{
|
133
|
+
"type": "array",
|
134
|
+
"items": { "$ref": "#/definitions/simpleTypes" },
|
135
|
+
"minItems": 1,
|
136
|
+
"uniqueItems": true
|
137
|
+
}
|
138
|
+
]
|
139
|
+
},
|
140
|
+
"allOf": { "$ref": "#/definitions/schemaArray" },
|
141
|
+
"anyOf": { "$ref": "#/definitions/schemaArray" },
|
142
|
+
"oneOf": { "$ref": "#/definitions/schemaArray" },
|
143
|
+
"not": { "$ref": "#" }
|
144
|
+
},
|
145
|
+
"dependencies": {
|
146
|
+
"exclusiveMaximum": [ "maximum" ],
|
147
|
+
"exclusiveMinimum": [ "minimum" ]
|
148
|
+
},
|
149
|
+
"default": {}
|
150
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Strain
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
|
4
|
+
require "json-schema"
|
5
|
+
require "jsonapi-resources"
|
6
|
+
|
7
|
+
isolate_namespace Strain
|
8
|
+
|
9
|
+
config.autoload_paths += Dir["#{config.root}/lib/**/"]
|
10
|
+
|
11
|
+
config.generators do |g|
|
12
|
+
g.test_framework :rspec,
|
13
|
+
view_specs: false,
|
14
|
+
helper_specs: false,
|
15
|
+
routing_specs: true,
|
16
|
+
controller_specs: true,
|
17
|
+
request_specs: true
|
18
|
+
g.fixture_replacement :factory_girl, dir: "spec/factories"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Strain
|
2
|
+
module SchemaTools
|
3
|
+
class Defaults
|
4
|
+
|
5
|
+
def initialize(schema_hash, data_hash)
|
6
|
+
@schema = schema_hash
|
7
|
+
@data = data_hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_values
|
11
|
+
raise "Invalid schema." unless @schema["type"] == "object" && @schema.has_key?("properties")
|
12
|
+
property_defaults(@schema["properties"])
|
13
|
+
end
|
14
|
+
|
15
|
+
def data_with_defaults
|
16
|
+
merge_defaults(@data, default_values)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def property_defaults(properties_hash)
|
22
|
+
properties_hash.each_with_object({}) do | (prop_name, prop_schema), memo |
|
23
|
+
if prop_schema["type"] === "object" &&
|
24
|
+
prop_schema.has_key?("default")
|
25
|
+
memo[prop_name] = prop_schema["default"]
|
26
|
+
end
|
27
|
+
|
28
|
+
if prop_schema["type"] === "object" &&
|
29
|
+
prop_schema.has_key?("properties")
|
30
|
+
extracted_defaults = property_defaults(prop_schema["properties"])
|
31
|
+
memo[prop_name] = extracted_defaults unless extracted_defaults.empty?
|
32
|
+
elsif prop_schema.has_key?("default")
|
33
|
+
memo[prop_name] = prop_schema["default"]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge_defaults(data, defaults)
|
39
|
+
data.merge(defaults) do | key, data_value, default_value |
|
40
|
+
if data_value.is_a? Hash
|
41
|
+
merge_defaults(data_value, default_value)
|
42
|
+
elsif [nil, "", []].include?(data_value)
|
43
|
+
default_value
|
44
|
+
else
|
45
|
+
data_value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/strain.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "strain/engine"
|
2
|
+
|
3
|
+
module Strain
|
4
|
+
|
5
|
+
JSONAPI::Exceptions::ValidationErrors.module_eval do
|
6
|
+
def json_api_error(attr_key, message)
|
7
|
+
a = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR,
|
8
|
+
status: :unprocessable_entity,
|
9
|
+
title: message,
|
10
|
+
detail: "#{format_key(attr_key)} - #{message}",
|
11
|
+
source: { pointer: pointer(attr_key) })
|
12
|
+
# binding.pry
|
13
|
+
a
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
|
3
|
+
factory :article, :class => 'Strain::Article' do
|
4
|
+
body_hash = {
|
5
|
+
"summary": "this is a test article",
|
6
|
+
"main": "This is the main article body."
|
7
|
+
}
|
8
|
+
|
9
|
+
body body_hash
|
10
|
+
slug "test-article"
|
11
|
+
title "Test Article"
|
12
|
+
variant
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :variant, class: Strain::Variant do
|
3
|
+
schema_hash = {
|
4
|
+
"type": "object",
|
5
|
+
"properties": {
|
6
|
+
"summary": {"type": "string"},
|
7
|
+
"main": {"type": "string"}
|
8
|
+
},
|
9
|
+
"additionalProperties": false
|
10
|
+
}
|
11
|
+
name "test"
|
12
|
+
schema schema_hash
|
13
|
+
|
14
|
+
trait :nested_schema do
|
15
|
+
schema_hash = {
|
16
|
+
"type": "object",
|
17
|
+
"additionalProperties": false,
|
18
|
+
"properties": {
|
19
|
+
"summary": {"type": "string"},
|
20
|
+
"main": {
|
21
|
+
"type": "object",
|
22
|
+
"additionalProperties": false,
|
23
|
+
"properties": {
|
24
|
+
"section_title": {"type": "string"},
|
25
|
+
"section_text": {"type": "string"}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
schema schema_hash
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'strain/schema_tools/defaults.rb'
|
3
|
+
|
4
|
+
module Strain::SchemaTools
|
5
|
+
RSpec.describe Defaults do
|
6
|
+
subject(:d) { Defaults.new(schema, data) }
|
7
|
+
let(:schema) do
|
8
|
+
{ "type" => "object", "properties" => {
|
9
|
+
"title" => {"type" => "string", "default" => "default title"},
|
10
|
+
"summary" => {"type" => "string"},
|
11
|
+
"author" => {
|
12
|
+
"type" => "object", "properties" => {
|
13
|
+
"name" => {"type" => "string", "default" => "default author"},
|
14
|
+
"date" => {"type" => "integer", "default" => 2005}
|
15
|
+
} } } }
|
16
|
+
end
|
17
|
+
let(:data) do
|
18
|
+
{ "title" => nil,
|
19
|
+
"summary" => "test summary",
|
20
|
+
"author" => {
|
21
|
+
"name" => "Jane Author",
|
22
|
+
"date" => nil
|
23
|
+
} }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#default_values" do
|
27
|
+
subject(:default_values) {d.default_values}
|
28
|
+
|
29
|
+
context "with a simple schema" do
|
30
|
+
let(:schema) do
|
31
|
+
{ "type" => "object", "properties" => {
|
32
|
+
"title" => {"type" => "string", "default" => "default title"}
|
33
|
+
} }
|
34
|
+
end
|
35
|
+
it "returns a hash with all declared defaults" do
|
36
|
+
expect(default_values).to eq({"title" => "default title"})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with a typical schema" do
|
41
|
+
it "returns a hash with all declared defaults" do
|
42
|
+
expect(default_values).to eq(
|
43
|
+
{"title"=>"default title", "author"=>{"name"=>"default author", "date"=>2005}}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with a nested schema" do
|
49
|
+
let(:schema) do
|
50
|
+
{
|
51
|
+
"type" => "object", "properties" => {
|
52
|
+
"title" => {"type" => "string", "default" => "default title"},
|
53
|
+
"summary" => {"type" => "string"},
|
54
|
+
"author" => {
|
55
|
+
"type" => "object", "properties" => {
|
56
|
+
"name" => {"type" => "string", "default" => "default author"},
|
57
|
+
"date" => {"type" => "integer", "default" => 2005}
|
58
|
+
} } } }
|
59
|
+
end
|
60
|
+
it "returns a hash with all declared defaults" do
|
61
|
+
expect(default_values).to eq({
|
62
|
+
"title" => "default title",
|
63
|
+
"author" => {
|
64
|
+
"name" => "default author",
|
65
|
+
"date" => 2005
|
66
|
+
} })
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with array defaults" do
|
71
|
+
let(:schema) do
|
72
|
+
{ "type" => "object", "properties" => {
|
73
|
+
"numbers" => {"type" => "array", "default" => "[1, 2, 3]"}
|
74
|
+
} }
|
75
|
+
end
|
76
|
+
it "returns a hash with all declared defaults" do
|
77
|
+
expect(default_values).to eq({"numbers" => "[1, 2, 3]"})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "with non-conflicting object defaults" do
|
82
|
+
let(:schema) do
|
83
|
+
{ "type" => "object", "properties" => {
|
84
|
+
"obj_with_defaults" => {
|
85
|
+
"type" => "object",
|
86
|
+
"default" => {"inner_prop" => "test"},
|
87
|
+
"properties" => {"inner_prop" => { "type": "string" }}
|
88
|
+
} } }
|
89
|
+
end
|
90
|
+
it "returns a hash with all declared defaults" do
|
91
|
+
expect(default_values).to eq(
|
92
|
+
{"obj_with_defaults" => {"inner_prop" => "test"}}
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with conflicting object defaults" do
|
98
|
+
let(:schema) do
|
99
|
+
{ "type" => "object", "properties" => {
|
100
|
+
"obj_with_defaults" => {
|
101
|
+
"type" => "object",
|
102
|
+
"default" => {"inner_prop" => "outer_default"},
|
103
|
+
"properties" => {
|
104
|
+
"inner_prop" => {"type" => "string", "default" => "inner_default"}
|
105
|
+
} } } }
|
106
|
+
end
|
107
|
+
it "prioritizes inner defaults" do
|
108
|
+
expect(default_values).to eq(
|
109
|
+
{"obj_with_defaults" => {"inner_prop" => "inner_default"}}
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with no properties on an object and no defaults" do
|
115
|
+
let(:schema) do
|
116
|
+
{ "type" => "object", "properties" => {
|
117
|
+
"obj_with_defaults" => {
|
118
|
+
"type" => "object"
|
119
|
+
} } }
|
120
|
+
end
|
121
|
+
it "returns a blank hash" do
|
122
|
+
expect(default_values).to eq({})
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "with no properties on an object and defaults" do
|
127
|
+
let(:schema) do
|
128
|
+
{ "type" => "object", "properties" => {
|
129
|
+
"obj_with_defaults" => {
|
130
|
+
"type" => "object",
|
131
|
+
"default" => {"inner_prop" => "test"}
|
132
|
+
} } }
|
133
|
+
end
|
134
|
+
it "returns a hash with defaults set" do
|
135
|
+
expect(default_values).to eq({"obj_with_defaults" => {"inner_prop" => "test"}})
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "#data_with_defaults" do
|
141
|
+
subject(:data_with_defaults) {d.data_with_defaults}
|
142
|
+
|
143
|
+
context "with some missing data and basic schema" do
|
144
|
+
it "merges defaults with the data" do
|
145
|
+
expect(data_with_defaults).to eq({
|
146
|
+
"title" => "default title",
|
147
|
+
"summary" => "test summary",
|
148
|
+
"author" => {
|
149
|
+
"name" => "Jane Author",
|
150
|
+
"date" => 2005
|
151
|
+
} })
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "with blank strings" do
|
156
|
+
let(:data) do
|
157
|
+
{ "title" => "",
|
158
|
+
"summary" => "",
|
159
|
+
"author" => ""
|
160
|
+
}
|
161
|
+
end
|
162
|
+
it "replaces blank strings with defaults" do
|
163
|
+
expect(data_with_defaults).to eq({
|
164
|
+
"title" => "default title",
|
165
|
+
"summary" => "",
|
166
|
+
"author" => {
|
167
|
+
"name" => "default author",
|
168
|
+
"date" => 2005
|
169
|
+
} })
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "with blank arrays" do
|
174
|
+
let(:data) do
|
175
|
+
{"numbers" => []}
|
176
|
+
end
|
177
|
+
let(:schema) do
|
178
|
+
{ "type" => "object",
|
179
|
+
"properties" => {
|
180
|
+
"numbers" => {"type" => "array", "default" => [1,2,3]}
|
181
|
+
} }
|
182
|
+
end
|
183
|
+
it "replaces blank arrays with defaults" do
|
184
|
+
expect(data_with_defaults).to eq({
|
185
|
+
"numbers" => [1,2,3]
|
186
|
+
})
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "with blank arrays containing objects" do
|
191
|
+
let(:data) do
|
192
|
+
{"tags" => []}
|
193
|
+
end
|
194
|
+
let(:schema) do
|
195
|
+
{ "type" => "object",
|
196
|
+
"properties" => {
|
197
|
+
"tags" => {
|
198
|
+
"type" => "array",
|
199
|
+
"items" => {"type" => "string"},
|
200
|
+
"default" => [{"tag1" => "red"}, {"tag2"=>"green"}]
|
201
|
+
} } }
|
202
|
+
end
|
203
|
+
it "replaces blank arrays with defaults" do
|
204
|
+
expect(data_with_defaults).to eq({
|
205
|
+
"tags" => [{"tag1" => "red"}, {"tag2"=>"green"}]
|
206
|
+
})
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "with deeply nested objects" do
|
211
|
+
let(:data) do
|
212
|
+
{"second_level" => {} }
|
213
|
+
end
|
214
|
+
let(:schema) do
|
215
|
+
{ "type" => "object",
|
216
|
+
"properties" => {
|
217
|
+
"second_level" => {
|
218
|
+
"type" => "object",
|
219
|
+
"properties" => {
|
220
|
+
"third_level" => {
|
221
|
+
"type" => "object",
|
222
|
+
"default" => {"deep_property" => "test"}
|
223
|
+
} } } } }
|
224
|
+
end
|
225
|
+
it "merges defaults" do
|
226
|
+
expect(data_with_defaults).to eq({
|
227
|
+
"second_level"=>{"third_level"=>{"deep_property"=>"test"}}
|
228
|
+
})
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module Strain
|
4
|
+
RSpec.describe Article, type: :model do
|
5
|
+
|
6
|
+
it "has a valid factory" do
|
7
|
+
expect(build(:article)).to be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is invalid without a slug" do
|
11
|
+
expect(build(:article, slug: nil)).to be_invalid
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is invalid with a non-URL-safe slug" do
|
15
|
+
expect(build(:article, slug: "invalid slug")).to be_invalid
|
16
|
+
end
|
17
|
+
|
18
|
+
it "is invalid without a variant" do
|
19
|
+
expect(build(:article, variant: nil)).to be_invalid
|
20
|
+
end
|
21
|
+
|
22
|
+
it "is invalid without a body" do
|
23
|
+
expect(build(:article, body: nil)).to be_invalid
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is invalid without a title" do
|
27
|
+
expect(build(:article, title: nil)).to be_invalid
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises a database error with a nonunique slug" do
|
31
|
+
a = create(:article)
|
32
|
+
b = build(:article, slug: a.slug, variant: a.variant)
|
33
|
+
expect {b.save(validate: false)}.to raise_error(ActiveRecord::RecordNotUnique)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "is invalid with a nonunique slug" do
|
37
|
+
a = create(:article)
|
38
|
+
b = build(:article, slug: a.slug, variant: a.variant)
|
39
|
+
expect(b).to be_invalid
|
40
|
+
end
|
41
|
+
|
42
|
+
it "is valid when compliant with its Variant" do
|
43
|
+
v = create(:variant)
|
44
|
+
a = build(:article, variant: v, body: {"summary": "test", "main": "test"})
|
45
|
+
expect(a).to be_valid
|
46
|
+
end
|
47
|
+
|
48
|
+
it "is invalid when not compliant with its Variant" do
|
49
|
+
v = create(:variant)
|
50
|
+
a = build(:article, variant: v, body: {"extra_property": "test", "main": "test"})
|
51
|
+
expect(a).to be_invalid
|
52
|
+
end
|
53
|
+
|
54
|
+
it "fills in schema-defined defaults" do
|
55
|
+
v = build(:variant, schema: {
|
56
|
+
"type": "object",
|
57
|
+
"properties": {
|
58
|
+
"title": {
|
59
|
+
"type": "string",
|
60
|
+
"default": "default Title"
|
61
|
+
}
|
62
|
+
}
|
63
|
+
})
|
64
|
+
a = create(:article, variant: v, body:{})
|
65
|
+
expect(a.body["title"]).to eq("default Title")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "provides detailed errors when invalid according to JSON schema"
|
69
|
+
it "enforces key format? - hyphenated, underscores, camelcase?"
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "rails_helper"
|
2
|
+
|
3
|
+
module Strain
|
4
|
+
RSpec.describe Variant, type: :model do
|
5
|
+
|
6
|
+
it "has a valid factory" do
|
7
|
+
expect(build(:variant)).to be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is invalid without a name" do
|
11
|
+
expect(build(:variant, name: nil)).to be_invalid
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is invalid without a Schema" do
|
15
|
+
expect(build(:variant, schema: nil)).to be_invalid
|
16
|
+
expect(build(:variant, schema: {})).to be_invalid
|
17
|
+
end
|
18
|
+
|
19
|
+
it "is invalid with an invalid JSON Schema" do
|
20
|
+
expect(build(:variant, schema: {"type": "nonsense"})).to be_invalid
|
21
|
+
end
|
22
|
+
|
23
|
+
it "raises a database error with a nonunique name" do
|
24
|
+
a = create(:variant)
|
25
|
+
b = build(:variant, name: a.name)
|
26
|
+
expect {b.save(validate: false)}.to raise_error(ActiveRecord::RecordNotUnique)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is invalid with a nonunique name" do
|
30
|
+
a = create(:variant)
|
31
|
+
b = build(:variant, name: a.name)
|
32
|
+
expect(b).to be_invalid
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is invalid unless root schema type is object" do
|
36
|
+
a = build(:variant, schema: {"type": "string"})
|
37
|
+
expect(a).to be_invalid
|
38
|
+
expect(a.errors.messages[:schema]).to include("must have an object as root element.")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|