wcc-contentful-app 0.2.2
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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/README.md +0 -0
- data/lib/generators/wcc/USAGE +24 -0
- data/lib/generators/wcc/model_generator.rb +90 -0
- data/lib/generators/wcc/templates/.keep +0 -0
- data/lib/generators/wcc/templates/Procfile +3 -0
- data/lib/generators/wcc/templates/contentful_shell_wrapper +385 -0
- data/lib/generators/wcc/templates/menu/generated_add_menus.ts +192 -0
- data/lib/generators/wcc/templates/menu/models/menu.rb +23 -0
- data/lib/generators/wcc/templates/menu/models/menu_button.rb +23 -0
- data/lib/generators/wcc/templates/page/generated_add_pages.ts +50 -0
- data/lib/generators/wcc/templates/page/models/page.rb +23 -0
- data/lib/generators/wcc/templates/release +9 -0
- data/lib/generators/wcc/templates/wcc_contentful.rb +17 -0
- data/lib/tasks/validate.rake +20 -0
- data/lib/wcc/contentful/app.rb +17 -0
- data/lib/wcc/contentful/app/exceptions.rb +36 -0
- data/lib/wcc/contentful/app/model_validators.rb +121 -0
- data/lib/wcc/contentful/app/model_validators/dsl.rb +166 -0
- data/lib/wcc/contentful/app/version.rb +9 -0
- data/lib/wcc/contentful/ext/model.rb +5 -0
- data/lib/wcc/contentful/model/dropdown_menu.rb +7 -0
- data/lib/wcc/contentful/model/menu.rb +6 -0
- data/lib/wcc/contentful/model/menu_button.rb +16 -0
- data/lib/wcc/contentful/model/page.rb +8 -0
- data/lib/wcc/contentful/model/redirect.rb +19 -0
- data/wcc-contentful-app.gemspec +60 -0
- metadata +442 -0
@@ -0,0 +1,192 @@
|
|
1
|
+
|
2
|
+
import Migration from 'contentful-migration-cli'
|
3
|
+
|
4
|
+
export = function (migration: Migration) {
|
5
|
+
const menu = migration.createContentType('menu', {
|
6
|
+
displayField: 'name',
|
7
|
+
name: 'Menu',
|
8
|
+
description: 'A Menu contains a number of Menu Buttons or other Menus, ' +
|
9
|
+
'which will be rendered as drop-downs.'
|
10
|
+
})
|
11
|
+
|
12
|
+
menu.createField('name', {
|
13
|
+
name: 'Menu Name',
|
14
|
+
type: 'Symbol',
|
15
|
+
localized: false,
|
16
|
+
required: true,
|
17
|
+
validations: [],
|
18
|
+
disabled: false,
|
19
|
+
omitted: false
|
20
|
+
})
|
21
|
+
|
22
|
+
menu.createField('items', {
|
23
|
+
name: 'Items',
|
24
|
+
type: 'Array',
|
25
|
+
localized: false,
|
26
|
+
required: false,
|
27
|
+
validations: [],
|
28
|
+
disabled: false,
|
29
|
+
omitted: false,
|
30
|
+
items:
|
31
|
+
{
|
32
|
+
type: 'Link',
|
33
|
+
validations:
|
34
|
+
[{
|
35
|
+
linkContentType:
|
36
|
+
['dropdownMenu',
|
37
|
+
'menuButton'],
|
38
|
+
message: 'The items must be either buttons or drop-down menus.'
|
39
|
+
}],
|
40
|
+
linkType: 'Entry'
|
41
|
+
}
|
42
|
+
})
|
43
|
+
|
44
|
+
menu.changeEditorInterface('name', 'singleLine')
|
45
|
+
menu.changeEditorInterface('items', 'entryLinksEditor')
|
46
|
+
|
47
|
+
const menubutton = migration.createContentType('menuButton', {
|
48
|
+
displayField: 'text',
|
49
|
+
name: 'Menu Button',
|
50
|
+
description: 'A Menu Button is a clickable button that goes on a Menu. It has a link to a Page or a URL.'
|
51
|
+
})
|
52
|
+
|
53
|
+
menubutton.createField('text', {
|
54
|
+
name: 'Text',
|
55
|
+
type: 'Symbol',
|
56
|
+
localized: false,
|
57
|
+
required: true,
|
58
|
+
validations:
|
59
|
+
[{
|
60
|
+
size:
|
61
|
+
{
|
62
|
+
min: 1,
|
63
|
+
max: 60
|
64
|
+
},
|
65
|
+
message: 'A Menu Button should have a very short text field - ideally a single word. Please limit the text to 60 characters.'
|
66
|
+
}],
|
67
|
+
disabled: false,
|
68
|
+
omitted: false
|
69
|
+
})
|
70
|
+
|
71
|
+
menubutton.createField('icon', {
|
72
|
+
name: 'Icon',
|
73
|
+
type: 'Link',
|
74
|
+
localized: false,
|
75
|
+
required: false,
|
76
|
+
validations: [{ linkMimetypeGroup: ['image'] }],
|
77
|
+
disabled: false,
|
78
|
+
omitted: false,
|
79
|
+
linkType: 'Asset'
|
80
|
+
})
|
81
|
+
|
82
|
+
menubutton.createField('externalLink', {
|
83
|
+
name: 'External Link',
|
84
|
+
type: 'Symbol',
|
85
|
+
localized: false,
|
86
|
+
required: false,
|
87
|
+
validations:
|
88
|
+
[{
|
89
|
+
regexp: { pattern: '^(\\w+):(\\/\\/)?(\\w+:{0,1}\\w*@)?((\\w+\\.)+[^\\s\\/#]+)(:[0-9]+)?(\\/|(\\/|\\#)([\\w#!:.?+=&%@!\\-\\/]+))?$|^(\\/|(\\/|\\#)([\\w#!:.?+=&%@!\\-\\/]+))$' },
|
90
|
+
message: 'The external link must be a URL like \'https://www.watermark.org/\', a mailto url like \'mailto:info@watermark.org\', or a relative URL like \'#location-on-page\''
|
91
|
+
}],
|
92
|
+
disabled: false,
|
93
|
+
omitted: false
|
94
|
+
})
|
95
|
+
|
96
|
+
menubutton.createField('link', {
|
97
|
+
name: 'Page Link',
|
98
|
+
type: 'Link',
|
99
|
+
localized: false,
|
100
|
+
required: false,
|
101
|
+
validations:
|
102
|
+
[{
|
103
|
+
linkContentType: ['page'],
|
104
|
+
message: 'The Page Link must be a link to a Page which has a slug.'
|
105
|
+
}],
|
106
|
+
disabled: false,
|
107
|
+
omitted: false,
|
108
|
+
linkType: 'Entry'
|
109
|
+
})
|
110
|
+
|
111
|
+
menubutton.createField('ionIcon', {
|
112
|
+
name: 'Ion Icon',
|
113
|
+
type: 'Symbol',
|
114
|
+
localized: false,
|
115
|
+
required: false,
|
116
|
+
validations:
|
117
|
+
[{
|
118
|
+
regexp: { pattern: '^ion-[a-z\\-]+$' },
|
119
|
+
message: 'The icon should start with \'ion-\', like \'ion-arrow-down-c\'. See http://ionicons.com/'
|
120
|
+
}],
|
121
|
+
disabled: false,
|
122
|
+
omitted: false
|
123
|
+
})
|
124
|
+
|
125
|
+
menubutton.createField('style', {
|
126
|
+
name: 'Style',
|
127
|
+
type: 'Symbol',
|
128
|
+
localized: false,
|
129
|
+
required: false,
|
130
|
+
validations: [{ in: ['oval-border'] }],
|
131
|
+
disabled: false,
|
132
|
+
omitted: false
|
133
|
+
})
|
134
|
+
|
135
|
+
menubutton.changeEditorInterface('text', 'singleLine')
|
136
|
+
menubutton.changeEditorInterface('icon', 'assetLinkEditor')
|
137
|
+
menubutton.changeEditorInterface('externalLink', 'singleLine')
|
138
|
+
menubutton.changeEditorInterface('link', 'entryLinkEditor')
|
139
|
+
menubutton.changeEditorInterface('ionIcon', 'singleLine')
|
140
|
+
menubutton.changeEditorInterface('style', 'dropdown')
|
141
|
+
|
142
|
+
const dropdownmenu = migration.createContentType('dropdownMenu', {
|
143
|
+
displayField: 'name',
|
144
|
+
name: 'Dropdown Menu',
|
145
|
+
description: 'A Dropdown Menu can be attached to a main menu to show additional menu items on click.'
|
146
|
+
})
|
147
|
+
|
148
|
+
dropdownmenu.createField('name', {
|
149
|
+
name: 'Menu Name',
|
150
|
+
type: 'Symbol',
|
151
|
+
localized: false,
|
152
|
+
required: false,
|
153
|
+
validations: [],
|
154
|
+
disabled: false,
|
155
|
+
omitted: false
|
156
|
+
})
|
157
|
+
|
158
|
+
dropdownmenu.createField('label', {
|
159
|
+
name: 'Menu Label',
|
160
|
+
type: 'Link',
|
161
|
+
localized: false,
|
162
|
+
required: false,
|
163
|
+
validations: [{ linkContentType: ['menuButton'] }],
|
164
|
+
disabled: false,
|
165
|
+
omitted: false,
|
166
|
+
linkType: 'Entry'
|
167
|
+
})
|
168
|
+
|
169
|
+
dropdownmenu.createField('items', {
|
170
|
+
name: 'Items',
|
171
|
+
type: 'Array',
|
172
|
+
localized: false,
|
173
|
+
required: false,
|
174
|
+
validations: [],
|
175
|
+
disabled: false,
|
176
|
+
omitted: false,
|
177
|
+
items:
|
178
|
+
{
|
179
|
+
type: 'Link',
|
180
|
+
validations:
|
181
|
+
[{
|
182
|
+
linkContentType:
|
183
|
+
['menuButton']
|
184
|
+
}],
|
185
|
+
linkType: 'Entry'
|
186
|
+
}
|
187
|
+
})
|
188
|
+
|
189
|
+
dropdownmenu.changeEditorInterface('name', 'singleLine')
|
190
|
+
dropdownmenu.changeEditorInterface('label', 'entryLinkEditor')
|
191
|
+
dropdownmenu.changeEditorInterface('items', 'entryLinksEditor')
|
192
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This model represents the 'menu' content type in Contentful. Any linked
|
4
|
+
# entries of the 'menu' content type will be resolved as instances of this class.
|
5
|
+
# It exposes #find, #find_by, and #find_all methods to query Contentful.
|
6
|
+
class Menu < WCC::Contentful::Model::Menu
|
7
|
+
# Add custom validations to ensure that app-specific properties exist:
|
8
|
+
# validate_field :foo, :String, :required
|
9
|
+
# validate_field :bar_links, :Array, link_to: %w[bar baz]
|
10
|
+
|
11
|
+
# Override functionality or add utilities
|
12
|
+
#
|
13
|
+
# # Example: override equality
|
14
|
+
# def ===(other)
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Example: override "name" attribute to always be camelized.
|
19
|
+
# # `@name` is populated by the gem in the initializer.
|
20
|
+
# def name
|
21
|
+
# @name_camelized ||= @name.camelize(true)
|
22
|
+
# end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This model represents the 'menuButton' content type in Contentful. Any linked
|
4
|
+
# entries of the 'menuButton' content type will be resolved as instances of this class.
|
5
|
+
# It exposes #find, #find_by, and #find_all methods to query Contentful.
|
6
|
+
class MenuButton < WCC::Contentful::Model::MenuButton
|
7
|
+
# Add custom validations to ensure that app-specific properties exist:
|
8
|
+
# validate_field :foo, :String, :required
|
9
|
+
# validate_field :bar_links, :Array, link_to: %w[bar baz]
|
10
|
+
|
11
|
+
# Override functionality or add utilities
|
12
|
+
#
|
13
|
+
# # Example: override equality
|
14
|
+
# def ===(other)
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Example: override "text" attribute to always be camelized.
|
19
|
+
# # `@text` is populated by the gem in the initializer.
|
20
|
+
# def text
|
21
|
+
# @text_camelized ||= @text.camelize(true)
|
22
|
+
# end
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import Migration from 'contentful-migration-cli'
|
2
|
+
|
3
|
+
export = function (migration: Migration) {
|
4
|
+
const page = migration.createContentType('page')
|
5
|
+
.name('Page')
|
6
|
+
.description('A page describes a collection of sections that correspond' +
|
7
|
+
'to a URL slug')
|
8
|
+
.displayField('title')
|
9
|
+
|
10
|
+
page.createField('title')
|
11
|
+
.name('Title')
|
12
|
+
.type('Symbol')
|
13
|
+
.required(true)
|
14
|
+
|
15
|
+
page.createField('slug')
|
16
|
+
.name('Slug')
|
17
|
+
.type('Symbol')
|
18
|
+
.required(true)
|
19
|
+
.validations([
|
20
|
+
{
|
21
|
+
unique: true
|
22
|
+
},
|
23
|
+
{
|
24
|
+
regexp: { pattern: "(\\/|\\/([\w#!:.?+=&%@!\\-\\/]))?$" },
|
25
|
+
message: "The slug must look like the path part of a URL and begin with a forward slash, example: '/my-page-slug'"
|
26
|
+
}
|
27
|
+
])
|
28
|
+
|
29
|
+
page.createField('sections')
|
30
|
+
.name('Sections')
|
31
|
+
.type('Array')
|
32
|
+
.items({
|
33
|
+
type: 'Link',
|
34
|
+
linkType: 'Entry'
|
35
|
+
})
|
36
|
+
|
37
|
+
page.createField('subpages')
|
38
|
+
.name('Subpages')
|
39
|
+
.type('Array')
|
40
|
+
.items({
|
41
|
+
type: 'Link',
|
42
|
+
linkType: 'Entry',
|
43
|
+
validations: [
|
44
|
+
{
|
45
|
+
linkContentType: [ 'page' ]
|
46
|
+
}
|
47
|
+
]
|
48
|
+
})
|
49
|
+
|
50
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This model represents the 'page' content type in Contentful. Any linked
|
4
|
+
# entries of the 'page' content type will be resolved as instances of this class.
|
5
|
+
# It exposes #find, #find_by, and #find_all methods to query Contentful.
|
6
|
+
class Page < WCC::Contentful::Model::Page
|
7
|
+
# Add custom validations to ensure that app-specific properties exist:
|
8
|
+
# validate_field :foo, :String, :required
|
9
|
+
# validate_field :bar_links, :Array, link_to: %w[bar baz]
|
10
|
+
|
11
|
+
# Override functionality or add utilities
|
12
|
+
#
|
13
|
+
# # Example: override equality
|
14
|
+
# def ===(other)
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Example: override "title" attribute to always be titlecase.
|
19
|
+
# # `@title` is populated by the gem in the initializer.
|
20
|
+
# def title
|
21
|
+
# @title_titlecased ||= @title.titlecase
|
22
|
+
# end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
WCC::Contentful.configure do |config|
|
3
|
+
# Required
|
4
|
+
config.access_token = # Contentful CDN access token
|
5
|
+
config.space = # Contentful Space ID
|
6
|
+
|
7
|
+
# Optional
|
8
|
+
config.management_token = # Contentful API management token
|
9
|
+
config.default_locale = # Set default locale, if left blank this is 'en-US'
|
10
|
+
config.content_delivery = # :direct, :eager_sync, or :lazy_sync
|
11
|
+
end
|
12
|
+
|
13
|
+
# Download content types, build models, and sync content
|
14
|
+
WCC::Contentful.init!
|
15
|
+
|
16
|
+
# Validate that models conform to a defined specification
|
17
|
+
WCC::Contentful.validate_models! unless defined?(Rails) && Rails.env.development?
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :wcc_contentful do
|
4
|
+
desc 'Validates content types in your space against the validations defined on your models'
|
5
|
+
task :validate, :environment do |_t|
|
6
|
+
# Ensure application models are loaded before we validate
|
7
|
+
Rails.application.eager_load!
|
8
|
+
|
9
|
+
client = Services.instance.management_client ||
|
10
|
+
Services.instance.client
|
11
|
+
|
12
|
+
content_types = client.content_types(limit: 1000).items
|
13
|
+
|
14
|
+
content_types = WCC::Contentful::ModelValidators
|
15
|
+
.transform_content_types_for_validation(content_types)
|
16
|
+
|
17
|
+
errors = WCC::Contentful::Model.schema.call(content_types)
|
18
|
+
raise WCC::Contentful::ValidationError, errors.errors unless errors.success?
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './app/exceptions'
|
4
|
+
require_relative './app/model_validators'
|
5
|
+
require_relative './ext/model'
|
6
|
+
|
7
|
+
module WCC::Contentful::App
|
8
|
+
def self.init!
|
9
|
+
raise ArgumentError, 'Please first call WCC::Contentful.init!' unless WCC::Contentful.types
|
10
|
+
|
11
|
+
# Extend all model types w/ validation & extra fields
|
12
|
+
WCC::Contentful.types.each_value do |t|
|
13
|
+
file = File.dirname(__FILE__) + "/model/#{t.name.underscore}.rb"
|
14
|
+
require file if File.exist?(file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WCC::Contentful::App
|
4
|
+
# Raised by {WCC::Contentful.validate_models!} if a content type in the space
|
5
|
+
# does not match the validation defined on the associated model.
|
6
|
+
class ValidationError < StandardError
|
7
|
+
Message =
|
8
|
+
Struct.new(:path, :error) do
|
9
|
+
def to_s
|
10
|
+
"#{path}: #{error}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :errors
|
15
|
+
|
16
|
+
def initialize(errors)
|
17
|
+
@errors = ValidationError.join_msg_keys(errors)
|
18
|
+
super("Content Type Schema from Contentful failed validation!\n #{@errors.join("\n ")}")
|
19
|
+
end
|
20
|
+
|
21
|
+
# Turns the error messages hash into an array of message structs like:
|
22
|
+
# menu.fields.name.type: must be equal to String
|
23
|
+
def self.join_msg_keys(hash)
|
24
|
+
ret =
|
25
|
+
hash.map do |k, v|
|
26
|
+
if v.is_a?(Hash)
|
27
|
+
msgs = join_msg_keys(v)
|
28
|
+
msgs.map { |msg| Message.new(k.to_s + '.' + msg.path, msg.error) }
|
29
|
+
else
|
30
|
+
v.map { |msg| Message.new(k.to_s, msg) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
ret.flatten(1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry-validation'
|
4
|
+
|
5
|
+
require_relative 'model_validators/dsl'
|
6
|
+
|
7
|
+
module WCC::Contentful::App::ModelValidators
|
8
|
+
def schema
|
9
|
+
return if validations.nil? || validations.empty?
|
10
|
+
|
11
|
+
all_field_validations =
|
12
|
+
validations.each_with_object({}) do |(content_type, procs), h|
|
13
|
+
next if procs.empty?
|
14
|
+
|
15
|
+
# "page": {
|
16
|
+
# "sys": { ... }
|
17
|
+
# "fields": {
|
18
|
+
# "title": { ... },
|
19
|
+
# "sections": { ... },
|
20
|
+
# ...
|
21
|
+
# }
|
22
|
+
# }
|
23
|
+
h[content_type] =
|
24
|
+
Dry::Validation.Schema do
|
25
|
+
# Had to dig through the internals of Dry::Validation to find
|
26
|
+
# this magic incantation
|
27
|
+
procs.each { |dsl| instance_eval(&dsl.to_proc) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Dry::Validation.Schema do
|
32
|
+
all_field_validations.each do |content_type, fields_schema|
|
33
|
+
required(content_type).schema do
|
34
|
+
required('fields').schema(fields_schema)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validations
|
41
|
+
# This needs to be a class variable so that subclasses defined in application
|
42
|
+
# code can add to the total package of model validations
|
43
|
+
# rubocop:disable Style/ClassVars
|
44
|
+
@@validations ||= {}
|
45
|
+
# rubocop:enable Style/ClassVars
|
46
|
+
end
|
47
|
+
|
48
|
+
# Accepts a block which uses the {dry-validation DSL}[http://dry-rb.org/gems/dry-validation/]
|
49
|
+
# to validate the 'fields' object of a content type.
|
50
|
+
def validate_fields(&block)
|
51
|
+
raise ArgumentError, 'validate_fields requires a block' unless block_given?
|
52
|
+
|
53
|
+
dsl = ProcDsl.new(Proc.new(&block))
|
54
|
+
|
55
|
+
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
56
|
+
(validations[ct] ||= []) << dsl
|
57
|
+
end
|
58
|
+
|
59
|
+
# Validates a single field is of the expected type.
|
60
|
+
# Type expectations are one of:
|
61
|
+
#
|
62
|
+
# [:String] the field type must be `Symbol` or `Text`
|
63
|
+
# [:Int] the field type must be `Integer`
|
64
|
+
# [:Float] the field type must be `Number`
|
65
|
+
# [:DateTime] the field type must be 'Date'
|
66
|
+
# [:Asset] the field must be a link and the `linkType` must be `Asset`
|
67
|
+
# [:Link] the field must be a link and the `linkType` must be `Entry`.
|
68
|
+
# [:Location] the field type must be `Location`
|
69
|
+
# [:Boolean] the field type must be `Boolean`
|
70
|
+
# [:Json] the field type must be `Json` - a json blob.
|
71
|
+
# [:Array] the field must be a List.
|
72
|
+
#
|
73
|
+
# Additional validation options can be enforced:
|
74
|
+
#
|
75
|
+
# [:required] the 'Required Field' checkbox must be checked
|
76
|
+
# [:optional] the 'Required Field' checkbox must not be checked
|
77
|
+
# [:link_to] (only `:Link` or `:Array` type) the given content type(s) must be
|
78
|
+
# checked in the 'Accept only specified entry type' validations
|
79
|
+
# Example:
|
80
|
+
# validate_field :button, :Link, link_to: ['button', 'altButton']
|
81
|
+
#
|
82
|
+
# [:items] (only `:Array` type) the items of the list must be of the given type.
|
83
|
+
# Example:
|
84
|
+
# validate_field :my_strings, :Array, items: :String
|
85
|
+
#
|
86
|
+
# Examples:
|
87
|
+
# see WCC::Contentful::Model::Menu and WCC::Contentful::Model::MenuButton
|
88
|
+
def validate_field(field, type, *options)
|
89
|
+
dsl = FieldDsl.new(field, type, options)
|
90
|
+
|
91
|
+
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
92
|
+
(validations[ct] ||= []) << dsl
|
93
|
+
end
|
94
|
+
|
95
|
+
def no_validate_field(field)
|
96
|
+
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
97
|
+
return unless v = validations[ct]
|
98
|
+
|
99
|
+
field = field.to_s.camelize(:lower) unless field.is_a?(String)
|
100
|
+
v.reject! { |dsl| dsl.try(:field) == field }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Accepts a content types response from the API and transforms it
|
104
|
+
# to be acceptible for the validator.
|
105
|
+
def self.transform_content_types_for_validation(content_types)
|
106
|
+
if !content_types.is_a?(Array) && items = content_types.try(:[], 'items')
|
107
|
+
content_types = items
|
108
|
+
end
|
109
|
+
|
110
|
+
# Transform the array into a hash keyed by content type ID
|
111
|
+
content_types.each_with_object({}) do |ct, ct_hash|
|
112
|
+
# Transform the fields into a hash keyed by field ID
|
113
|
+
ct['fields'] =
|
114
|
+
ct['fields'].each_with_object({}) do |f, f_hash|
|
115
|
+
f_hash[f['id']] = f
|
116
|
+
end
|
117
|
+
|
118
|
+
ct_hash[ct.dig('sys', 'id')] = ct
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|