releaf-content 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|