locomotivecms_mounter 1.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/locomotive/mounter/config.rb +21 -0
- data/lib/locomotive/mounter/engine_api.rb +40 -0
- data/lib/locomotive/mounter/exceptions.rb +38 -0
- data/lib/locomotive/mounter/extensions/compass.rb +36 -0
- data/lib/locomotive/mounter/extensions/httmultiparty.rb +22 -0
- data/lib/locomotive/mounter/extensions/tilt/css.rb +31 -0
- data/lib/locomotive/mounter/extensions/tilt/haml.rb +27 -0
- data/lib/locomotive/mounter/extensions/tilt/liquid.rb +23 -0
- data/lib/locomotive/mounter/extensions/tilt/template.rb +11 -0
- data/lib/locomotive/mounter/fields.rb +250 -0
- data/lib/locomotive/mounter/models/base.rb +41 -0
- data/lib/locomotive/mounter/models/content_asset.rb +84 -0
- data/lib/locomotive/mounter/models/content_entry.rb +290 -0
- data/lib/locomotive/mounter/models/content_field.rb +128 -0
- data/lib/locomotive/mounter/models/content_select_option.rb +29 -0
- data/lib/locomotive/mounter/models/content_type.rb +217 -0
- data/lib/locomotive/mounter/models/editable_element.rb +27 -0
- data/lib/locomotive/mounter/models/page.rb +377 -0
- data/lib/locomotive/mounter/models/site.rb +27 -0
- data/lib/locomotive/mounter/models/snippet.rb +55 -0
- data/lib/locomotive/mounter/models/theme_asset.rb +135 -0
- data/lib/locomotive/mounter/models/translation.rb +28 -0
- data/lib/locomotive/mounter/mounting_point.rb +49 -0
- data/lib/locomotive/mounter/reader/api/base.rb +49 -0
- data/lib/locomotive/mounter/reader/api/content_assets_reader.rb +40 -0
- data/lib/locomotive/mounter/reader/api/content_entries_reader.rb +141 -0
- data/lib/locomotive/mounter/reader/api/content_types_reader.rb +74 -0
- data/lib/locomotive/mounter/reader/api/pages_reader.rb +174 -0
- data/lib/locomotive/mounter/reader/api/site_reader.rb +37 -0
- data/lib/locomotive/mounter/reader/api/snippets_reader.rb +59 -0
- data/lib/locomotive/mounter/reader/api/theme_assets_reader.rb +42 -0
- data/lib/locomotive/mounter/reader/api/translations_reader.rb +28 -0
- data/lib/locomotive/mounter/reader/api.rb +49 -0
- data/lib/locomotive/mounter/reader/file_system/base.rb +65 -0
- data/lib/locomotive/mounter/reader/file_system/content_assets_reader.rb +88 -0
- data/lib/locomotive/mounter/reader/file_system/content_entries_reader.rb +101 -0
- data/lib/locomotive/mounter/reader/file_system/content_types_reader.rb +88 -0
- data/lib/locomotive/mounter/reader/file_system/pages_reader.rb +206 -0
- data/lib/locomotive/mounter/reader/file_system/site_reader.rb +24 -0
- data/lib/locomotive/mounter/reader/file_system/snippets_reader.rb +78 -0
- data/lib/locomotive/mounter/reader/file_system/theme_assets_reader.rb +78 -0
- data/lib/locomotive/mounter/reader/file_system/translations_reader.rb +36 -0
- data/lib/locomotive/mounter/reader/file_system.rb +42 -0
- data/lib/locomotive/mounter/reader/runner.rb +89 -0
- data/lib/locomotive/mounter/utils/hash.rb +31 -0
- data/lib/locomotive/mounter/utils/string.rb +17 -0
- data/lib/locomotive/mounter/utils/yaml.rb +125 -0
- data/lib/locomotive/mounter/version.rb +8 -0
- data/lib/locomotive/mounter/writer/api/base.rb +323 -0
- data/lib/locomotive/mounter/writer/api/content_assets_writer.rb +74 -0
- data/lib/locomotive/mounter/writer/api/content_entries_writer.rb +223 -0
- data/lib/locomotive/mounter/writer/api/content_types_writer.rb +151 -0
- data/lib/locomotive/mounter/writer/api/pages_writer.rb +225 -0
- data/lib/locomotive/mounter/writer/api/site_writer.rb +164 -0
- data/lib/locomotive/mounter/writer/api/snippets_writer.rb +111 -0
- data/lib/locomotive/mounter/writer/api/theme_assets_writer.rb +152 -0
- data/lib/locomotive/mounter/writer/api/translations_writer.rb +83 -0
- data/lib/locomotive/mounter/writer/api.rb +62 -0
- data/lib/locomotive/mounter/writer/file_system/base.rb +61 -0
- data/lib/locomotive/mounter/writer/file_system/content_assets_writer.rb +33 -0
- data/lib/locomotive/mounter/writer/file_system/content_entries_writer.rb +29 -0
- data/lib/locomotive/mounter/writer/file_system/content_types_writer.rb +27 -0
- data/lib/locomotive/mounter/writer/file_system/pages_writer.rb +73 -0
- data/lib/locomotive/mounter/writer/file_system/site_writer.rb +25 -0
- data/lib/locomotive/mounter/writer/file_system/snippets_writer.rb +54 -0
- data/lib/locomotive/mounter/writer/file_system/theme_assets_writer.rb +35 -0
- data/lib/locomotive/mounter/writer/file_system/translations_writer.rb +22 -0
- data/lib/locomotive/mounter/writer/file_system.rb +69 -0
- data/lib/locomotive/mounter/writer/runner.rb +68 -0
- data/lib/locomotive/mounter.rb +97 -0
- metadata +487 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
module Locomotive
|
2
|
+
module Mounter
|
3
|
+
module Models
|
4
|
+
|
5
|
+
class ContentEntry < Base
|
6
|
+
|
7
|
+
## fields ##
|
8
|
+
field :_slug, localized: true
|
9
|
+
field :_position, default: 0
|
10
|
+
field :_visible, default: true
|
11
|
+
field :seo_title, localized: true
|
12
|
+
field :meta_keywords, localized: true
|
13
|
+
field :meta_description, localized: true
|
14
|
+
|
15
|
+
field :content_type, association: true
|
16
|
+
|
17
|
+
attr_accessor :dynamic_attributes, :main_locale, :errors
|
18
|
+
|
19
|
+
alias :_permalink :_slug
|
20
|
+
alias :_permalink= :_slug=
|
21
|
+
|
22
|
+
## callbacks ##
|
23
|
+
set_callback :initialize, :after, :set_slug
|
24
|
+
set_callback :initialize, :after, :set_default_main_locale
|
25
|
+
|
26
|
+
## methods ##
|
27
|
+
|
28
|
+
# Return the internal label used to identify a content entry
|
29
|
+
# in a YAML file for instance. It is based on the first field
|
30
|
+
# of the related content type.
|
31
|
+
#
|
32
|
+
# @return [ String ] The internal label
|
33
|
+
#
|
34
|
+
def _label
|
35
|
+
name = self.content_type.label_field_name
|
36
|
+
self.dynamic_getter(name)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Process a minimal validation by checking if the required fields
|
40
|
+
# are filled in or not.
|
41
|
+
#
|
42
|
+
# @return [ Boolean ] False if one of the required fields is missing.
|
43
|
+
#
|
44
|
+
def valid?
|
45
|
+
self.errors = []
|
46
|
+
self.content_type.fields.each do |field|
|
47
|
+
if field.required
|
48
|
+
if self.dynamic_getter(field.name).blank?
|
49
|
+
self.errors << field.name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
self.errors.blank?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the list of the fields defined in the content type
|
57
|
+
# for which there is a value assigned.
|
58
|
+
#
|
59
|
+
# @return [ Array ] The list of fields
|
60
|
+
#
|
61
|
+
def dynamic_fields
|
62
|
+
self.dynamic_attributes.keys.map do |name|
|
63
|
+
self.content_type.find_field(name)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Loop over the list of dynamic fields defined in
|
68
|
+
# the content type for which there is a value assigned.
|
69
|
+
#
|
70
|
+
# @example: each_dynamic_field { |field, value| .... }
|
71
|
+
#
|
72
|
+
def each_dynamic_field(&block)
|
73
|
+
return unless block_given?
|
74
|
+
|
75
|
+
self.dynamic_fields.each do |field|
|
76
|
+
value = self.localized_dynamic_attribute_value(field)
|
77
|
+
|
78
|
+
# value = (self.dynamic_attributes || {})[field.name.to_sym]
|
79
|
+
# value = value.try(:[], Locomotive::Mounter.locale) unless field.is_relationship? || !field.localized
|
80
|
+
|
81
|
+
block.call(field, value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Determine if field passed in parameter is one of the dynamic fields.
|
86
|
+
#
|
87
|
+
# @param [ String/Symbol ] name Name of the dynamic field
|
88
|
+
#
|
89
|
+
# @return [ Boolean ] True if it is a dynamic field
|
90
|
+
#
|
91
|
+
def is_dynamic_field?(name)
|
92
|
+
name = name.to_s.gsub(/\=$/, '').to_sym
|
93
|
+
!self.content_type.find_field(name).nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return the value of a dynamic field and cast it depending
|
97
|
+
# on the type of the field (string, date, belongs_to, ...etc).
|
98
|
+
#
|
99
|
+
# @param [ String/Symbol ] name Name of the dynamic field
|
100
|
+
#
|
101
|
+
# @return [ Object ] The casted value (String, Date, ContentEntry, ...etc)
|
102
|
+
#
|
103
|
+
def dynamic_getter(name)
|
104
|
+
field = self.content_type.find_field(name)
|
105
|
+
|
106
|
+
value = self.localized_dynamic_attribute_value(field)
|
107
|
+
|
108
|
+
case field.type
|
109
|
+
when :string, :text, :select, :boolean, :category
|
110
|
+
value
|
111
|
+
when :date
|
112
|
+
value.is_a?(String) ? Date.parse(value) : value
|
113
|
+
when :file
|
114
|
+
{ 'url' => value }
|
115
|
+
when :belongs_to
|
116
|
+
field.klass.find_entry(value)
|
117
|
+
when :has_many
|
118
|
+
field.klass.find_entries_by(field.inverse_of, [self._label, self._permalink])
|
119
|
+
when :many_to_many
|
120
|
+
field.klass.find_entries_among(value)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Set the value of a dynamic field. If the value is a hash,
|
125
|
+
# it assumes that it represents the translations.
|
126
|
+
#
|
127
|
+
# @param [ String/Symbol ] name Name of the dynamic field
|
128
|
+
# @param [ Object ] value Value to set
|
129
|
+
#
|
130
|
+
def dynamic_setter(name, value)
|
131
|
+
self.dynamic_attributes ||= {}
|
132
|
+
self.dynamic_attributes[name.to_sym] ||= {}
|
133
|
+
|
134
|
+
field = self.content_type.find_field(name)
|
135
|
+
|
136
|
+
if value.is_a?(Hash) # already localized
|
137
|
+
value.keys.each { |locale| self.add_locale(locale) }
|
138
|
+
self.dynamic_attributes[name.to_sym].merge!(value.symbolize_keys)
|
139
|
+
else
|
140
|
+
if field.is_relationship? || !field.localized
|
141
|
+
self.dynamic_attributes[name.to_sym] = value
|
142
|
+
else
|
143
|
+
self.add_locale(Locomotive::Mounter.locale)
|
144
|
+
self.dynamic_attributes[name.to_sym][Locomotive::Mounter.locale] = value
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# The magic of dynamic fields happens within this method.
|
150
|
+
# It calls the getter/setter of a dynamic field if it is one of them.
|
151
|
+
def method_missing(name, *args, &block)
|
152
|
+
if self.is_dynamic_field?(name)
|
153
|
+
if name.to_s.ends_with?('=')
|
154
|
+
name = name.to_s.gsub(/\=$/, '').to_sym
|
155
|
+
self.dynamic_setter(name, args.first)
|
156
|
+
else
|
157
|
+
self.dynamic_getter(name)
|
158
|
+
end
|
159
|
+
else
|
160
|
+
super
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns a hash with the label_field value as the key and the other fields as the value
|
165
|
+
#
|
166
|
+
# @param [ Boolean ] nested True to have a hash of hash (whose key is the label)
|
167
|
+
#
|
168
|
+
# @return [ Hash ] A simple hash (nested to false) or a hash of hash
|
169
|
+
#
|
170
|
+
def to_hash(nested = true)
|
171
|
+
# no need of _position and _visible (unless it's false)
|
172
|
+
hash = super.delete_if { |k, v| k == '_position' || (k == '_visible' && v == true) }
|
173
|
+
|
174
|
+
# also no need of the content type
|
175
|
+
hash.delete('content_type')
|
176
|
+
|
177
|
+
# dynamic attributes
|
178
|
+
hash.merge!(self.dynamic_attributes.deep_stringify_keys)
|
179
|
+
|
180
|
+
# no need of the translation of the field name in the current locale
|
181
|
+
label_field = self.content_type.label_field
|
182
|
+
|
183
|
+
if label_field.localized && !hash[label_field.name].empty?
|
184
|
+
hash[label_field.name].delete(Locomotive::Mounter.locale.to_s)
|
185
|
+
|
186
|
+
hash.delete(label_field.name) if hash[label_field.name].empty?
|
187
|
+
end
|
188
|
+
|
189
|
+
nested ? { self._label => hash } : hash
|
190
|
+
end
|
191
|
+
|
192
|
+
# Return the main default params used for the API, meaning all except
|
193
|
+
# the dynamic fields which have to be defined outside the model.
|
194
|
+
#
|
195
|
+
# @return [ Hash ] The params
|
196
|
+
#
|
197
|
+
def to_params
|
198
|
+
self.filter_attributes %w(_slug _position _visible seo_title meta_keywords meta_description)
|
199
|
+
end
|
200
|
+
|
201
|
+
def to_s
|
202
|
+
"#{self.content_type.slug} / #{self._slug}"
|
203
|
+
end
|
204
|
+
|
205
|
+
protected
|
206
|
+
|
207
|
+
# Sets the slug of the instance by using the value of the highlighted field
|
208
|
+
# (if available). If a sibling content instance has the same permalink then a
|
209
|
+
# unique one will be generated.
|
210
|
+
# It applies that to every translated version of the content entry.
|
211
|
+
def set_slug
|
212
|
+
self.translated_in.each do |locale|
|
213
|
+
Locomotive::Mounter.with_locale(locale) do
|
214
|
+
self._slug = self._label.dup if self._label.present?
|
215
|
+
|
216
|
+
if self._slug.blank?
|
217
|
+
self._slug = self.content_type.send(:label_to_slug)
|
218
|
+
end
|
219
|
+
|
220
|
+
self._slug.permalink!
|
221
|
+
|
222
|
+
self._slug = self.next_unique_slug if self.slug_already_taken?
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Once the entry has been initialized, we keep track of the current locale
|
228
|
+
#
|
229
|
+
def set_default_main_locale
|
230
|
+
self.main_locale = Locomotive::Mounter.locale
|
231
|
+
end
|
232
|
+
|
233
|
+
# Return the next available unique slug as a string
|
234
|
+
#
|
235
|
+
# @return [ String] An unique permalink (or slug)
|
236
|
+
#
|
237
|
+
def next_unique_slug
|
238
|
+
slug = self._slug.gsub(/-\d*$/, '')
|
239
|
+
next_number = 0
|
240
|
+
|
241
|
+
self.content_type.entries.each do |entry|
|
242
|
+
if entry._permalink =~ /^#{slug}-?(\d*)$/i
|
243
|
+
next_number = $1.to_i if $1.to_i > next_number
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
[slug, next_number + 1].join('-')
|
248
|
+
end
|
249
|
+
|
250
|
+
def slug_already_taken?
|
251
|
+
entry = self.content_type.find_entry(self._slug)
|
252
|
+
entry.try(:_slug) == self._slug
|
253
|
+
end
|
254
|
+
|
255
|
+
# Return the value of a dynamic attribute specified by its
|
256
|
+
# corresponding content field.
|
257
|
+
# If that attribute is localized and in the current locale
|
258
|
+
# its value is nil, it returns the value in the main locale.
|
259
|
+
#
|
260
|
+
# @param [ Object ] The content field
|
261
|
+
#
|
262
|
+
# @return [ Object ] The value
|
263
|
+
#
|
264
|
+
def localized_dynamic_attribute_value(field)
|
265
|
+
value = (self.dynamic_attributes || {})[field.name.to_sym]
|
266
|
+
|
267
|
+
# puts "[#{field.name.inspect}] #{value.inspect} / #{field.localized.inspect} / #{value.is_a?(Hash).inspect}"
|
268
|
+
|
269
|
+
if !field.is_relationship? && field.localized && value.is_a?(Hash)
|
270
|
+
# get the localized value for the current locale
|
271
|
+
_value = value[Locomotive::Mounter.locale]
|
272
|
+
|
273
|
+
# puts "[#{field.name}] _value = #{value.inspect} / current #{Locomotive::Mounter.locale.inspect} / main #{self.main_locale.inspect}"
|
274
|
+
|
275
|
+
# no value for the current locale, give a try to the main one
|
276
|
+
if _value.nil? && Locomotive::Mounter.locale != self.main_locale
|
277
|
+
_value = value[self.main_locale]
|
278
|
+
end
|
279
|
+
|
280
|
+
value = _value
|
281
|
+
end
|
282
|
+
|
283
|
+
value #.tap { |v| puts "[#{field.name}] returning #{v.inspect}" }
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Locomotive
|
2
|
+
module Mounter
|
3
|
+
module Models
|
4
|
+
|
5
|
+
class ContentField < Base
|
6
|
+
|
7
|
+
## fields ##
|
8
|
+
field :label
|
9
|
+
field :name
|
10
|
+
field :type, default: :string
|
11
|
+
field :hint
|
12
|
+
field :position, default: 0
|
13
|
+
field :required, default: false
|
14
|
+
field :localized, default: false
|
15
|
+
|
16
|
+
# text
|
17
|
+
field :text_formatting
|
18
|
+
|
19
|
+
# select
|
20
|
+
field :select_options, type: :array, class_name: 'Locomotive::Mounter::Models::ContentSelectOption'
|
21
|
+
|
22
|
+
# relationships: belongs_to, has_many, many_to_many
|
23
|
+
field :class_name
|
24
|
+
field :inverse_of
|
25
|
+
field :order_by
|
26
|
+
field :ui_enabled
|
27
|
+
|
28
|
+
alias :target :class_name
|
29
|
+
alias :target= :class_name=
|
30
|
+
|
31
|
+
## callbacks ##
|
32
|
+
set_callback :initialize, :after, :prepare_attributes
|
33
|
+
|
34
|
+
## other accessors ##
|
35
|
+
attr_accessor :_destroy
|
36
|
+
|
37
|
+
## methods ##
|
38
|
+
|
39
|
+
def prepare_attributes
|
40
|
+
self.label ||= self.name.try(:humanize)
|
41
|
+
self.type = self.type.to_sym
|
42
|
+
self.sanitize
|
43
|
+
end
|
44
|
+
|
45
|
+
# Tell if it describes a relationship (belongs_to, many_to_many, has_many) or not.
|
46
|
+
#
|
47
|
+
# @return [ Boolean ] True if describing a relationship
|
48
|
+
#
|
49
|
+
def is_relationship?
|
50
|
+
%w(belongs_to has_many many_to_many).include?(self.type.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the content type matching the class_name / target attribute
|
54
|
+
#
|
55
|
+
# @return [ Object ] The matching Content Type
|
56
|
+
#
|
57
|
+
def klass
|
58
|
+
(@klass ||= self.mounting_point.content_types[self.class_name]).tap do |klass|
|
59
|
+
if klass.nil?
|
60
|
+
raise UnknownContentTypeException.new("unknow content type #{self.class_name}")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Find a select option by its name IN the current locale.
|
66
|
+
#
|
67
|
+
# @param [ String / Symbol] name_or_id Name or Id of the option
|
68
|
+
#
|
69
|
+
# @return [ Object ] The select option or nil if not found
|
70
|
+
#
|
71
|
+
def find_select_option(name_or_id)
|
72
|
+
return nil if self.select_options.blank?
|
73
|
+
self.select_options.detect { |option| option.name.to_s == name_or_id.to_s || option._id == name_or_id }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Instead of returning a simple hash, it returns a hash with name as the key and
|
77
|
+
# the remaining attributes as the value.
|
78
|
+
#
|
79
|
+
# @return [ Hash ] A hash of hash
|
80
|
+
#
|
81
|
+
def to_hash
|
82
|
+
hash = super.delete_if { |k, v| %w(name position).include?(k) }
|
83
|
+
{ self.name => hash }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Return the params used for the API.
|
87
|
+
#
|
88
|
+
# @return [ Hash ] The params
|
89
|
+
#
|
90
|
+
def to_params
|
91
|
+
params = self.filter_attributes %w(label name type hint position required localized)
|
92
|
+
|
93
|
+
# we set the _id / _destroy attributes for embedded documents
|
94
|
+
params[:_id] = self._id if self.persisted?
|
95
|
+
params[:_destroy] = self._destroy if self._destroy
|
96
|
+
|
97
|
+
case self.type
|
98
|
+
when :text
|
99
|
+
params[:text_formatting] = self.text_formatting
|
100
|
+
when :select
|
101
|
+
params[:raw_select_options] = self.select_options.map(&:to_params)
|
102
|
+
when :belongs_to
|
103
|
+
params[:class_name] = self.class_name
|
104
|
+
when :has_many, :many_to_many
|
105
|
+
%w(class_name inverse_of order_by ui_enabled).each do |name|
|
106
|
+
params[name.to_sym] = self.send(name.to_sym)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
params
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
# Clean up useless properties depending on its type
|
116
|
+
def sanitize
|
117
|
+
# ui_enabled only for the belongs_to, has_many and many_to_many types
|
118
|
+
self.ui_enabled = nil unless self.is_relationship?
|
119
|
+
|
120
|
+
# text_formatting only for the text type
|
121
|
+
self.text_formatting = nil unless self.type == :text
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Locomotive
|
2
|
+
module Mounter
|
3
|
+
module Models
|
4
|
+
|
5
|
+
class ContentSelectOption < Base
|
6
|
+
|
7
|
+
## fields ##
|
8
|
+
field :name, localized: true
|
9
|
+
field :position, default: 0
|
10
|
+
|
11
|
+
## methods ##
|
12
|
+
|
13
|
+
# Return the params used for the API.
|
14
|
+
#
|
15
|
+
# @param [ Hash ] options For now, none
|
16
|
+
#
|
17
|
+
# @return [ Hash ] The params
|
18
|
+
#
|
19
|
+
def to_params(options = nil)
|
20
|
+
{ name: self.name_translations, position: self.position }.tap do |params|
|
21
|
+
params[:id] = self._id if self.persisted?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
module Locomotive
|
2
|
+
module Mounter
|
3
|
+
module Models
|
4
|
+
|
5
|
+
class ContentType < Base
|
6
|
+
|
7
|
+
## fields ##
|
8
|
+
field :name
|
9
|
+
field :description
|
10
|
+
field :slug
|
11
|
+
field :label_field_name
|
12
|
+
field :group_by
|
13
|
+
field :order_by
|
14
|
+
field :order_direction
|
15
|
+
field :public_submission_enabled
|
16
|
+
field :public_submission_accounts
|
17
|
+
field :raw_item_template
|
18
|
+
|
19
|
+
field :fields, type: :array, class_name: 'Locomotive::Mounter::Models::ContentField'
|
20
|
+
|
21
|
+
field :entries, association: true
|
22
|
+
|
23
|
+
## callbacks ##
|
24
|
+
set_callback :initialize, :after, :sanitize
|
25
|
+
set_callback :initialize, :after, :assign_mounting_point_to_fields
|
26
|
+
|
27
|
+
## other accessors ##
|
28
|
+
attr_accessor :klass_name, :group_by_field_id
|
29
|
+
|
30
|
+
## aliases ##
|
31
|
+
alias_method :group_by_field_name, :group_by
|
32
|
+
alias_method :group_by_field_name=, :group_by=
|
33
|
+
|
34
|
+
## methods ##
|
35
|
+
|
36
|
+
# Return the label field (by the default the first field)
|
37
|
+
#
|
38
|
+
# @return [ Object ] The label field
|
39
|
+
#
|
40
|
+
def label_field
|
41
|
+
self.find_field(self.label_field_name) || self.fields.first
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return the group_by field
|
45
|
+
#
|
46
|
+
# @return [ Object ] The group_by field
|
47
|
+
#
|
48
|
+
def group_by_field
|
49
|
+
self.fields.find_field(self.group_by_field_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Build a content entry and add it to the list of entries of the content type.
|
53
|
+
# The content type will be referenced into the newly built entry .
|
54
|
+
#
|
55
|
+
# @param [ Hash ] attributes Attributes of the new content entry
|
56
|
+
#
|
57
|
+
# @return [ Object ] The newly built content entry
|
58
|
+
#
|
59
|
+
def build_entry(attributes)
|
60
|
+
ContentEntry.new(content_type: self).tap do |entry|
|
61
|
+
# do not forget that we are manipulating dynamic fields
|
62
|
+
attributes.each do |k, v|
|
63
|
+
begin
|
64
|
+
entry.send(:"#{k}=", v)
|
65
|
+
rescue NoMethodError => e
|
66
|
+
Mounter.logger.error e.backtrace
|
67
|
+
raise FieldDoesNotExistException.new("The '#{self.slug}' content type does not have a field named '#{k}'.")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# force the slug to be defined from its label and in all the locales
|
72
|
+
entry.send :set_slug
|
73
|
+
|
74
|
+
(self.entries ||= []) << entry
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Tell if the content type owns a field which defines
|
79
|
+
# a relationship to another content type.
|
80
|
+
#
|
81
|
+
# @return [ Boolean ] True if a relationship field exists
|
82
|
+
#
|
83
|
+
def with_relationships?
|
84
|
+
self.fields.any? { |field| field.is_relationship? }
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return the list of fields which do not describe
|
88
|
+
# a relationship.
|
89
|
+
#
|
90
|
+
# @return [ Array ] The list of fields.
|
91
|
+
#
|
92
|
+
def non_relationship_fields
|
93
|
+
self.fields.select { |field| !field.is_relationship? }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Find a field by its name (string or symbol) or its id (API)
|
97
|
+
#
|
98
|
+
# @param [ String / Symbol] name_or_id Name or Id of the field
|
99
|
+
#
|
100
|
+
# @return [ Object ] The field if it exists or nil
|
101
|
+
#
|
102
|
+
def find_field(name_or_id)
|
103
|
+
self.fields.detect { |field| field.name.to_s == name_or_id.to_s || field._id == name_or_id }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Find a content entry by its ids (ie: _permalink or _label)
|
107
|
+
#
|
108
|
+
# @param [ String ] id A permalink or a label
|
109
|
+
#
|
110
|
+
# @return [ Object ] The content entry if it exists or nil
|
111
|
+
#
|
112
|
+
def find_entry(id)
|
113
|
+
(self.entries || []).detect { |entry| [entry._permalink, entry._label].include?(id) }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Find all the entries whose their _permalink or _label is among the ids
|
117
|
+
# passed in parameter.
|
118
|
+
#
|
119
|
+
# @param [ Array ] ids List of permalinks or labels
|
120
|
+
#
|
121
|
+
# @return [ Array ] List of content entries or [] if none
|
122
|
+
#
|
123
|
+
def find_entries_among(ids)
|
124
|
+
(self.entries || []).find_all { |entry| [*ids].any? { |v| [entry._permalink, entry._label].include?(v) } }
|
125
|
+
end
|
126
|
+
|
127
|
+
# Find all the entries by a field and its value.
|
128
|
+
#
|
129
|
+
# @param [ String ] name Name of the field
|
130
|
+
# @param [ String / Array ] value The different value of the field to test
|
131
|
+
#
|
132
|
+
# @return [ Array ] List of content entries or [] if none
|
133
|
+
#
|
134
|
+
def find_entries_by(name, value)
|
135
|
+
(self.entries || []).find_all { |entry| [*value].include?(entry.send(name.to_sym)) }
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return the params used for the API.
|
139
|
+
# The options parameter can be used to get all the fields even
|
140
|
+
# the ones describing a relationship with another content type.
|
141
|
+
#
|
142
|
+
# @param [ Hash ] options Default values: { all_fields: false }
|
143
|
+
#
|
144
|
+
# @return [ Hash ] The params
|
145
|
+
#
|
146
|
+
def to_params(options = nil)
|
147
|
+
options = { all_fields: false }.merge(options || {})
|
148
|
+
|
149
|
+
params = self.filter_attributes %w(name slug description label_field_name group_by_field_name order_by order_by_direction public_submission_enabled raw_item_template)
|
150
|
+
|
151
|
+
# order by
|
152
|
+
params[:order_by] = '_position' if self.order_by == 'manually'
|
153
|
+
|
154
|
+
# fields
|
155
|
+
_fields = options[:all_fields] ? self.fields : self.non_relationship_fields
|
156
|
+
params[:entries_custom_fields] = _fields.map(&:to_params)
|
157
|
+
|
158
|
+
params
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_s
|
162
|
+
self.name
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
# Give an unique slug based on a label and within the scope of the content type.
|
168
|
+
#
|
169
|
+
# @param [ String ] label The label. If nil, we take the singularized version of the content type slug.
|
170
|
+
#
|
171
|
+
# @return [ String ] An unique slug
|
172
|
+
#
|
173
|
+
def label_to_slug(label = nil)
|
174
|
+
label ||= self.slug.singularize
|
175
|
+
base, index = label.parameterize('-'), 1
|
176
|
+
unique_slug = base
|
177
|
+
|
178
|
+
while self.find_entry(unique_slug)
|
179
|
+
unique_slug = "#{base}-#{index}"
|
180
|
+
index += 1
|
181
|
+
end
|
182
|
+
|
183
|
+
unique_slug
|
184
|
+
end
|
185
|
+
|
186
|
+
# Method used to clean up the content and its fields.
|
187
|
+
# Besides, it also sets the values defined by other attributes.
|
188
|
+
def sanitize
|
189
|
+
# if no label_field_name provided, take the first field
|
190
|
+
unless self.label_field_name
|
191
|
+
self.label_field_name = (self.fields || []).first.try(:name)
|
192
|
+
end
|
193
|
+
|
194
|
+
# define group_by_field from group_by_field_id
|
195
|
+
if self.group_by_field_id
|
196
|
+
self.group_by_field_name = self.find_field(self.group_by_field_id)
|
197
|
+
end
|
198
|
+
|
199
|
+
# public_submission_accounts means public_submission_enabled set to true
|
200
|
+
self.public_submission_enabled = true if self.public_submission_accounts.is_a?(Array)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Each field should have a reference to the mounting point
|
204
|
+
#
|
205
|
+
def assign_mounting_point_to_fields
|
206
|
+
return if self.fields.blank?
|
207
|
+
|
208
|
+
self.fields.each do |field|
|
209
|
+
field.mounting_point = self.mounting_point
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Locomotive
|
2
|
+
module Mounter
|
3
|
+
module Models
|
4
|
+
|
5
|
+
class EditableElement < Base
|
6
|
+
|
7
|
+
## fields ##
|
8
|
+
field :content, localized: true
|
9
|
+
|
10
|
+
## other accessors
|
11
|
+
attr_accessor :block, :slug
|
12
|
+
|
13
|
+
## methods ##
|
14
|
+
|
15
|
+
def to_params
|
16
|
+
{ block: self.block, slug: self.slug, content: self.content }
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_yaml
|
20
|
+
{ "#{block}/#{slug}" => content }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|