releaf-content 0.2.1 → 1.0.3

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +19 -21
  3. data/app/assets/javascripts/{releaf/controllers → controllers}/releaf/content/nodes.js +0 -0
  4. data/app/assets/stylesheets/{releaf/controllers → controllers}/releaf/content/nodes.scss +0 -0
  5. data/app/builders/releaf/content/builders/action_dialog.rb +9 -1
  6. data/app/builders/releaf/content/builders/tree.rb +1 -1
  7. data/app/builders/releaf/content/content_type_dialog_builder.rb +2 -2
  8. data/app/builders/releaf/content/nodes/form_builder.rb +3 -3
  9. data/app/builders/releaf/content/nodes/index_builder.rb +1 -1
  10. data/app/builders/releaf/content/nodes/toolbox_builder.rb +0 -5
  11. data/app/controllers/releaf/content/nodes_controller.rb +109 -129
  12. data/app/middleware/releaf/content/routes_reloader.rb +12 -4
  13. data/app/services/releaf/content/node/copy.rb +90 -0
  14. data/app/services/releaf/content/node/move.rb +21 -0
  15. data/app/services/releaf/content/node/save_under_parent.rb +22 -0
  16. data/app/services/releaf/content/node/service.rb +17 -0
  17. data/app/validators/releaf/content/node/singleness_validator.rb +1 -24
  18. data/lib/releaf-content.rb +85 -6
  19. data/lib/releaf/content/acts_as_node.rb +0 -5
  20. data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +2 -8
  21. data/lib/releaf/content/configuration.rb +61 -0
  22. data/lib/releaf/content/engine.rb +1 -34
  23. data/lib/releaf/content/node.rb +30 -101
  24. data/lib/releaf/content/node_mapper.rb +28 -2
  25. data/lib/releaf/content/route.rb +37 -25
  26. data/spec/builders/content/nodes/action_dialog_spec.rb +39 -0
  27. data/spec/builders/content/nodes/form_builder_spec.rb +47 -3
  28. data/spec/builders/content/nodes/toolbox_builder_spec.rb +1 -32
  29. data/spec/controllers/releaf/content/nodes_controller_spec.rb +42 -11
  30. data/spec/features/nodes_services_spec.rb +207 -0
  31. data/spec/features/nodes_spec.rb +328 -30
  32. data/spec/lib/releaf/content/acts_as_node_spec.rb +4 -32
  33. data/spec/lib/releaf/content/configuration_spec.rb +159 -0
  34. data/spec/lib/releaf/content/engine_spec.rb +149 -0
  35. data/spec/lib/releaf/content/node_spec.rb +66 -324
  36. data/spec/lib/releaf/content/route_spec.rb +223 -34
  37. data/spec/middleware/routes_reloader_spec.rb +62 -14
  38. data/spec/routing/node_mapper_spec.rb +223 -55
  39. data/spec/services/releaf/content/node/copy_spec.rb +115 -0
  40. data/spec/services/releaf/content/node/move_spec.rb +20 -0
  41. data/spec/services/releaf/content/node/save_under_parent_spec.rb +49 -0
  42. data/spec/services/releaf/content/node/service_spec.rb +19 -0
  43. metadata +38 -21
  44. data/app/builders/releaf/content/go_to_dialog_builder.rb +0 -9
  45. data/app/views/releaf/content/nodes/go_to_dialog.ruby +0 -1
  46. data/lib/releaf/content/builders_autoload.rb +0 -18
  47. data/releaf-content.gemspec +0 -20
@@ -0,0 +1,90 @@
1
+ module Releaf
2
+ module Content::Node
3
+ class Copy
4
+ include Releaf::Content::Node::Service
5
+ attribute :parent_id, Integer, strict: false
6
+
7
+ def call
8
+ prevent_infinite_copy_loop
9
+ begin
10
+ new_node = nil
11
+ node.class.transaction do
12
+ new_node = make_copy
13
+ end
14
+ new_node
15
+ rescue ActiveRecord::RecordInvalid
16
+ add_error_and_raise 'descendant invalid'
17
+ else
18
+ node.update_settings_timestamp
19
+ end
20
+ end
21
+
22
+ def prevent_infinite_copy_loop
23
+ return if node.self_and_descendants.find_by_id(parent_id).blank?
24
+ add_error_and_raise("source or descendant node can't be parent of new node")
25
+ end
26
+
27
+ def duplicate_under
28
+ new_node = nil
29
+ node.class.transaction do
30
+ new_node = node.class.new
31
+ new_node.assign_attributes_from(node)
32
+ new_node.content_id = duplicate_content.try(:id)
33
+ new_node.prevent_auto_update_settings_timestamp do
34
+ Releaf::Content::Node::SaveUnderParent.call(node: new_node, parent_id: parent_id)
35
+ end
36
+ end
37
+ new_node
38
+ end
39
+
40
+ def duplicate_content
41
+ return if node.content_id.blank?
42
+
43
+ new_content = node.content.class.new(cloned_content_attributes)
44
+ duplicate_content_dragonfly_attributes(new_content)
45
+
46
+ new_content.save!
47
+ new_content
48
+ end
49
+
50
+ def cloned_content_attributes
51
+ skippable_attribute_names = ["id"] + content_dragonfly_attributes
52
+ node.content.attributes.except(*skippable_attribute_names)
53
+ end
54
+
55
+ def duplicate_content_dragonfly_attributes(new_content)
56
+ content_dragonfly_attributes.each do |attribute_name|
57
+ accessor_name = attribute_name.gsub("_uid", "")
58
+ dragonfly_attachment = node.content.send(accessor_name)
59
+
60
+ if dragonfly_attachment.present?
61
+ begin
62
+ dragonfly_attachment.path # verify that the file exists
63
+ rescue Dragonfly::Job::Fetch::NotFound
64
+ dragonfly_attachment = nil
65
+ end
66
+ end
67
+
68
+ new_content.send("#{attribute_name}=", nil)
69
+ new_content.send("#{accessor_name}=", dragonfly_attachment)
70
+ end
71
+ end
72
+
73
+ def content_dragonfly_attributes
74
+ node.content.class.attribute_names.select do |attribute_name|
75
+ Releaf::Builders::Utilities::ResolveAttributeFieldMethodName.new(object: node.content, attribute_name: attribute_name).file?
76
+ end
77
+ end
78
+
79
+ def make_copy
80
+ new_node = duplicate_under
81
+
82
+ node.children.each do |child|
83
+ self.class.new(node: child, parent_id: new_node.id).make_copy
84
+ end
85
+
86
+ new_node
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,21 @@
1
+ module Releaf
2
+ module Content::Node
3
+ class Move
4
+ include Releaf::Content::Node::Service
5
+ attribute :parent_id, Integer, strict: false
6
+
7
+ def call
8
+ return if node.parent_id.to_i == parent_id
9
+
10
+ node.class.transaction do
11
+ Releaf::Content::Node::SaveUnderParent.call(node: node, parent_id: parent_id)
12
+
13
+ node.descendants.each do |descendant_node|
14
+ next if descendant_node.valid?
15
+ add_error_and_raise("descendant invalid")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ module Releaf
2
+ module Content::Node
3
+ class SaveUnderParent
4
+ include Releaf::Content::Node::Service
5
+ attribute :parent_id, Integer, strict: false
6
+
7
+ def call
8
+ node.parent_id = parent_id
9
+ if node.validate_root_locale_uniqueness?
10
+ # When copying root nodes it is important to reset locale to nil.
11
+ # Later user should fill in locale. This is needed to prevent
12
+ # Rails errors about conflicting routes.
13
+ node.locale = nil
14
+ end
15
+
16
+ node.maintain_name
17
+ node.reasign_slug
18
+ node.save!
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Releaf
2
+ module Content::Node
3
+ module Service
4
+ extend ActiveSupport::Concern
5
+ include Releaf::Service
6
+
7
+ included do
8
+ attribute :node, Releaf::Content::Node
9
+ end
10
+
11
+ def add_error_and_raise(error)
12
+ node.errors.add(:base, error)
13
+ raise ActiveRecord::RecordInvalid.new(node)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,28 +1,5 @@
1
1
  module Releaf
2
2
  module Content::Node
3
- # Validator to test if node is valid, when created under given parrent node
4
- #
5
- # validator needs :for and :under options, both can be either array of classes
6
- # or single class.
7
- #
8
- # :for option specifies for which nodes validation should be applied
9
- #
10
- # :under option specifies under which nodes only single node of given type
11
- # should be valid.
12
- # If under is unspecified, then only single node with given class can be
13
- # created under entrie content tree.
14
- #
15
- # @example
16
- #
17
- # class Node < ActiveRecord::Base
18
- # includes Releaf::Content::Node
19
- # validates_with Releaf::Content::Node::ParentValidator, for: [Text, Book], under: Store
20
- # end
21
- #
22
- # In the example above, Text node or book node can be added in any leve
23
- # under Store node, but only once.
24
- # However they can be added to any other node as many times as needed, as
25
- # long as none of ancestors are Store node.
26
3
  class SinglenessValidator < ActiveModel::Validator
27
4
 
28
5
  def validate node
@@ -67,7 +44,7 @@ module Releaf
67
44
  # return some other parent, than @node.parent.ancestors, even though
68
45
  # both return same node.
69
46
  # Seams like a bug in AwesomeNestedSet (in case of @node.parent.ancestors).
70
- parent_node = Node.find(@node.parent_id)
47
+ parent_node = @node.class.find(@node.parent_id)
71
48
 
72
49
  ancestor_node = parent_node.self_and_ancestors.where(content_type: ancestor_class_names).reorder(:depth).last
73
50
  if ancestor_node.nil?
@@ -1,6 +1,85 @@
1
- require 'releaf/content/router_proxy'
2
- require 'releaf/content/node_mapper'
3
- require 'releaf/content/engine'
4
- require 'releaf/content/acts_as_node'
5
- require 'releaf/content/node'
6
- require 'releaf/content/route'
1
+ require 'awesome_nested_set'
2
+ require 'stringex'
3
+
4
+ module Releaf::Content
5
+ require 'releaf/content/engine'
6
+ require 'releaf/content/configuration'
7
+ require 'releaf/content/router_proxy'
8
+ require 'releaf/content/node_mapper'
9
+ require 'releaf/content/acts_as_node'
10
+ require 'releaf/content/node'
11
+ require 'releaf/content/route'
12
+
13
+ # expose configuration wrapper methods as class methods for easier access
14
+ # so that, for example,
15
+ # Releaf::Content.models
16
+ # can be used instead of
17
+ # Releaf::Content.configuration.models
18
+ class << self
19
+ delegate :resources, :models, :default_model, :controllers, :routing, to: :configuration
20
+ end
21
+
22
+ def self.configuration
23
+ Releaf.application.config.content
24
+ end
25
+
26
+ def self.configure_component
27
+ Releaf.application.config.add_configuration(
28
+ Releaf::Content::Configuration.new(
29
+ resources: { 'Node' => { controller: 'Releaf::Content::NodesController' } }
30
+ )
31
+ )
32
+ end
33
+
34
+ def self.initialize_component
35
+ Rails.application.config.middleware.use Releaf::Content::RoutesReloader
36
+
37
+ ActiveSupport.on_load :action_controller do
38
+ ActionDispatch::Routing::Mapper.send(:include, Releaf::Content::NodeMapper)
39
+ end
40
+ end
41
+
42
+ def self.draw_component_routes(router)
43
+ resources.each do |_model_name, options|
44
+ draw_resource_routes(router, options)
45
+ end
46
+ end
47
+
48
+ def self.draw_resource_routes router, options
49
+ route_params = resource_route_params options
50
+
51
+ router.releaf_resources(*route_params) do
52
+ router.collection do
53
+ router.get :content_type_dialog
54
+ router.get :generate_url
55
+ end
56
+
57
+ router.member do
58
+ router.get :copy_dialog
59
+ router.post :copy
60
+ router.get :move_dialog
61
+ router.post :move
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.resource_route_params options
67
+ # Releaf::Content::NodesController -> releaf/content/nodes
68
+ controller_path = options[:controller].constantize.controller_path
69
+ controller_path_parts = controller_path.split('/')
70
+
71
+ resources_name = controller_path_parts.last
72
+
73
+ route_options = {
74
+ # releaf/content/nodes -> releaf/content
75
+ module: controller_path_parts.slice(0...-1).join('/'),
76
+
77
+ # releaf/content/nodes -> releaf_content_nodes
78
+ as: controller_path.gsub(/\//, '_'),
79
+
80
+ except: [:show]
81
+ }
82
+
83
+ [ resources_name, route_options ]
84
+ end
85
+ end
@@ -10,11 +10,6 @@ module ActsAsNode
10
10
 
11
11
  module ClassMethods
12
12
 
13
- # Load all nodes for class
14
- def nodes
15
- ::Node.where(content_type: self.name)
16
- end
17
-
18
13
  # There are no configuration options yet.
19
14
  #
20
15
  def acts_as_node(params: nil, fields: nil)
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
 
23
23
  def acts_as_node_params
24
24
  if acts_as_node_configuration[:params].nil?
25
- Releaf::Core::ResourceParams.new(self).values << :id
25
+ Releaf::ResourceParams.new(self).values << :id
26
26
  else
27
27
  acts_as_node_configuration[:params] << :id
28
28
  end
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  # @return [Array] list of fields to display
34
34
  def acts_as_node_fields
35
35
  if acts_as_node_configuration[:fields].nil?
36
- Releaf::Core::ResourceFields.new(self).values
36
+ Releaf::ResourceFields.new(self).values
37
37
  else
38
38
  acts_as_node_configuration[:fields]
39
39
  end
@@ -43,12 +43,6 @@ module ActiveRecord
43
43
  # All the methods available to a record that has had <tt>acts_as_node</tt> specified.
44
44
  module InstanceMethods
45
45
 
46
- # Return object corresponding node object
47
- # @return [::Node]
48
- def node
49
- ::Node.find_by(content_type: self.class.name, content_id: id)
50
- end
51
-
52
46
  end
53
47
  end
54
48
  end
@@ -0,0 +1,61 @@
1
+ module Releaf::Content
2
+ class Configuration
3
+ include Virtus.model(strict: true)
4
+ attribute :resources, Hash
5
+
6
+ def resources=(value)
7
+ verify_resources_config(value)
8
+ super
9
+ end
10
+
11
+ def verify_resources_config(resource_config)
12
+ # perform some basic config structure validation
13
+ unless resource_config.is_a? Hash
14
+ raise Releaf::Error, "Releaf.application.config.content.resources must be a Hash"
15
+ end
16
+
17
+ resource_config.each do | key, values |
18
+ unless key.is_a? String
19
+ raise Releaf::Error, "Releaf.application.config.content.resources must have string keys"
20
+ end
21
+ unless values.is_a? Hash
22
+ raise Releaf::Error, "#{key} in Releaf.application.config.content.resources must have a hash value"
23
+ end
24
+ unless values[:controller].is_a? String
25
+ raise Releaf::Error, "#{key} in Releaf.application.config.content.resources must have controller class specified as a string"
26
+ end
27
+ end
28
+ end
29
+
30
+ def models
31
+ model_names.map(&:constantize)
32
+ end
33
+
34
+ def model_names
35
+ @model_names ||= resources.keys
36
+ end
37
+
38
+ def default_model
39
+ models.first
40
+ end
41
+
42
+ def controllers
43
+ controller_names.map(&:constantize)
44
+ end
45
+
46
+ def controller_names
47
+ @controller_names ||= resources.values.map { |options| options[:controller] }
48
+ end
49
+
50
+ def routing
51
+ @routing ||= resources.map do | node_class_name, options |
52
+ routing = options[:routing] || {}
53
+ routing[:site] ||= nil
54
+ routing[:constraints] ||= nil
55
+ [ node_class_name, routing ]
56
+ end.to_h
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -1,40 +1,7 @@
1
- require 'awesome_nested_set'
2
- require 'stringex'
3
-
4
1
  module Releaf::Content
5
- require 'releaf/content/builders_autoload'
6
2
  class Engine < ::Rails::Engine
7
3
  initializer 'precompile', group: :all do |app|
8
- app.config.assets.precompile += %w(releaf/controllers/releaf/content/*)
4
+ app.config.assets.precompile += %w(controllers/releaf/content/*)
9
5
  end
10
6
  end
11
-
12
- def self.initialize_component
13
- Rails.application.config.middleware.use Releaf::Content::RoutesReloader
14
- end
15
-
16
- def self.draw_component_routes router
17
- router.namespace :releaf, path: nil do
18
- router.namespace :content, path: nil do
19
- router.releaf_resources :nodes, except: [:show] do
20
- router.collection do
21
- router.get :content_type_dialog
22
- router.get :generate_url
23
- router.get :go_to_dialog
24
- end
25
-
26
- router.member do
27
- router.get :copy_dialog
28
- router.post :copy
29
- router.get :move_dialog
30
- router.post :move
31
- end
32
- end
33
- end
34
- end
35
- end
36
-
37
- ActiveSupport.on_load :action_controller do
38
- ActionDispatch::Routing::Mapper.send(:include, Releaf::Content::NodeMapper)
39
- end
40
7
  end
@@ -1,13 +1,12 @@
1
1
  module Releaf::Content
2
2
  module Node
3
3
  extend ActiveSupport::Concern
4
- # TODO Node should be configurable
5
4
 
6
5
  def locale_selection_enabled?
7
6
  false
8
7
  end
9
8
 
10
- def build_content(params = {}, assignment_options = nil)
9
+ def build_content(params = {})
11
10
  self.content = content_class.new(params)
12
11
  end
13
12
 
@@ -17,11 +16,18 @@ module Releaf::Content
17
16
 
18
17
  # Return node public path
19
18
  def path
20
- if parent
21
- parent.path + "/" + slug.to_s
22
- else
23
- "/" + slug.to_s
24
- end
19
+ "/" + path_parts.join("/") + (trailing_slash_for_path? ? "/" : "")
20
+ end
21
+
22
+ def path_parts
23
+ list = []
24
+ list += parent.path_parts if parent
25
+ list << slug.to_s
26
+ list
27
+ end
28
+
29
+ def trailing_slash_for_path?
30
+ Rails.application.routes.default_url_options[:trailing_slash] == true
25
31
  end
26
32
 
27
33
  def to_s
@@ -45,40 +51,21 @@ module Releaf::Content
45
51
  end
46
52
 
47
53
  def attributes_to_not_copy
48
- %w[content_id depth id item_position lft rgt slug created_at updated_at]
54
+ list = %w[content_id depth id item_position lft rgt slug created_at updated_at]
55
+ list << "locale" if locale_before_type_cast.blank?
56
+ list
49
57
  end
50
58
 
51
59
  def attributes_to_copy
52
60
  self.class.column_names - attributes_to_not_copy
53
61
  end
54
62
 
55
- def copy parent_id
56
- prevent_infinite_copy_loop(parent_id)
57
- begin
58
- new_node = nil
59
- self.class.transaction do
60
- new_node = _copy!(parent_id)
61
- end
62
- new_node
63
- rescue ActiveRecord::RecordInvalid
64
- add_error_and_raise 'descendant invalid'
65
- else
66
- update_settings_timestamp
67
- end
63
+ def copy(parent_id)
64
+ Releaf::Content::Node::Copy.call(node: self, parent_id: parent_id)
68
65
  end
69
66
 
70
- def move parent_id
71
- return if parent_id.to_i == self.parent_id
72
-
73
- self.class.transaction do
74
- save_under(parent_id)
75
- descendants.each do |node|
76
- next if node.valid?
77
- add_error_and_raise 'descendant invalid'
78
- end
79
- end
80
-
81
- self
67
+ def move(parent_id)
68
+ Releaf::Content::Node::Move.call(node: self, parent_id: parent_id)
82
69
  end
83
70
 
84
71
  # Maintain unique name within parent_id scope.
@@ -118,59 +105,18 @@ module Releaf::Content
118
105
  self_and_ancestors.where(active: false).any? == false
119
106
  end
120
107
 
121
- def add_error_and_raise error
122
- errors.add(:base, error)
123
- raise ActiveRecord::RecordInvalid.new(self)
124
- end
125
-
126
- def duplicate_content
127
- return nil if content_id.blank?
128
-
129
- new_content = content.dup
130
- new_content.save!
131
- new_content
132
- end
133
-
134
- def copy_attributes_from node
135
- node.attributes_to_copy.each do |attribute|
136
- send("#{attribute}=", node.send(attribute))
137
- end
138
- end
139
-
140
- def duplicate_under! parent_id
141
- new_node = nil
142
- self.class.transaction do
143
- new_node = self.class.new
144
- new_node.copy_attributes_from self
145
- new_node.content_id = duplicate_content.try(:id)
146
- new_node.prevent_auto_update_settings_timestamp do
147
- new_node.save_under(parent_id)
148
- end
149
- end
150
- new_node
151
- end
152
-
153
108
  def reasign_slug
154
109
  self.slug = nil
155
110
  ensure_unique_url
156
111
  end
157
112
 
158
- def save_under target_parent_node_id
159
- self.parent_id = target_parent_node_id
160
- if validate_root_locale_uniqueness?
161
- # When copying root nodes it is important to reset locale to nil.
162
- # Later user should fill in locale. This is needed to prevent
163
- # Rails errors about conflicting routes.
164
- self.locale = nil
113
+ def assign_attributes_from(source_node)
114
+ source_node.attributes_to_copy.each do |attribute|
115
+ send("#{attribute}=", source_node.send(attribute))
165
116
  end
166
-
167
- self.item_position = self.class.children_max_item_position(self.parent) + 1
168
- maintain_name
169
- reasign_slug
170
- save!
171
117
  end
172
118
 
173
- def prevent_auto_update_settings_timestamp &block
119
+ def prevent_auto_update_settings_timestamp
174
120
  original = @prevent_auto_update_settings_timestamp
175
121
  @prevent_auto_update_settings_timestamp = true
176
122
  yield
@@ -178,12 +124,16 @@ module Releaf::Content
178
124
  @prevent_auto_update_settings_timestamp = original
179
125
  end
180
126
 
181
- protected
127
+ def update_settings_timestamp
128
+ self.class.updated
129
+ end
182
130
 
183
131
  def validate_root_locale_uniqueness?
184
132
  locale_selection_enabled? && root?
185
133
  end
186
134
 
135
+ protected
136
+
187
137
  def validate_parent_node_is_not_self
188
138
  return if parent_id.nil?
189
139
  return if parent_id.to_i != id
@@ -198,28 +148,10 @@ module Releaf::Content
198
148
 
199
149
  private
200
150
 
201
- def _copy! parent_id
202
- new_node = duplicate_under! parent_id
203
-
204
- children.each do |child|
205
- child.send(:_copy!, new_node.id)
206
- end
207
- new_node
208
- end
209
-
210
- def prevent_infinite_copy_loop(parent_id)
211
- return if self_and_descendants.find_by_id(parent_id).blank?
212
- add_error_and_raise("source or descendant node can't be parent of new node")
213
- end
214
-
215
151
  def prevent_auto_update_settings_timestamp?
216
152
  @prevent_auto_update_settings_timestamp == true
217
153
  end
218
154
 
219
- def update_settings_timestamp
220
- self.class.updated
221
- end
222
-
223
155
  module ClassMethods
224
156
  def updated_at
225
157
  Releaf::Settings['releaf.content.nodes.updated_at']
@@ -254,7 +186,7 @@ module Releaf::Content
254
186
 
255
187
  included do
256
188
  acts_as_nested_set order_column: :item_position
257
- acts_as_list scope: :parent_id, column: :item_position
189
+ acts_as_list scope: :parent_id, column: :item_position, add_new_at: :bottom
258
190
 
259
191
  default_scope { order(:item_position) }
260
192
  scope :active, ->() { where(active: true) }
@@ -266,9 +198,6 @@ module Releaf::Content
266
198
  validates_presence_of :parent, if: :parent_id?
267
199
  validate :validate_parent_node_is_not_self
268
200
  validate :validate_parent_is_not_descendant
269
-
270
- alias_attribute :to_text, :name
271
-
272
201
  belongs_to :content, polymorphic: true, dependent: :destroy
273
202
  accepts_nested_attributes_for :content
274
203