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.
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