releaf-content 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +24 -0
  3. data/app/assets/javascripts/releaf/controllers/releaf/content/nodes.js +88 -0
  4. data/app/assets/stylesheets/releaf/controllers/releaf/content/nodes.scss +234 -0
  5. data/app/builders/releaf/content/builders/action_dialog.rb +60 -0
  6. data/app/builders/releaf/content/builders/dialog.rb +15 -0
  7. data/app/builders/releaf/content/builders/tree.rb +84 -0
  8. data/app/builders/releaf/content/content_type_dialog_builder.rb +74 -0
  9. data/app/builders/releaf/content/copy_dialog_builder.rb +9 -0
  10. data/app/builders/releaf/content/go_to_dialog_builder.rb +9 -0
  11. data/app/builders/releaf/content/move_dialog_builder.rb +9 -0
  12. data/app/builders/releaf/content/nodes/content_form_builder.rb +7 -0
  13. data/app/builders/releaf/content/nodes/form_builder.rb +108 -0
  14. data/app/builders/releaf/content/nodes/index_builder.rb +24 -0
  15. data/app/builders/releaf/content/nodes/toolbox_builder.rb +33 -0
  16. data/app/controllers/releaf/content/nodes_controller.rb +166 -0
  17. data/app/middleware/releaf/content/routes_reloader.rb +25 -0
  18. data/app/validators/releaf/content/node/parent_validator.rb +48 -0
  19. data/app/validators/releaf/content/node/root_validator.rb +43 -0
  20. data/app/validators/releaf/content/node/singleness_validator.rb +102 -0
  21. data/app/views/releaf/content/nodes/content_type_dialog.ruby +1 -0
  22. data/app/views/releaf/content/nodes/copy_dialog.ruby +1 -0
  23. data/app/views/releaf/content/nodes/go_to_dialog.ruby +1 -0
  24. data/app/views/releaf/content/nodes/move_dialog.ruby +1 -0
  25. data/lib/releaf-content.rb +6 -0
  26. data/lib/releaf/content/acts_as_node.rb +73 -0
  27. data/lib/releaf/content/acts_as_node/action_controller/acts/node.rb +17 -0
  28. data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +55 -0
  29. data/lib/releaf/content/builders_autoload.rb +18 -0
  30. data/lib/releaf/content/engine.rb +40 -0
  31. data/lib/releaf/content/node.rb +280 -0
  32. data/lib/releaf/content/node_mapper.rb +9 -0
  33. data/lib/releaf/content/route.rb +93 -0
  34. data/lib/releaf/content/router_proxy.rb +23 -0
  35. data/releaf-content.gemspec +20 -0
  36. data/spec/builders/content/nodes/content_form_builder_spec.rb +24 -0
  37. data/spec/builders/content/nodes/form_builder_spec.rb +218 -0
  38. data/spec/builders/content/nodes/toolbox_builder_spec.rb +108 -0
  39. data/spec/controllers/releaf/content/nodes_controller_spec.rb +21 -0
  40. data/spec/features/nodes_spec.rb +239 -0
  41. data/spec/lib/releaf/content/acts_as_node_spec.rb +118 -0
  42. data/spec/lib/releaf/content/node_spec.rb +779 -0
  43. data/spec/lib/releaf/content/route_spec.rb +85 -0
  44. data/spec/middleware/routes_reloader_spec.rb +48 -0
  45. data/spec/routing/node_mapper_spec.rb +142 -0
  46. data/spec/validators/content/node/parent_validator_spec.rb +56 -0
  47. data/spec/validators/content/node/root_validator_spec.rb +69 -0
  48. data/spec/validators/content/node/singleness_validator_spec.rb +145 -0
  49. 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,6 @@
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'
@@ -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