releaf-content 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +24 -0
- data/app/assets/javascripts/releaf/controllers/releaf/content/nodes.js +88 -0
- data/app/assets/stylesheets/releaf/controllers/releaf/content/nodes.scss +234 -0
- data/app/builders/releaf/content/builders/action_dialog.rb +60 -0
- data/app/builders/releaf/content/builders/dialog.rb +15 -0
- data/app/builders/releaf/content/builders/tree.rb +84 -0
- data/app/builders/releaf/content/content_type_dialog_builder.rb +74 -0
- data/app/builders/releaf/content/copy_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/go_to_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/move_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/nodes/content_form_builder.rb +7 -0
- data/app/builders/releaf/content/nodes/form_builder.rb +108 -0
- data/app/builders/releaf/content/nodes/index_builder.rb +24 -0
- data/app/builders/releaf/content/nodes/toolbox_builder.rb +33 -0
- data/app/controllers/releaf/content/nodes_controller.rb +166 -0
- data/app/middleware/releaf/content/routes_reloader.rb +25 -0
- data/app/validators/releaf/content/node/parent_validator.rb +48 -0
- data/app/validators/releaf/content/node/root_validator.rb +43 -0
- data/app/validators/releaf/content/node/singleness_validator.rb +102 -0
- data/app/views/releaf/content/nodes/content_type_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/copy_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/go_to_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/move_dialog.ruby +1 -0
- data/lib/releaf-content.rb +6 -0
- data/lib/releaf/content/acts_as_node.rb +73 -0
- data/lib/releaf/content/acts_as_node/action_controller/acts/node.rb +17 -0
- data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +55 -0
- data/lib/releaf/content/builders_autoload.rb +18 -0
- data/lib/releaf/content/engine.rb +40 -0
- data/lib/releaf/content/node.rb +280 -0
- data/lib/releaf/content/node_mapper.rb +9 -0
- data/lib/releaf/content/route.rb +93 -0
- data/lib/releaf/content/router_proxy.rb +23 -0
- data/releaf-content.gemspec +20 -0
- data/spec/builders/content/nodes/content_form_builder_spec.rb +24 -0
- data/spec/builders/content/nodes/form_builder_spec.rb +218 -0
- data/spec/builders/content/nodes/toolbox_builder_spec.rb +108 -0
- data/spec/controllers/releaf/content/nodes_controller_spec.rb +21 -0
- data/spec/features/nodes_spec.rb +239 -0
- data/spec/lib/releaf/content/acts_as_node_spec.rb +118 -0
- data/spec/lib/releaf/content/node_spec.rb +779 -0
- data/spec/lib/releaf/content/route_spec.rb +85 -0
- data/spec/middleware/routes_reloader_spec.rb +48 -0
- data/spec/routing/node_mapper_spec.rb +142 -0
- data/spec/validators/content/node/parent_validator_spec.rb +56 -0
- data/spec/validators/content/node/root_validator_spec.rb +69 -0
- data/spec/validators/content/node/singleness_validator_spec.rb +145 -0
- metadata +145 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Releaf::Content
|
2
|
+
class RoutesReloader
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
self.class.routes_loaded
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
self.class.reload_if_expired
|
10
|
+
@app.call(env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.routes_loaded
|
14
|
+
@updated_at = Time.now
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.reload_if_expired
|
18
|
+
# TODO Node class should be configurable
|
19
|
+
return unless ::Node.updated_at.present? && @updated_at && @updated_at < ::Node.updated_at
|
20
|
+
Rails.application.reload_routes!
|
21
|
+
routes_loaded
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Releaf
|
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 given node can be added
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# class Node < ActiveRecord::Base
|
15
|
+
# includes Releaf::Content::Node
|
16
|
+
# validates_with Releaf::Content::Node::ParentValidator, for: [Text, Book], under: Store
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
class ParentValidator < ActiveModel::Validator
|
20
|
+
|
21
|
+
def validate node
|
22
|
+
@node = node
|
23
|
+
node.errors.add(:content_type, 'invalid parent node') unless node_parent_valid?
|
24
|
+
remove_instance_variable(:@node)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def node_parent_valid?
|
30
|
+
return true unless child_class_names.include? @node.content_type
|
31
|
+
return parent_class_names.include? @node.parent.try(:content_type)
|
32
|
+
end
|
33
|
+
|
34
|
+
def child_class_names
|
35
|
+
target_class_names :for
|
36
|
+
end
|
37
|
+
|
38
|
+
def parent_class_names
|
39
|
+
target_class_names :under
|
40
|
+
end
|
41
|
+
|
42
|
+
def target_class_names target_type
|
43
|
+
[options.fetch(target_type, [])].flatten.map(&:name)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Releaf
|
2
|
+
module Content::Node
|
3
|
+
# Validator to test if node is valid root node
|
4
|
+
#
|
5
|
+
# Validator needs :allow option.
|
6
|
+
#
|
7
|
+
# :allow option specifies which nodes are valid root nodes.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# class Node < ActiveRecord::Base
|
12
|
+
# includes Releaf::Content::Node
|
13
|
+
# validates_with Releaf::Content::Node::RootValidator, allow: [Text, Store]
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# In example above only Text and Book nodes can be created as root nodes
|
17
|
+
class RootValidator < ActiveModel::Validator
|
18
|
+
|
19
|
+
def validate node
|
20
|
+
@node = node
|
21
|
+
|
22
|
+
if allowed_root_node?
|
23
|
+
node.errors.add(:content_type, "can't be subnode") if @node.parent.present?
|
24
|
+
else
|
25
|
+
node.errors.add(:content_type, "can't be root node") if @node.parent.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
remove_instance_variable(:@node)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def allowed_root_node?
|
34
|
+
root_class_names.include? @node.content_type
|
35
|
+
end
|
36
|
+
|
37
|
+
def root_class_names
|
38
|
+
[options.fetch(:allow, [])].flatten.map(&:name)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Releaf
|
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
|
+
class SinglenessValidator < ActiveModel::Validator
|
27
|
+
|
28
|
+
def validate node
|
29
|
+
@node = node
|
30
|
+
node.errors.add(:content_type, 'node exists') unless node_valid?
|
31
|
+
remove_instance_variable(:@node)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def node_valid?
|
37
|
+
return true unless child_class_names.include? @node.content_type
|
38
|
+
|
39
|
+
relation = base_relation_for_validation
|
40
|
+
# if relation is nil, then node is under ancestor for which this validation
|
41
|
+
# shouldn't be appied
|
42
|
+
return true if relation.nil?
|
43
|
+
|
44
|
+
unless @node.new_record?
|
45
|
+
relation = relation.where('id <> ?', @node.id)
|
46
|
+
end
|
47
|
+
|
48
|
+
relation.any? == false
|
49
|
+
end
|
50
|
+
|
51
|
+
def base_relation_for_validation
|
52
|
+
if ancestor_classes.blank?
|
53
|
+
return base_relation_for_entire_tree
|
54
|
+
else
|
55
|
+
return base_relation_for_subtree
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def base_relation_for_entire_tree
|
60
|
+
@node.class.unscoped.where(content_type: @node.content_type)
|
61
|
+
end
|
62
|
+
|
63
|
+
def base_relation_for_subtree
|
64
|
+
return nil if @node.parent.nil?
|
65
|
+
|
66
|
+
# need to find parent node again, because Node.roots[n].ancestors can
|
67
|
+
# return some other parent, than @node.parent.ancestors, even though
|
68
|
+
# both return same node.
|
69
|
+
# Seams like a bug in AwesomeNestedSet (in case of @node.parent.ancestors).
|
70
|
+
parent_node = Node.find(@node.parent_id)
|
71
|
+
|
72
|
+
ancestor_node = parent_node.self_and_ancestors.where(content_type: ancestor_class_names).reorder(:depth).last
|
73
|
+
if ancestor_node.nil?
|
74
|
+
return nil
|
75
|
+
else
|
76
|
+
return ancestor_node.descendants.where(content_type: @node.content_type)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def child_class_names
|
81
|
+
target_class_names :for
|
82
|
+
end
|
83
|
+
|
84
|
+
def ancestor_class_names
|
85
|
+
target_class_names :under
|
86
|
+
end
|
87
|
+
|
88
|
+
def ancestor_classes
|
89
|
+
target_classes :under
|
90
|
+
end
|
91
|
+
|
92
|
+
def target_classes target_type
|
93
|
+
[options.fetch(target_type, [])].flatten
|
94
|
+
end
|
95
|
+
|
96
|
+
def target_class_names target_type
|
97
|
+
target_classes(target_type).map(&:name)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Releaf::Content::ContentTypeDialogBuilder.new(self).output
|
@@ -0,0 +1 @@
|
|
1
|
+
Releaf::Content::CopyDialogBuilder.new(self).output
|
@@ -0,0 +1 @@
|
|
1
|
+
Releaf::Content::GoToDialogBuilder.new(self).output
|
@@ -0,0 +1 @@
|
|
1
|
+
Releaf::Content::MoveDialogBuilder.new(self).output
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'releaf/content/acts_as_node/active_record/acts/node'
|
2
|
+
require 'releaf/content/acts_as_node/action_controller/acts/node'
|
3
|
+
|
4
|
+
module ActsAsNode
|
5
|
+
@classes = []
|
6
|
+
|
7
|
+
def self.register_class(class_name)
|
8
|
+
@classes << class_name unless @classes.include? class_name
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# Load all nodes for class
|
14
|
+
def nodes
|
15
|
+
::Node.where(content_type: self.name)
|
16
|
+
end
|
17
|
+
|
18
|
+
# There are no configuration options yet.
|
19
|
+
#
|
20
|
+
def acts_as_node(params: nil, fields: nil)
|
21
|
+
configuration = {params: params, fields: fields}
|
22
|
+
|
23
|
+
ActsAsNode.register_class(self.name)
|
24
|
+
|
25
|
+
# Store acts_as_node configuration
|
26
|
+
cattr_accessor :acts_as_node_configuration
|
27
|
+
self.acts_as_node_configuration = configuration
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.classes
|
32
|
+
# eager load in dev env
|
33
|
+
Rails.application.eager_load! if Rails.env.development?
|
34
|
+
|
35
|
+
@classes
|
36
|
+
end
|
37
|
+
|
38
|
+
if defined? Rails::Railtie
|
39
|
+
require 'rails'
|
40
|
+
class Railtie < Rails::Railtie
|
41
|
+
initializer 'acts_as_node.insert' do
|
42
|
+
ActiveSupport.on_load :active_record do
|
43
|
+
ActsAsNode::Railtie.insert_into_active_record
|
44
|
+
end
|
45
|
+
|
46
|
+
ActiveSupport.on_load :action_controller do
|
47
|
+
ActsAsNode::Railtie.insert_into_action_controller
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Railtie
|
54
|
+
def self.insert
|
55
|
+
insert_into_active_record
|
56
|
+
insert_into_action_controller
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.insert_into_active_record
|
60
|
+
if defined?(ActiveRecord)
|
61
|
+
ActiveRecord::Base.send(:include, ActiveRecord::Acts::Node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.insert_into_action_controller
|
66
|
+
if defined?(ActionController)
|
67
|
+
ActionController::Base.send(:include, ActionController::Acts::Node)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
ActsAsNode::Railtie.insert
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActionController
|
2
|
+
module Acts #:nodoc:
|
3
|
+
# This +acts_as+ extension provides the capabilities for attaching object to nodes tree.
|
4
|
+
#
|
5
|
+
# Text example:
|
6
|
+
#
|
7
|
+
# class ContactFormController < ActionController::Base
|
8
|
+
# has_many :acts_as_node
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
module Node #:nodoc:
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(::ActsAsNode::ClassMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Acts #:nodoc:
|
3
|
+
module Node #:nodoc:
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(::ActsAsNode::ClassMethods)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
# This +acts_as+ extension provides the capabilities for attaching object to nodes tree.
|
10
|
+
#
|
11
|
+
# Text example:
|
12
|
+
#
|
13
|
+
# class Text < ActiveRecord::Base
|
14
|
+
# has_many :acts_as_node
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
module ClassMethods
|
18
|
+
def acts_as_node(params: nil, fields: nil)
|
19
|
+
super
|
20
|
+
include ::ActiveRecord::Acts::Node::InstanceMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
def acts_as_node_params
|
24
|
+
if acts_as_node_configuration[:params].nil?
|
25
|
+
Releaf::Core::ResourceParams.new(self).values << :id
|
26
|
+
else
|
27
|
+
acts_as_node_configuration[:params] << :id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns fields to display for releaf content controller
|
32
|
+
#
|
33
|
+
# @return [Array] list of fields to display
|
34
|
+
def acts_as_node_fields
|
35
|
+
if acts_as_node_configuration[:fields].nil?
|
36
|
+
Releaf::Core::ResourceFields.new(self).values
|
37
|
+
else
|
38
|
+
acts_as_node_configuration[:fields]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# All the methods available to a record that has had <tt>acts_as_node</tt> specified.
|
44
|
+
module InstanceMethods
|
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
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
root_path = File.expand_path('../..', File.dirname(__dir__))
|
2
|
+
files = %w(
|
3
|
+
builders/tree
|
4
|
+
builders/dialog
|
5
|
+
builders/action_dialog
|
6
|
+
content_type_dialog_builder
|
7
|
+
copy_dialog_builder
|
8
|
+
copy_dialog_builder
|
9
|
+
go_to_dialog_builder
|
10
|
+
move_dialog_builder
|
11
|
+
nodes/content_form_builder
|
12
|
+
nodes/form_builder
|
13
|
+
nodes/index_builder
|
14
|
+
nodes/toolbox_builder
|
15
|
+
)
|
16
|
+
files.each do|file|
|
17
|
+
require "#{root_path}/app/builders/releaf/content/#{file}"
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'awesome_nested_set'
|
2
|
+
require 'stringex'
|
3
|
+
|
4
|
+
module Releaf::Content
|
5
|
+
require 'releaf/content/builders_autoload'
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
initializer 'precompile', group: :all do |app|
|
8
|
+
app.config.assets.precompile += %w(releaf/controllers/releaf/content/*)
|
9
|
+
end
|
10
|
+
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
|
+
end
|