locomotivecms_mounter 1.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/lib/locomotive/mounter/config.rb +21 -0
  2. data/lib/locomotive/mounter/engine_api.rb +40 -0
  3. data/lib/locomotive/mounter/exceptions.rb +38 -0
  4. data/lib/locomotive/mounter/extensions/compass.rb +36 -0
  5. data/lib/locomotive/mounter/extensions/httmultiparty.rb +22 -0
  6. data/lib/locomotive/mounter/extensions/tilt/css.rb +31 -0
  7. data/lib/locomotive/mounter/extensions/tilt/haml.rb +27 -0
  8. data/lib/locomotive/mounter/extensions/tilt/liquid.rb +23 -0
  9. data/lib/locomotive/mounter/extensions/tilt/template.rb +11 -0
  10. data/lib/locomotive/mounter/fields.rb +250 -0
  11. data/lib/locomotive/mounter/models/base.rb +41 -0
  12. data/lib/locomotive/mounter/models/content_asset.rb +84 -0
  13. data/lib/locomotive/mounter/models/content_entry.rb +290 -0
  14. data/lib/locomotive/mounter/models/content_field.rb +128 -0
  15. data/lib/locomotive/mounter/models/content_select_option.rb +29 -0
  16. data/lib/locomotive/mounter/models/content_type.rb +217 -0
  17. data/lib/locomotive/mounter/models/editable_element.rb +27 -0
  18. data/lib/locomotive/mounter/models/page.rb +377 -0
  19. data/lib/locomotive/mounter/models/site.rb +27 -0
  20. data/lib/locomotive/mounter/models/snippet.rb +55 -0
  21. data/lib/locomotive/mounter/models/theme_asset.rb +135 -0
  22. data/lib/locomotive/mounter/models/translation.rb +28 -0
  23. data/lib/locomotive/mounter/mounting_point.rb +49 -0
  24. data/lib/locomotive/mounter/reader/api/base.rb +49 -0
  25. data/lib/locomotive/mounter/reader/api/content_assets_reader.rb +40 -0
  26. data/lib/locomotive/mounter/reader/api/content_entries_reader.rb +141 -0
  27. data/lib/locomotive/mounter/reader/api/content_types_reader.rb +74 -0
  28. data/lib/locomotive/mounter/reader/api/pages_reader.rb +174 -0
  29. data/lib/locomotive/mounter/reader/api/site_reader.rb +37 -0
  30. data/lib/locomotive/mounter/reader/api/snippets_reader.rb +59 -0
  31. data/lib/locomotive/mounter/reader/api/theme_assets_reader.rb +42 -0
  32. data/lib/locomotive/mounter/reader/api/translations_reader.rb +28 -0
  33. data/lib/locomotive/mounter/reader/api.rb +49 -0
  34. data/lib/locomotive/mounter/reader/file_system/base.rb +65 -0
  35. data/lib/locomotive/mounter/reader/file_system/content_assets_reader.rb +88 -0
  36. data/lib/locomotive/mounter/reader/file_system/content_entries_reader.rb +101 -0
  37. data/lib/locomotive/mounter/reader/file_system/content_types_reader.rb +88 -0
  38. data/lib/locomotive/mounter/reader/file_system/pages_reader.rb +206 -0
  39. data/lib/locomotive/mounter/reader/file_system/site_reader.rb +24 -0
  40. data/lib/locomotive/mounter/reader/file_system/snippets_reader.rb +78 -0
  41. data/lib/locomotive/mounter/reader/file_system/theme_assets_reader.rb +78 -0
  42. data/lib/locomotive/mounter/reader/file_system/translations_reader.rb +36 -0
  43. data/lib/locomotive/mounter/reader/file_system.rb +42 -0
  44. data/lib/locomotive/mounter/reader/runner.rb +89 -0
  45. data/lib/locomotive/mounter/utils/hash.rb +31 -0
  46. data/lib/locomotive/mounter/utils/string.rb +17 -0
  47. data/lib/locomotive/mounter/utils/yaml.rb +125 -0
  48. data/lib/locomotive/mounter/version.rb +8 -0
  49. data/lib/locomotive/mounter/writer/api/base.rb +323 -0
  50. data/lib/locomotive/mounter/writer/api/content_assets_writer.rb +74 -0
  51. data/lib/locomotive/mounter/writer/api/content_entries_writer.rb +223 -0
  52. data/lib/locomotive/mounter/writer/api/content_types_writer.rb +151 -0
  53. data/lib/locomotive/mounter/writer/api/pages_writer.rb +225 -0
  54. data/lib/locomotive/mounter/writer/api/site_writer.rb +164 -0
  55. data/lib/locomotive/mounter/writer/api/snippets_writer.rb +111 -0
  56. data/lib/locomotive/mounter/writer/api/theme_assets_writer.rb +152 -0
  57. data/lib/locomotive/mounter/writer/api/translations_writer.rb +83 -0
  58. data/lib/locomotive/mounter/writer/api.rb +62 -0
  59. data/lib/locomotive/mounter/writer/file_system/base.rb +61 -0
  60. data/lib/locomotive/mounter/writer/file_system/content_assets_writer.rb +33 -0
  61. data/lib/locomotive/mounter/writer/file_system/content_entries_writer.rb +29 -0
  62. data/lib/locomotive/mounter/writer/file_system/content_types_writer.rb +27 -0
  63. data/lib/locomotive/mounter/writer/file_system/pages_writer.rb +73 -0
  64. data/lib/locomotive/mounter/writer/file_system/site_writer.rb +25 -0
  65. data/lib/locomotive/mounter/writer/file_system/snippets_writer.rb +54 -0
  66. data/lib/locomotive/mounter/writer/file_system/theme_assets_writer.rb +35 -0
  67. data/lib/locomotive/mounter/writer/file_system/translations_writer.rb +22 -0
  68. data/lib/locomotive/mounter/writer/file_system.rb +69 -0
  69. data/lib/locomotive/mounter/writer/runner.rb +68 -0
  70. data/lib/locomotive/mounter.rb +97 -0
  71. 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