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,85 @@
1
+ require "rails_helper"
2
+
3
+ describe Releaf::Content::Route do
4
+ let(:node_route) { FactoryGirl.build(:node_route, node_id: 12, locale: "en", path: "/en") }
5
+
6
+ describe ".node_class" do
7
+ it "returns ::Node" do
8
+ expect( described_class.node_class ).to eq ::Node
9
+ end
10
+ end
11
+
12
+ describe ".node_class_default_controller" do
13
+ context "when given node class inherits `ActionController::Base`" do
14
+ it "returns undercored, stripped down controller class" do
15
+ expect(described_class.node_class_default_controller(HomePagesController)).to eq("home_pages")
16
+ end
17
+ end
18
+
19
+ context "when given node class does not inherit `ActionController::Base`" do
20
+ it "returns pluralized, underscorized class" do
21
+ expect(described_class.node_class_default_controller(TextPage)).to eq("text_pages")
22
+ end
23
+ end
24
+ end
25
+
26
+ describe ".for" do
27
+ before do
28
+ create(:home_page_node)
29
+ end
30
+
31
+ it "returns an array" do
32
+ expect(described_class.for(HomePage, 'foo').class).to eq(Array)
33
+ end
34
+
35
+ context "when databse doesn't exists" do
36
+ it "returns an empty array" do
37
+ allow(described_class.node_class).to receive(:where).and_raise(ActiveRecord::NoDatabaseError.new("xxx"))
38
+ expect(described_class.for(HomePage, 'foo')).to eq([])
39
+ end
40
+ end
41
+
42
+ context "when releaf_nodes table doesn't exists" do
43
+ it "returns an empty array" do
44
+ allow(described_class.node_class).to receive(:where).and_raise(ActiveRecord::StatementInvalid.new("xxx"))
45
+ expect(described_class.for(HomePage, 'foo')).to eq([])
46
+ end
47
+ end
48
+
49
+ context "when releaf_nodes table exists" do
50
+ it "returns an array of Node::Route objects" do
51
+ result = described_class.for(HomePage, 'foo')
52
+ expect(result.count).to eq(1)
53
+ expect(result.first.class).to eq(described_class)
54
+ end
55
+
56
+ context "when node is not available" do
57
+ it "does not include it in return" do
58
+ allow_any_instance_of(Node).to receive(:available?).and_return(false)
59
+ expect(described_class.for(HomePage, 'foo')).to eq([])
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#params' do
66
+ it "returns params for router method" do
67
+ expect(node_route.params("home#index")).to eq ['/en', {:node_id=>"12", :locale=>"en", :to=>"home#index"}]
68
+ end
69
+
70
+ context "when :as given in args" do
71
+ context "when node has a locale" do
72
+ it "prepends locale to :as" do
73
+ expect(node_route.params("home#index", as: "home").last).to match hash_including(as: "en_home")
74
+ end
75
+ end
76
+
77
+ context "when node does not have a locale" do
78
+ it "doesn't modify :as option" do
79
+ node_route.locale = nil
80
+ expect(node_route.params("home#index", as: "home").last).to match hash_including(as: "home")
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,48 @@
1
+ require "rails_helper"
2
+
3
+ describe Releaf::Content::RoutesReloader do
4
+ let(:app) { ->(env) { [200, env, "app"] } }
5
+
6
+ let :request do
7
+ described_class.new(app).call(Rack::MockRequest.env_for('http://example.com'))
8
+ end
9
+
10
+ describe "on application startup" do
11
+ it "sets routes loading time" do
12
+ expect(described_class.routes_loaded).to_not be_nil
13
+ end
14
+ end
15
+
16
+ describe "on each request" do
17
+ it "compares latest updates" do
18
+ expect(described_class).to receive(:reload_if_expired)
19
+ request
20
+ end
21
+ end
22
+
23
+ describe ".reload_if_expired" do
24
+ context "when no nodes exists" do
25
+ it "does not reload routes" do
26
+ allow(Node).to receive(:updated_at).and_return(nil)
27
+ expect(Rails.application).to_not receive(:reload_routes!)
28
+ described_class.reload_if_expired
29
+ end
30
+ end
31
+
32
+ context "when routes is up to date" do
33
+ it "does not reload routes" do
34
+ allow(Node).to receive(:updated_at).and_return(Time.parse("1991-01-01"))
35
+ expect(Rails.application).to_not receive(:reload_routes!)
36
+ described_class.reload_if_expired
37
+ end
38
+ end
39
+
40
+ context "when routes is outdated" do
41
+ it "reloads routes" do
42
+ allow(Node).to receive(:updated_at).and_return(Time.now)
43
+ expect(Rails.application).to receive(:reload_routes!)
44
+ described_class.reload_if_expired
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,142 @@
1
+ require "rails_helper"
2
+
3
+ describe Releaf::Content::NodeMapper do
4
+ after(:all) do
5
+ # reset dummy app routes
6
+ Dummy::Application.reload_routes!
7
+ end
8
+
9
+ before do
10
+ Dummy::Application.reload_routes!
11
+ @text_page = FactoryGirl.create(:text_page)
12
+ @lv_root = create(:home_page_node, name: "lv", locale: "lv", slug: 'lv')
13
+ @node = FactoryGirl.create(:node, slug: 'test-page', content: @text_page, parent: @lv_root)
14
+ end
15
+
16
+ describe "#releaf_routes_for" do
17
+
18
+ describe "using current node path" do
19
+ example "using default controller " do
20
+ routes.draw do
21
+ releaf_routes_for(TextPage) do |route|
22
+ get 'show'
23
+ delete :destroy
24
+ end
25
+ end
26
+
27
+ expect(get: '/lv/test-page').to route_to(
28
+ "controller" => "text_pages",
29
+ "action" => "show",
30
+ "node_id" => @node.id.to_s,
31
+ "locale" => 'lv'
32
+ )
33
+
34
+ expect(delete: '/lv/test-page').to route_to(
35
+ "controller" => "text_pages",
36
+ "action" => "destroy",
37
+ "node_id" => @node.id.to_s,
38
+ "locale" => 'lv'
39
+ )
40
+ end
41
+
42
+ example "overriding default controller" do
43
+ routes.draw do
44
+ releaf_routes_for(TextPage, controller: 'home_pages') do |route|
45
+ get 'show'
46
+ delete :destroy
47
+ end
48
+ end
49
+
50
+ expect(get: '/lv/test-page').to route_to(
51
+ "controller" => "home_pages",
52
+ "action" => "show",
53
+ "node_id" => @node.id.to_s,
54
+ "locale" => 'lv'
55
+ )
56
+ end
57
+
58
+ example "specifying different than default controller for route" do
59
+ routes.draw do
60
+ releaf_routes_for(TextPage, controller: 'doesnt_matter') do |route|
61
+ get 'home_pages#hide'
62
+ end
63
+ end
64
+
65
+ expect(get: '/lv/test-page').to route_to(
66
+ "controller" => "home_pages",
67
+ "action" => "hide",
68
+ "node_id" => @node.id.to_s,
69
+ "locale" => 'lv'
70
+ )
71
+ end
72
+ end
73
+
74
+ describe "adding uri parts to node path" do
75
+ example "speciffying custom controller and action" do
76
+ routes.draw do
77
+ releaf_routes_for(TextPage) do |route|
78
+ get ':my_id/list', to: 'home_pages#list'
79
+ end
80
+ end
81
+
82
+ expect(get: '/lv/test-page/8888/list').to route_to(
83
+ "controller" => "home_pages",
84
+ "action" => "list",
85
+ "node_id" => @node.id.to_s,
86
+ "locale" => 'lv',
87
+ "my_id" => '8888'
88
+ )
89
+ end
90
+
91
+ example "speciffying action" do
92
+ routes.draw do
93
+ releaf_routes_for(TextPage) do |route|
94
+ get ':my_id/list', to: '#list'
95
+ end
96
+ end
97
+
98
+ expect(get: '/lv/test-page/8888/list').to route_to(
99
+ "controller" => "text_pages",
100
+ "action" => "list",
101
+ "node_id" => @node.id.to_s,
102
+ "locale" => 'lv',
103
+ "my_id" => '8888'
104
+ )
105
+ end
106
+
107
+ example "speciffying action and overriding default controller" do
108
+ routes.draw do
109
+ releaf_routes_for(TextPage, controller: 'home_pages') do |route|
110
+ get ':my_id/list', to: '#list'
111
+ end
112
+ end
113
+
114
+ expect(get: '/lv/test-page/8888/list').to route_to(
115
+ "controller" => "home_pages",
116
+ "action" => "list",
117
+ "node_id" => @node.id.to_s,
118
+ "locale" => 'lv',
119
+ "my_id" => '8888'
120
+ )
121
+ end
122
+
123
+ example "speciffying custom path" do
124
+ routes.draw do
125
+ releaf_routes_for(TextPage) do |route|
126
+ get 'home_pages#show', path: "#{route.path}/abc/:my_id"
127
+ end
128
+ end
129
+
130
+ expect(get: '/lv/test-page/abc/333').to route_to(
131
+ "controller" => "home_pages",
132
+ "action" => "show",
133
+ "node_id" => @node.id.to_s,
134
+ "locale" => 'lv',
135
+ "my_id" => '333'
136
+ )
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,56 @@
1
+ require 'rails_helper'
2
+
3
+ describe Releaf::Content::Node::ParentValidator do
4
+ let!(:root_node) { FactoryGirl.create(:node, content_type: 'HomePage') }
5
+
6
+ class DummyNodeParentValidatorModel < ActiveRecord::Base
7
+ acts_as_node
8
+ self.table_name = 'texts'
9
+ end
10
+
11
+ class DummyNodeParentValidator1Controller < ActionController::Base
12
+ acts_as_node
13
+ end
14
+
15
+ class DummyNodeParentValidator2Controller < ActionController::Base
16
+ acts_as_node
17
+ end
18
+
19
+
20
+ class DummyNodeParentValidatorNode < ActiveRecord::Base
21
+ self.table_name = 'nodes'
22
+ include Releaf::Content::Node
23
+ validates_with Releaf::Content::Node::ParentValidator, for: DummyNodeParentValidatorModel, under: DummyNodeParentValidator1Controller
24
+ end
25
+
26
+ context "when parent is valid" do
27
+ it "doesn't add error" do
28
+ parent = DummyNodeParentValidatorNode.create!( FactoryGirl.attributes_for(:node, content_type: 'DummyNodeParentValidator1Controller') )
29
+ child = DummyNodeParentValidatorNode.new( FactoryGirl.attributes_for(:node, content_type: 'DummyNodeParentValidatorModel', parent_id: parent.id) )
30
+
31
+ expect( child ).to be_valid
32
+ end
33
+ end
34
+
35
+ context "when parent is invalid" do
36
+ it "adds error on content_type" do
37
+ parent = DummyNodeParentValidatorNode.create!( FactoryGirl.attributes_for(:node, content_type: 'DummyNodeParentValidator2Controller') )
38
+ child = DummyNodeParentValidatorNode.new( FactoryGirl.attributes_for(:node, content_type: 'DummyNodeParentValidatorModel', parent_id: parent.id) )
39
+
40
+ expect( child ).to be_invalid
41
+ expect( child.errors[:content_type].size ).to eq(1)
42
+ expect( child.errors[:content_type] ).to include("invalid parent node")
43
+ end
44
+
45
+ end
46
+
47
+ context "when content_type is not in child list" do
48
+ it "doesn't add error" do
49
+ parent = DummyNodeParentValidatorNode.create!( FactoryGirl.attributes_for(:node, content_type: 'DummyNodeParentValidator1Controller') )
50
+ child = DummyNodeParentValidatorNode.new( FactoryGirl.attributes_for(:node, content_type: 'DummyNodeParentValidator2Controller', parent_id: parent.id) )
51
+
52
+ expect( child ).to be_valid
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,69 @@
1
+ require 'rails_helper'
2
+
3
+ describe Releaf::Content::Node::RootValidator do
4
+
5
+
6
+ class DummyRootValidatorController < ActionController::Base
7
+ acts_as_node
8
+ end
9
+
10
+ class DummyRootValidator2Controller < ActionController::Base
11
+ acts_as_node
12
+ end
13
+
14
+ class DummyRootValidatorNode < ActiveRecord::Base
15
+ self.table_name = 'nodes'
16
+ include Releaf::Content::Node
17
+ validates_with Releaf::Content::Node::RootValidator, allow: DummyRootValidatorController
18
+ end
19
+
20
+
21
+ def create_node *params
22
+ DummyRootValidatorNode.create!( FactoryGirl.attributes_for(:node, *params) )
23
+ end
24
+
25
+ def build_node *params
26
+ DummyRootValidatorNode.new( FactoryGirl.attributes_for(:node, *params) )
27
+ end
28
+
29
+ context "when node is allowed to be root node" do
30
+ context "when node is a root node" do
31
+ it "doesn't add an error" do
32
+ root_node = build_node(content_type: 'DummyRootValidatorController')
33
+ expect( root_node ).to be_valid
34
+ end
35
+ end
36
+
37
+ context "when node is not a root node" do
38
+ it "adds an error" do
39
+ root_node = create_node(content_type: 'DummyRootValidatorController')
40
+ subnode = build_node(content_type: 'DummyRootValidatorController', parent: root_node)
41
+ expect( subnode ).to be_invalid
42
+ expect( subnode.errors[:content_type].size ).to eq(1)
43
+ expect( subnode.errors[:content_type] ).to include("can't be subnode")
44
+ end
45
+ end
46
+ end
47
+
48
+ context "when node is not allowed to be a root node" do
49
+ context "when node is a root node" do
50
+ it "adds an error" do
51
+ root_node = build_node(content_type: 'DummyRootValidator2Controller')
52
+ expect( root_node ).to be_invalid
53
+ expect( root_node.errors[:content_type].size ).to eq(1)
54
+ expect( root_node.errors[:content_type] ).to include("can't be root node")
55
+ end
56
+ end
57
+
58
+ context "when node is not a root node" do
59
+ it "doesn't add an error" do
60
+ root_node = create_node(content_type: 'DummyRootValidatorController')
61
+ subnode = build_node(content_type: 'DummyRootValidator2Controller', parent: root_node)
62
+
63
+ expect( subnode ).to be_valid
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+ end
@@ -0,0 +1,145 @@
1
+ require 'rails_helper'
2
+
3
+ describe Releaf::Content::Node::SinglenessValidator do
4
+
5
+
6
+ class DummySinglenessValidatorModel < ActiveRecord::Base
7
+ acts_as_node
8
+ self.table_name = 'text_pages'
9
+ end
10
+
11
+ class DummySinglenessValidator2Model < ActiveRecord::Base
12
+ acts_as_node
13
+ self.table_name = 'text_pages'
14
+ end
15
+
16
+ class DummySinglenessValidatorController < ActionController::Base
17
+ acts_as_node
18
+ end
19
+
20
+ class DummySinglenessValidator2Controller < ActionController::Base
21
+ acts_as_node
22
+ end
23
+
24
+ class DummySinglenessValidatorNode < ActiveRecord::Base
25
+ self.table_name = 'nodes'
26
+ include Releaf::Content::Node
27
+ validates_with Releaf::Content::Node::SinglenessValidator, for: DummySinglenessValidatorModel
28
+ validates_with Releaf::Content::Node::SinglenessValidator, for: DummySinglenessValidator2Controller, under: TextPage
29
+ end
30
+
31
+
32
+ def create_node *params
33
+ DummySinglenessValidatorNode.create!( FactoryGirl.attributes_for(:node, *params) )
34
+ end
35
+
36
+ def build_node *params
37
+ DummySinglenessValidatorNode.new( FactoryGirl.attributes_for(:node, *params) )
38
+ end
39
+
40
+ let!(:root_node) { create_node(content_type: 'HomePage') }
41
+
42
+
43
+ context "when scope is entire page" do
44
+
45
+ context "When node not mentioned in list" do
46
+ it "doesn't add error" do
47
+ node = build_node(content_type: 'DummySinglenessValidatorController', parent_id: root_node.id)
48
+ expect( node ).to be_valid
49
+ end
50
+ end
51
+
52
+
53
+ context "when node with given content doesn't exist in tree" do
54
+ it "doesn't add error" do
55
+ node = build_node(content_type: 'DummySinglenessValidatorModel', parent_id: root_node.id)
56
+ expect( node ).to be_valid
57
+ end
58
+ end
59
+
60
+ context "when node with given content exists in tree" do
61
+ before do
62
+ create_node(content_type: 'DummySinglenessValidatorModel', parent_id: root_node.id)
63
+ end
64
+
65
+ it "adds error to #content_type" do
66
+ node = build_node(content_type: 'DummySinglenessValidatorModel', parent_id: root_node.id)
67
+ expect( node ).to be_invalid
68
+ expect( node.errors[:content_type].size ).to eq(1)
69
+ expect( node.errors[:content_type] ).to include("node exists")
70
+ end
71
+
72
+ end
73
+
74
+ context "when node is saved, and is only one in the tree" do
75
+ it "doesn't add error" do
76
+ node = create_node(content_type: 'DummySinglenessValidatorModel', parent_id: root_node.id)
77
+ expect( node ).to be_valid
78
+ end
79
+ end
80
+ end
81
+
82
+ context "when scope is subtree" do
83
+ context "when has ancestor in :under list" do
84
+ let!(:grand_parent_node) { create_node(content_type: 'TextPage', parent_id: root_node.id) }
85
+ let!(:parent_node) { create_node(content_type: 'DummySinglenessValidator2Model', parent_id: grand_parent_node.id) }
86
+
87
+ context "when node with given content doesn't exist in subtree" do
88
+ it "doesn't add error" do
89
+ node = build_node(content_type: 'DummySinglenessValidator2Controller', parent_id: parent_node.id)
90
+ expect( node ).to be_valid
91
+ end
92
+ end
93
+
94
+ context "when node with given content exists in subtree" do
95
+ before do
96
+ create_node(content_type: 'DummySinglenessValidator2Controller', parent_id: grand_parent_node.id)
97
+ end
98
+
99
+ it "adds error to #content_type" do
100
+ node = build_node(content_type: 'DummySinglenessValidator2Controller', parent_id: parent_node.id)
101
+ expect( node ).to be_invalid
102
+ expect( node.errors[:content_type].size ).to eq(1)
103
+ expect( node.errors[:content_type] ).to include("node exists")
104
+ end
105
+
106
+ end
107
+
108
+ context "when node is saved, and is only one in subtree" do
109
+ it "doesn't add error" do
110
+ node = create_node(content_type: 'DummySinglenessValidator2Controller', parent_id: parent_node.id)
111
+ expect( node ).to be_valid
112
+ end
113
+ end
114
+
115
+
116
+ end
117
+
118
+ context "when node has no ancestor in :under list" do
119
+ it "doesn't add error" do
120
+ node = create_node(content_type: 'DummySinglenessValidator2Controller', parent_id: root_node.id)
121
+ expect( node ).to be_valid
122
+ end
123
+ end
124
+ end
125
+
126
+ context "regression tests" do
127
+ context "@node.parent.self_and_ancestors bug" do
128
+ it "works correctly / is worked around" do
129
+ # for details see Releaf::Content::Node::SinglenessValidator#base_relation_for_subtree
130
+ @node1 = create_node(content_type: 'TextPage', locale: 'en')
131
+ @node2 = create_node(content_type: 'TextPage', locale: 'lv')
132
+ @node3 = create_node(content_type: 'TextPage', locale: 'ru')
133
+ @node4 = create_node(content_type: 'TextPage', locale: 'sp')
134
+
135
+ @node1_1 = create_node(content_type: 'DummySinglenessValidator2Controller', parent: @node1)
136
+ expect do
137
+ @node1_2 = create_node(content_type: 'DummySinglenessValidator2Controller', parent: @node2)
138
+ @node1_3 = create_node(content_type: 'DummySinglenessValidator2Controller', parent: @node3)
139
+ @node1_4 = create_node(content_type: 'DummySinglenessValidator2Controller', parent: @node4)
140
+ end.to_not raise_error
141
+ end
142
+ end
143
+ end
144
+
145
+ end