strain 0.0.1

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.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +17 -0
  6. data/Guardfile +72 -0
  7. data/LICENSE +20 -0
  8. data/README.md +74 -0
  9. data/Rakefile +26 -0
  10. data/app/assets/javascripts/strain/jsonapi/articles.js +2 -0
  11. data/app/assets/javascripts/strain/jsonapi/variants.js +2 -0
  12. data/app/assets/stylesheets/strain/jsonapi/articles.css +4 -0
  13. data/app/assets/stylesheets/strain/jsonapi/variants.css +4 -0
  14. data/app/controllers/strain/application_controller.rb +5 -0
  15. data/app/controllers/strain/jsonapi/articles_controller.rb +6 -0
  16. data/app/controllers/strain/jsonapi/jsonapi_controller.rb +10 -0
  17. data/app/controllers/strain/jsonapi/variants_controller.rb +7 -0
  18. data/app/helpers/strain/jsonapi/articles_helper.rb +4 -0
  19. data/app/helpers/strain/jsonapi/variants_helper.rb +4 -0
  20. data/app/models/strain/article.rb +37 -0
  21. data/app/models/strain/variant.rb +32 -0
  22. data/app/resources/strain/jsonapi/article_resource.rb +21 -0
  23. data/app/resources/strain/jsonapi/variant_resource.rb +16 -0
  24. data/bin/_guard-core +16 -0
  25. data/bin/guard +16 -0
  26. data/bin/rails +12 -0
  27. data/bin/rake +16 -0
  28. data/bin/rspec +16 -0
  29. data/config/initializers/jsonapi_resources.rb +4 -0
  30. data/config/routes.rb +8 -0
  31. data/db/migrate/20151103060035_enable_uuid.rb +5 -0
  32. data/db/migrate/20151103060224_create_strain_variants.rb +11 -0
  33. data/db/migrate/20151104200908_create_strain_articles.rb +14 -0
  34. data/lib/json_schema/draft_4_meta_schema.json +150 -0
  35. data/lib/strain/engine.rb +21 -0
  36. data/lib/strain/schema_tools/defaults.rb +53 -0
  37. data/lib/strain/version.rb +3 -0
  38. data/lib/strain.rb +17 -0
  39. data/lib/tasks/strain_tasks.rake +4 -0
  40. data/spec/factories/strain_articles.rb +15 -0
  41. data/spec/factories/strain_variants.rb +34 -0
  42. data/spec/lib/strain/schema_tools/defaults_spec.rb +234 -0
  43. data/spec/models/strain/article_spec.rb +72 -0
  44. data/spec/models/strain/variant_spec.rb +42 -0
  45. data/spec/rails_helper.rb +59 -0
  46. data/spec/requests/strain/jsonapi/articles_spec.rb +220 -0
  47. data/spec/requests/strain/jsonapi/variants_spec.rb +159 -0
  48. data/spec/spec_helper.rb +99 -0
  49. data/spec/test_app/README.rdoc +28 -0
  50. data/spec/test_app/Rakefile +6 -0
  51. data/spec/test_app/app/assets/javascripts/application.js +13 -0
  52. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  53. data/spec/test_app/app/controllers/application_controller.rb +5 -0
  54. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  55. data/spec/test_app/app/models/tweet.rb +4 -0
  56. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  57. data/spec/test_app/bin/bundle +3 -0
  58. data/spec/test_app/bin/rails +4 -0
  59. data/spec/test_app/bin/rake +4 -0
  60. data/spec/test_app/bin/setup +29 -0
  61. data/spec/test_app/config/application.rb +32 -0
  62. data/spec/test_app/config/boot.rb +5 -0
  63. data/spec/test_app/config/database.yml +85 -0
  64. data/spec/test_app/config/environment.rb +5 -0
  65. data/spec/test_app/config/environments/development.rb +41 -0
  66. data/spec/test_app/config/environments/production.rb +79 -0
  67. data/spec/test_app/config/environments/test.rb +42 -0
  68. data/spec/test_app/config/initializers/assets.rb +11 -0
  69. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  70. data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
  71. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  72. data/spec/test_app/config/initializers/inflections.rb +16 -0
  73. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  74. data/spec/test_app/config/initializers/session_store.rb +3 -0
  75. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  76. data/spec/test_app/config/locales/en.yml +23 -0
  77. data/spec/test_app/config/routes.rb +4 -0
  78. data/spec/test_app/config/secrets.yml +22 -0
  79. data/spec/test_app/config.ru +4 -0
  80. data/spec/test_app/db/schema.rb +42 -0
  81. data/spec/test_app/log/development.log +2854 -0
  82. data/spec/test_app/log/test.log +40082 -0
  83. data/spec/test_app/public/404.html +67 -0
  84. data/spec/test_app/public/422.html +67 -0
  85. data/spec/test_app/public/500.html +66 -0
  86. data/spec/test_app/public/favicon.ico +0 -0
  87. 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
@@ -0,0 +1,3 @@
1
+ module Strain
2
+ VERSION = "0.0.1"
3
+ 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,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :strain do
3
+ # # Task goes here
4
+ # 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