CRUDtree 0.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Simon Hafner aka Tass
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,42 @@
1
+ = CRUDtree
2
+
3
+ == Summary
4
+
5
+ A resource helper mainly for usher, but may be adapted for other routers as
6
+ well.
7
+
8
+ == See
9
+
10
+ Usher:: http://github.com/joshbuddy/usher
11
+ IRC:: #rango@irc.freenode.net
12
+ Baretest:: http://github.com/apeiros/baretest
13
+
14
+ == Terminology
15
+
16
+ Master:: The main body, only one per Usher instance as well.
17
+ Node:: You attach other Nodes or EndNodes as subnodes here.
18
+ EndNode:: A route endpoint.
19
+
20
+ == Usage
21
+
22
+ === as Tinkerer
23
+
24
+ CRUDtree::Interface.for(:usher_rack, rango: true).add_helper(CRUDtree::Interface::Helper)
25
+
26
+ Usher::Interface.for(:rack) do
27
+ node(klass: Posts) do
28
+ sub(type: :member, call: :show, rest: :get)
29
+ sub(type: :collection, call: :index, rest: :get)
30
+ end
31
+ end
32
+
33
+ === As Dev
34
+
35
+ CRUDtree::Interface.for(:usher_rack, rango: true)
36
+
37
+ Usher::Interface.for(:rack) do
38
+ resource(klass: Posts) do # the resource helper will include a bunch of default routes
39
+ member(call: :show, rest: :get)
40
+ collection(call: :index, rest: :get)
41
+ end
42
+ end
data/lib/crudtree.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'crudtree/tree'
2
+ require_relative 'crudtree/interface'
3
+ require_relative 'crudtree/generator'
@@ -0,0 +1,86 @@
1
+ module CRUDtree
2
+ class Generator
3
+ def initialize(master)
4
+ @master = master
5
+ @model_to_node = {}
6
+ @master.nodes.each {|node| add_node_models(node) }
7
+ end
8
+
9
+ def generate(resource, *names)
10
+ if resource.is_a? Symbol
11
+ names << resource
12
+ node = @master
13
+ url = ""
14
+ else
15
+ node, identifiers = find_node(resource)
16
+ url = generate_url_from_node(node, identifiers)
17
+ end
18
+ url << generate_from_sub(node, names) unless names.empty?
19
+ url
20
+ end
21
+
22
+ private
23
+ def find_node(resource)
24
+ case nodes = @model_to_node[resource.class]
25
+ when Node
26
+ [nodes, [resource.send(nodes.identifier)]]
27
+ when Array
28
+ valid_nodes = {}
29
+ nodes.each do |node|
30
+ identifiers = identifiers_to_node(resource, node)
31
+ valid_nodes[node] = identifiers if identifiers
32
+ end
33
+ parents = valid_nodes.keys.map(&:parents).flatten
34
+ valid_nodes.reject!{|node, identifiers| parents.include?(node)}
35
+ case valid_nodes.size
36
+ when 1
37
+ valid_nodes.first
38
+ when 0
39
+ raise(NoNode, "No node found for #{resource}.")
40
+ else
41
+ raise(NoUniqueNode, "No unique node found for #{resource}.")
42
+ end
43
+ end
44
+ end
45
+
46
+ def add_node_models(node)
47
+ @model_to_node[node.model] = if target_node = @model_to_node[node.model]
48
+ ([target_node] << node).flatten
49
+ else
50
+ node
51
+ end
52
+ node.nodes.each { |subnode|
53
+ add_node_models(subnode)
54
+ }
55
+ end
56
+
57
+ def generate_url_from_node(node, identifiers)
58
+ (node.parents.reverse + [node]).map {|parent|
59
+ "/#{parent.path}/#{identifiers.shift}"
60
+ }.join
61
+ end
62
+
63
+ # @return [Array] with [String] of identifiers or false if the model is invalid
64
+ # for this node
65
+ def identifiers_to_node(model, node, identifiers = [])
66
+ return false unless node.model == model.class
67
+ identifiers << model.send(node.identifier).to_s
68
+ unless node.parent_is_master?
69
+ identifiers_to_node(model.send(node.parent_call), node.parent, identifiers) or return false
70
+ end
71
+ identifiers.reverse
72
+ end
73
+
74
+ def generate_from_sub(node, names)
75
+ name = names.shift
76
+ sub = node.subs.find{|sub| sub.name == name} or raise(ArgumentError, "No subnode found on #{node} with name of #{name}.")
77
+ url = "/#{sub.path}"
78
+ url << generate_from_sub(sub, names) unless names.empty?
79
+ url
80
+ end
81
+ end
82
+
83
+ class InvalidPath < StandardError; end
84
+ class NoUniqueNode < StandardError; end
85
+ class NoNode < StandardError; end
86
+ end
@@ -0,0 +1,19 @@
1
+ module CRUDtree
2
+ module Interface
3
+ module Helper
4
+ def resource(params, &resource_block)
5
+ node(params) do
6
+ collection(call: :index, rest: :get)
7
+ collection(call: :new, rest: :get)
8
+ collection(call: :create, rest: :post, path: "")
9
+ member(call: :show, rest: :get)
10
+ member(call: :edit, rest: :get)
11
+ member(call: :update, rest: :put, path: "")
12
+ member(call: :delete, rest: :get)
13
+ member(call: :destroy, rest: :delete, path: "")
14
+ eval(&resource_block) if resource_block
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require_relative "tree"
2
+ module CRUDtree
3
+ module Interface
4
+ InterfaceRegistry = {}
5
+
6
+ def self.register(name, mod)
7
+ InterfaceRegistry[name.to_sym] = mod
8
+ end
9
+
10
+ def self.for(name)
11
+ InterfaceRegistry[name.to_sym].attach
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ require_relative "../interface"
2
+ module CRUDtree
3
+ module Interface
4
+ module Usher
5
+ # Integration part
6
+
7
+ def master_params
8
+ master.params
9
+ end
10
+
11
+ def master
12
+ @master ||= Master.new
13
+ end
14
+
15
+ # Logic part
16
+
17
+ def node(params, &block)
18
+ node = master.node(params, &block)
19
+ compile_node("", node)
20
+ end
21
+
22
+ private
23
+ def compile_node(pre_path, node)
24
+ paths = node.paths.map {|p| "#{pre_path}/#{p}"}
25
+ paths.each do |path|
26
+ node.subnodes.each do |subnode|
27
+ compile_subnode(path, subnode)
28
+ end
29
+ end
30
+ end
31
+
32
+ def compile_subnode(pre_path, subnode)
33
+ case subnode
34
+ when EndNode
35
+ compile_endnode(pre_path, subnode)
36
+ when Node
37
+ compile_node("#{pre_path}/:#{subnode.identifier}", subnode)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ require_relative '../usher'
2
+
3
+ module CRUDtree
4
+ module Interface
5
+ module Usher
6
+ module Rack
7
+ include CRUDtree::Interface::Usher
8
+
9
+ def self.attach
10
+ ::Usher::Interface.class_for(:rack).send(:include, self)
11
+ end
12
+
13
+ def self.add_helper(helper)
14
+ include helper
15
+ end
16
+
17
+ private
18
+ def compile_endnode(pre_path, endnode)
19
+ conditions = {}
20
+ conditions.merge!({request_method: endnode.rest.to_s.upcase}) if endnode.rest
21
+ method_call = [endnode.call]
22
+ method_call.unshift(master_params[:dispatcher])
23
+ # Here we call usher.
24
+ path(compile_path(pre_path, endnode), conditions: conditions).to(endnode.parent.klass.send(*method_call))
25
+ end
26
+
27
+ def compile_path(pre_path, endnode)
28
+ node = endnode.parent
29
+ compiled_path = [pre_path]
30
+ compiled_path << ":#{node.identifier}" if endnode.type == :member
31
+ compiled_path << "#{endnode.path}" unless endnode.path.empty?
32
+ compiled_path.join('/')
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ CRUDtree::Interface.register(:usher_rack, CRUDtree::Interface::Usher::Rack)
@@ -0,0 +1,3 @@
1
+ require_relative 'tree/master'
2
+ require_relative 'tree/node'
3
+ require_relative 'tree/endnode'
@@ -0,0 +1,40 @@
1
+ module CRUDtree
2
+ class EndNode
3
+ # The params Hash takes the following keys:
4
+ #
5
+ # :type
6
+ # may either be member or collection
7
+ #
8
+ # :path
9
+ # path to this endnode - you can use
10
+ # join
11
+ # or
12
+ # join/:date/:foo/:whatever
13
+ # the interface will handle those parameters.
14
+ # Defaults to call.to_s
15
+ #
16
+ # :rest
17
+ # which REST method should match this route. Defaults to nil, aka all.
18
+ #
19
+ # :call
20
+ # The method to be called if this route is matched. Required.
21
+ #
22
+ # :name
23
+ # The name of this route, used for generating. Symbol.
24
+ # Defaults to call
25
+ #
26
+ def initialize(parent, params)
27
+ @type = params[:type] if [:member, :collection].include? params[:type]
28
+ raise ArgumentError, "Invalid type: #{params[:type]}" unless @type
29
+ @call = params[:call] or raise ArgumentError, "No call given."
30
+ @path = params[:path] || @call.to_s
31
+ raise ArgumentError, "No path given." unless @path
32
+ @rest = params[:rest]
33
+ @name = params[:name] || @call
34
+ @parent = parent
35
+ end
36
+
37
+ attr_reader :type, :path, :rest, :call, :name, :parent
38
+
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ module CRUDtree
2
+ class Master
3
+
4
+ # Use :rango => true if you're using rango
5
+ def initialize(params = {})
6
+ @nodes = []
7
+ @params = {dispatcher: :dispatcher}.merge(params)
8
+ end
9
+
10
+ attr_reader :nodes
11
+ attr_accessor :mapping, :params
12
+
13
+ alias_method :subs, :nodes
14
+
15
+ def node(params, &block)
16
+ @nodes << new_node = Node.new(self, params, &block)
17
+ new_node
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,121 @@
1
+ module CRUDtree
2
+ class Node
3
+ # The params Hash takes the following keys:
4
+ #
5
+ # :klass
6
+ # The object where to send the method that is returned by the router.
7
+ # Mostly a class, therefore it's called 'class_name'. Defaults to nil,
8
+ # but the interface may complain. You have been warned ;).
9
+ # May be used by the interface as needed (Rango wants Class.send
10
+ # :dispatcher, :send_method)
11
+ #
12
+ # :identifier
13
+ # The identifier used to identify a resource, aka /user/:name instead of
14
+ # /user/:id (default).
15
+ #
16
+ # :default_collection
17
+ # The collection that is called when nothing is given. Defaults to :index.
18
+ #
19
+ # :default_member
20
+ # The member which is chosen when no method is given. Defaults to :show.
21
+ #
22
+ # :paths
23
+ # Specify the path(s) you want to call this resource with.
24
+ # Defaults to klass.to_s.downcase
25
+ # The first one is used for generation.
26
+ #
27
+ # Options used for generating
28
+ #
29
+ # :model
30
+ # The name of the model. Needed. We don't do magic here.
31
+ #
32
+ # :parent_call
33
+ # The method to call on the model object to get its parent (for nested
34
+ # resources). Defaults to :model.downcase of the parent.
35
+ #
36
+ # :name
37
+ # Symbol used to identify the node when generating a collection route.
38
+ # Defaults to Klass.to_s.downcase.to_sym
39
+ #
40
+ def initialize(parent, params, &block)
41
+ @klass = params[:klass]
42
+ @identifier = params[:identifier] || :id
43
+ @default_collection = params[:default_collection] || :index
44
+ @default_member = params[:default_member] || :show
45
+ @paths = if params[:paths]
46
+ [params[:paths]].flatten
47
+ elsif params[:klass]
48
+ [params[:klass].to_s.downcase.split("::").last]
49
+ else
50
+ raise ArgumentError, "No paths given"
51
+ end
52
+ @subnodes = []
53
+ @parent = parent
54
+ # default routes
55
+ @subnodes.unshift(EndNode.new(self, type: :member, call: :show, path: "", rest: :get))
56
+ @subnodes.unshift(EndNode.new(self, type: :collection, call: :index, path: "", rest: :get))
57
+ # generating
58
+ unless @model = params[:model]
59
+ raise(ArgumentError, "No model given.")
60
+ end
61
+ @parent_call = if params[:parent_call]
62
+ params[:parent_call]
63
+ elsif ! parent_is_master?
64
+ parent.model.to_s.split('::').last.downcase
65
+ else
66
+ nil
67
+ end
68
+ @name = params[:name] || klass.to_s.downcase.to_sym
69
+ block ? instance_eval(&block) : raise(ArgumentError, "No block given.")
70
+ end
71
+
72
+ attr_reader :klass, :identifier, :default_collection, :default_member, :paths, :parent, :subnodes, :model, :parent_call, :name
73
+
74
+ # Creates a new End and attaches it to this Node.
75
+ def endnode(params)
76
+ @subnodes << EndNode.new(self, params)
77
+ end
78
+
79
+ # Creates a new endnode with type member. See Endnode.
80
+ def member(params)
81
+ endnode(params.merge({type: :member}))
82
+ end
83
+
84
+ # Creates a new endnode with type collection. See EndNode.
85
+ def collection(params)
86
+ endnode(params.merge({type: :collection}))
87
+ end
88
+
89
+ def node(params, &block)
90
+ @subnodes << Node.new(self, params, &block)
91
+ end
92
+
93
+ def parents
94
+ [find_parent(self)].flatten[0..-2]
95
+ end
96
+
97
+ def nodes
98
+ subnodes.select{|subnode| subnode.is_a? Node}
99
+ end
100
+
101
+ def endnodes
102
+ subnodes.select{|subnode| subnode.is_a? EndNode}
103
+ end
104
+
105
+ def parent_is_master?
106
+ ! parent.respond_to? :parent
107
+ end
108
+
109
+ # Duck typing used for generation
110
+ def path
111
+ @paths.first
112
+ end
113
+
114
+ private
115
+ def find_parent(node)
116
+ if node.parent.respond_to? :parent
117
+ [node.parent, find_parent(node.parent)]
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,44 @@
1
+ class Model0
2
+ def id
3
+ 0
4
+ end
5
+ end
6
+ class Model1
7
+ end
8
+ class Model2
9
+ def id
10
+ 2
11
+ end
12
+ def model0
13
+ Model0.new
14
+ end
15
+ end
16
+ class Klass0
17
+ end
18
+ class Klass1
19
+ end
20
+ class Klass2
21
+ end
22
+
23
+ class Mod0
24
+ def id
25
+ 0
26
+ end
27
+ end
28
+ class Mod1
29
+ def id
30
+ 1
31
+ end
32
+ def mod0
33
+ Mod0.new
34
+ end
35
+ end
36
+ class Mod2
37
+ def id
38
+ 2
39
+ end
40
+ def initialize(parent = Mod0)
41
+ instance_variable_set("@#{parent.to_s.downcase}", parent.new)
42
+ end
43
+ attr_accessor :mod0, :mod1, :mod2
44
+ end
@@ -0,0 +1,9 @@
1
+ module Usher
2
+ module Interface
3
+ def self.class_for(interface)
4
+ Rack
5
+ end
6
+ class Rack
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,36 @@
1
+ def results_in(path, klass, call, params)
2
+ klass.expects(:dispatcher).with(call).returns(:yeah!)
3
+ to_mock = mock('to')
4
+ to_mock.expects(:to).with(:yeah!).returns(true)
5
+ Usher::Interface.class_for(:rack).any_instance.expects(:path).with(path, params).returns(to_mock)
6
+ end
7
+
8
+ module Usher; module Interface; class Rack;
9
+ include CRUDtree::Interface::Usher::Rack
10
+ def initialize(app = nil, options = nil, &block)
11
+ instance_eval(&block) if block
12
+ end
13
+ end; end; end
14
+ class TestObj0; end
15
+ class TestObj1; end
16
+ class TestObj2; end
17
+
18
+ def default_routes(number=0)
19
+ default_route(TestObj0)
20
+ default_route(TestObj1, "/testobj0/:id") if number > 0
21
+ default_route(TestObj2, "/testobj0/:id/testobj1/:id") if number > 1
22
+ end
23
+
24
+ def default_route(klass, pre_path=nil)
25
+ results_in("#{pre_path}/#{klass.to_s.downcase}", klass, :index, :conditions => {:request_method => "GET"})
26
+ results_in("#{pre_path}/#{klass.to_s.downcase}/:id", klass, :show, :conditions => {:request_method => "GET"})
27
+ end
28
+ module Usher
29
+ module Interface
30
+ def self.class_for(interface)
31
+ Rack
32
+ end
33
+ class Rack
34
+ end
35
+ end
36
+ end
data/test/setup.rb ADDED
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift(File.expand_path("#{__FILE__}/../../lib")) # Add PROJECT/lib to $LOAD_PATH
2
+ require 'crudtree'
3
+ require 'baretest/mocha'
4
+ include CRUDtree
5
+ include Mocha::API
6
+ require 'ruby-debug'
@@ -0,0 +1,218 @@
1
+ require 'ostruct'
2
+ BareTest.suite do
3
+
4
+ suite "CRUDtree" do
5
+
6
+ suite "Generator" do
7
+
8
+ suite "#generate_url_from_node" do
9
+
10
+ setup :node, "a simple node" do
11
+ @node = Node.new(nil, :paths => ["foo"], klass: Object, model: Object ){:foo}
12
+ @path = "/foo/23"
13
+ @identifiers = [23]
14
+ end
15
+
16
+ setup :node, "a node with multiple parents" do
17
+ master = Master.new
18
+ master.node(klass: Object, model: Object, paths: ["foo"]) do
19
+ node(klass: Object, model: Object, paths: ["bar"]) do
20
+ node(klass: Object, model: Object, paths: ["baz"]) {:foo}
21
+ end
22
+ end
23
+ @node = master.nodes.first.nodes.first.nodes.first
24
+ @path = "/foo/1/bar/2/baz/3"
25
+ @identifiers = [1,2,3]
26
+ end
27
+
28
+ assert ":node goes to the right path" do
29
+ equal(@path, CRUDtree::Generator.allocate.send(:generate_url_from_node, @node, @identifiers))
30
+ end
31
+
32
+ end
33
+
34
+ suite "#model_to_node" do
35
+
36
+ setup do
37
+ class TestObj0; end
38
+ class TestObj1; end
39
+ end
40
+
41
+ setup :master, "a simple master" do
42
+ master = CRUDtree::Master.new
43
+ master.node(klass: Object, model: ::TestObj0) {:foo}
44
+ @generator = CRUDtree::Generator.new(master)
45
+ @node = master.nodes.first
46
+ @model = ::TestObj0
47
+ end
48
+
49
+ setup :master, "a complex master" do
50
+ master = CRUDtree::Master.new
51
+ master.node(klass: Object, model: ::TestObj1){
52
+ node(klass: Object, model: ::TestObj0){:foo}
53
+ }
54
+ @generator = CRUDtree::Generator.new(master)
55
+ @node = master.nodes.first.nodes.first
56
+ @model = ::TestObj0
57
+ end
58
+
59
+ setup :master, "a master with duplicate models" do
60
+ master = CRUDtree::Master.new
61
+ master.node(klass: Object, model: TestObj1){
62
+ node(klass: Object, model: ::TestObj0){:foo}
63
+ node(klass: Object, model: ::TestObj0){:foo}
64
+ }
65
+ @generator = CRUDtree::Generator.new(master)
66
+ @node = master.nodes.first.nodes
67
+ @model = ::TestObj0
68
+ end
69
+
70
+ assert "The right model is found on :master." do
71
+ equal(@node, @generator.instance_variable_get(:@model_to_node)[@model])
72
+ end
73
+
74
+ end
75
+
76
+ suite "node finding" do
77
+
78
+ suite "without duplicates" do
79
+
80
+ setup do
81
+ @master = CRUDtree::Master.new
82
+ end
83
+
84
+ setup :master, "a simple master" do
85
+ @node = @master.node(klass: Klass0, model: Model0){:foo}
86
+ @resource = Model0.new
87
+ @valid = [[Model0.new, @node, ["0"]]]
88
+ @invalid = [[Model2.new, @node], [Model1.new, @node]]
89
+ end
90
+
91
+ setup :master, "a complex master" do
92
+ @master.node(klass: Klass0, model: Model0) do
93
+ node(klass: Klass1, model: Model1){:foo}
94
+ node(klass: Klass2, model: Model2){:foo}
95
+ end
96
+ @node = @master.nodes.first.nodes.last
97
+ @resource = Model2.new
98
+ @valid = [[Model2.new, @node, ["0","2"]], [Model0.new, @master.nodes.first, ["0"]]]
99
+ @invalid = [[Model0.new, @node], [Model1.new, @node]]
100
+ end
101
+
102
+ assert "#identifiers_returns the corret identifiers with :master" do
103
+ generator = CRUDtree::Generator.new(@master)
104
+ @valid.all?{|model, node, ary| generator.send(:identifiers_to_node, model, node) == ary}
105
+ end
106
+
107
+ assert "#identifiers returns false invalid with :master" do
108
+ generator = CRUDtree::Generator.new(@master)
109
+ @invalid.all?{|model, node| ! generator.send(:identifiers_to_node, model, node)}
110
+ end
111
+
112
+ assert "#find_node gets the correct node from :master" do
113
+ same(@node, CRUDtree::Generator.new(@master).send(:find_node, @resource).first)
114
+ end
115
+
116
+ end
117
+
118
+ suite "with duplicates" do
119
+
120
+ setup do
121
+ @master = Master.new
122
+ @master.node(klass: Klass0, model: Mod0) do
123
+ node(klass: Klass1, model: Mod1){:foo}
124
+ node(klass: Klass2, model: Mod2) do
125
+ node(klass: Klass2, model: Mod2){:foo}
126
+ end
127
+ end
128
+ @generator = CRUDtree::Generator.new(@master)
129
+ end
130
+
131
+ setup :node, "nested node" do
132
+ @node = @master.nodes.first.nodes.last.nodes.first
133
+ @resource = Mod2.new(Mod2)
134
+ @identifier = %w(0 2 2)
135
+ end
136
+
137
+ setup :node, "first node" do
138
+ @node = @master.nodes.first.nodes.last
139
+ @resource = Mod2.new(Mod0)
140
+ @identifier = %w(0 2)
141
+ end
142
+
143
+ assert "#identifiers_to_node returns an Array of identifiers for the :node" do
144
+ equal(@identifier, @generator.send(:identifiers_to_node, @resource, @node))
145
+ end
146
+
147
+ assert "#find_node gets :node based on the model" do
148
+ same(@node, @generator.send(:find_node, @resource).first)
149
+ end
150
+
151
+ end
152
+
153
+ end
154
+
155
+ suite "#generate_from_sub" do
156
+
157
+ setup do
158
+ @foo = OpenStruct.new(:name => :foo, :path => "foo")
159
+ @bar = OpenStruct.new(:name => :bar, :path => "bar")
160
+ end
161
+
162
+ setup :names, "one name" do
163
+ @node = OpenStruct.new(:subs => [@foo, @bar])
164
+ @names = [:foo]
165
+ @path = "/foo"
166
+ end
167
+
168
+ setup :names, "stacked names" do
169
+ @node = OpenStruct.new(:subs => [OpenStruct.new(:subs => [@foo, @bar], :name => :foo, :path => "foo"), @bar])
170
+ @names = [:foo, :bar]
171
+ @path = "/foo/bar"
172
+ end
173
+
174
+ assert "generates the correct url from :names" do
175
+ equal @path, Generator.allocate.send(:generate_from_sub, @node, @names)
176
+ end
177
+
178
+ end
179
+
180
+ suite "blackbox" do
181
+
182
+ setup do
183
+ @master = Master.new
184
+ @master.node(klass: Klass0, model: Mod0) do
185
+ node(klass: Klass1, model: Mod1){:foo}
186
+ node(klass: Klass2, model: Mod2) do
187
+ node(klass: Klass2, model: Mod2){:foo}
188
+ end
189
+ end
190
+ @generator = CRUDtree::Generator.new(@master)
191
+ end
192
+
193
+ setup :model, "model for the nested node" do
194
+ @resource = Mod2.new(Mod2)
195
+ @path = "/klass0/0/klass2/2/klass2/2"
196
+ end
197
+
198
+ setup :model, "model for the first node" do
199
+ @resource = Mod2.new(Mod0)
200
+ @path = "/klass0/0/klass2/2"
201
+ end
202
+
203
+ setup :model, "model for the simplest node" do
204
+ @resource = Mod0.new
205
+ @path = "/klass0/0"
206
+ end
207
+
208
+ assert "#generate" do
209
+ equal(@path, @generator.generate(@resource))
210
+ end
211
+
212
+ end
213
+
214
+ end
215
+
216
+ end
217
+
218
+ end
@@ -0,0 +1,33 @@
1
+ require 'crudtree/interface/usher/rack'
2
+ module Usher
3
+ module Interface
4
+ class Rack
5
+ def initialize(app = nil, options = nil, &blk)
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ BareTest.suite do
12
+
13
+ suite "CRUDtree" do
14
+
15
+ suite "integartion" do
16
+
17
+ suite "inclusion" do
18
+
19
+ setup do
20
+ CRUDtree::Interface.for(:usher_rack)
21
+ end
22
+
23
+ assert "the module gets included and the methods are avaible" do
24
+ Usher::Interface.class_for(:rack).new.respond_to? :node
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,132 @@
1
+ BareTest.suite "CRUDtree" do
2
+
3
+ suite "blackbox" do
4
+
5
+ setup :tree, "a simple tree" do
6
+ @block = proc {
7
+ node(model: Object, klass: TestObj0) do
8
+ collection(call: :index, rest: :get)
9
+ end
10
+ }
11
+ results_in("/testobj0/index", TestObj0, :index, :conditions => {:request_method => "GET"})
12
+ # default routes
13
+ default_routes
14
+ end
15
+
16
+ setup :tree, "a nested tree" do
17
+ @block = proc {
18
+ node(model: Object, klass: TestObj0) do
19
+ node(model: Object, klass: TestObj1) do
20
+ member(call: :show, rest: :get)
21
+ end
22
+ end
23
+ }
24
+ results_in("/testobj0/:id/testobj1/:id/show", TestObj1, :show, :conditions => {:request_method => "GET"})
25
+ # default routes
26
+ default_routes(1)
27
+ end
28
+
29
+ setup :tree, "a twice nested tree" do
30
+ @block = proc {
31
+ node(model: Object, klass: TestObj0) do
32
+ node(model: Object, klass: TestObj1) do
33
+ node(model: Object, klass: TestObj2) do
34
+ collection(call: :create, rest: :post)
35
+ end
36
+ end
37
+ end
38
+ }
39
+ results_in("/testobj0/:id/testobj1/:id/testobj2/create", TestObj2, :create, :conditions => {:request_method => "POST"})
40
+ # default routes
41
+ default_routes(2)
42
+ end
43
+
44
+ setup :tree, "a tree with some subnodes" do
45
+ @block = proc {
46
+ node(model: Object, klass: TestObj0) do
47
+ collection(call: :index, rest: :get)
48
+ member(call: :show, rest: :get)
49
+ member(call: :update, path: "edit", rest: :put)
50
+ end
51
+ }
52
+ results_in("/testobj0/index", TestObj0, :index, :conditions => {:request_method => "GET"})
53
+ results_in("/testobj0/:id/edit", TestObj0, :update, :conditions => {:request_method => "PUT"})
54
+ results_in("/testobj0/:id/show", TestObj0, :show, :conditions => {:request_method => "GET"})
55
+ # default routes
56
+ default_routes
57
+ end
58
+
59
+ setup :tree, "a nested tree with some subnodes" do
60
+ @block = proc {
61
+ node(model: Object, klass: TestObj0) do
62
+ member(call: :show, rest: :get)
63
+ node(model: Object, klass: TestObj1) do
64
+ collection(call: :index, rest: :get)
65
+ member(call: :show, rest: :get)
66
+ member(call: :update, path: "edit", rest: :put)
67
+ end
68
+ end
69
+ }
70
+ results_in("/testobj0/:id/show", TestObj0, :show, :conditions => {:request_method => "GET"})
71
+ results_in("/testobj0/:id/testobj1/index", TestObj1, :index, :conditions => {:request_method => "GET"})
72
+ results_in("/testobj0/:id/testobj1/:id/show", TestObj1, :show, :conditions => {:request_method => "GET"})
73
+ results_in("/testobj0/:id/testobj1/:id/edit", TestObj1, :update, :conditions => {:request_method => "PUT"})
74
+ # default routes
75
+ default_routes(1)
76
+ end
77
+
78
+ setup :tree, "a tree with multiple paths" do
79
+ @block = proc {
80
+ node(model: Object, paths: ["test", "foo", "bar"], klass: TestObj0) do
81
+ collection(call: :index, rest: :get)
82
+ end
83
+ }
84
+ results_in("/test/index", TestObj0, :index, :conditions => {:request_method => "GET"})
85
+ results_in("/foo/index", TestObj0, :index, :conditions => {:request_method => "GET"})
86
+ results_in("/bar/index", TestObj0, :index, :conditions => {:request_method => "GET"})
87
+ # default routes
88
+ results_in("/test", TestObj0, :index, :conditions => {:request_method => "GET"})
89
+ results_in("/foo", TestObj0, :index, :conditions => {:request_method => "GET"})
90
+ results_in("/bar", TestObj0, :index, :conditions => {:request_method => "GET"})
91
+ results_in("/test/:id", TestObj0, :show, :conditions => {:request_method => "GET"})
92
+ results_in("/foo/:id", TestObj0, :show, :conditions => {:request_method => "GET"})
93
+ results_in("/bar/:id", TestObj0, :show, :conditions => {:request_method => "GET"})
94
+ end
95
+
96
+ setup :tree, "a nested tree with multiple paths" do
97
+ @block = proc {
98
+ node(model: Object, paths: ["foo", "bar"], klass: TestObj0) do
99
+ collection(call: :index, rest: :get)
100
+ node(model: Object, paths: ["test", "baz"], klass: TestObj1) do
101
+ member(call: :edit, rest: :get)
102
+ end
103
+ end
104
+ }
105
+ results_in("/foo/index", TestObj0, :index, :conditions => {:request_method => "GET"})
106
+ results_in("/bar/index", TestObj0, :index, :conditions => {:request_method => "GET"})
107
+ results_in("/foo/:id/test/:id/edit", TestObj1, :edit, :conditions => {:request_method => "GET"})
108
+ results_in("/bar/:id/test/:id/edit", TestObj1, :edit, :conditions => {:request_method => "GET"})
109
+ results_in("/foo/:id/baz/:id/edit", TestObj1, :edit, :conditions => {:request_method => "GET"})
110
+ results_in("/bar/:id/baz/:id/edit", TestObj1, :edit, :conditions => {:request_method => "GET"})
111
+ # default routes
112
+ results_in("/foo", TestObj0, :index, :conditions => {:request_method => "GET"})
113
+ results_in("/bar", TestObj0, :index, :conditions => {:request_method => "GET"})
114
+ results_in("/foo/:id", TestObj0, :show, :conditions => {:request_method => "GET"})
115
+ results_in("/bar/:id", TestObj0, :show, :conditions => {:request_method => "GET"})
116
+ results_in("/foo/:id/test", TestObj1, :index, :conditions => {:request_method => "GET"})
117
+ results_in("/bar/:id/test", TestObj1, :index, :conditions => {:request_method => "GET"})
118
+ results_in("/foo/:id/baz", TestObj1, :index, :conditions => {:request_method => "GET"})
119
+ results_in("/bar/:id/baz", TestObj1, :index, :conditions => {:request_method => "GET"})
120
+ results_in("/foo/:id/test/:id", TestObj1, :show, :conditions => {:request_method => "GET"})
121
+ results_in("/bar/:id/test/:id", TestObj1, :show, :conditions => {:request_method => "GET"})
122
+ results_in("/foo/:id/baz/:id", TestObj1, :show, :conditions => {:request_method => "GET"})
123
+ results_in("/bar/:id/baz/:id", TestObj1, :show, :conditions => {:request_method => "GET"})
124
+ end
125
+
126
+ assert "compilation of :tree" do
127
+ Usher::Interface.class_for(:rack).new(&@block)
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,55 @@
1
+ require 'crudtree/interface/usher'
2
+ require 'ostruct'
3
+
4
+ module CRUDtree::Interface::Usher
5
+ public :compile_subnode, :compile_node
6
+ extend self
7
+ end
8
+
9
+ BareTest.suite "CRUDtree" do
10
+
11
+ suite "interface" do
12
+
13
+ suite "usher" do
14
+
15
+ suite "#compile_subnode" do
16
+
17
+ setup :subnode, "Node" do
18
+ @pre_path = "/foo"
19
+ @subnode = Node.new(nil, klass: Object, paths: "foo", model: Object) {:foo}
20
+ CRUDtree::Interface::Usher.expects(:compile_node).with("/foo/:id", @subnode).returns(true)
21
+ end
22
+
23
+ setup :subnode, "EndNode" do
24
+ @pre_path = ""
25
+ @subnode = EndNode.allocate
26
+ CRUDtree::Interface::Usher.expects(:compile_subnode).with(@pre_path, @subnode).returns(true)
27
+ end
28
+
29
+ assert "Compilation with a :subnode as subnode" do
30
+ CRUDtree::Interface::Usher.compile_subnode(@pre_path, @subnode)
31
+ end
32
+
33
+ end
34
+
35
+ suite "#compile_node" do
36
+
37
+ setup do
38
+ @subnode = Object.new
39
+ @pre_path = "/baz"
40
+ @node = OpenStruct.new(paths: ["foo", "bar"], subnodes: [@subnode])
41
+ CRUDtree::Interface::Usher.expects(:compile_subnode).with("/baz/foo", @subnode).returns(true)
42
+ CRUDtree::Interface::Usher.expects(:compile_subnode).with("/baz/bar", @subnode).returns(true)
43
+ end
44
+
45
+ assert "compilation" do
46
+ CRUDtree::Interface::Usher.compile_node(@pre_path, @node)
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,90 @@
1
+ require 'crudtree/interface/usher/rack'
2
+ require 'ostruct'
3
+
4
+
5
+
6
+ BareTest.suite "CRUDtree" do
7
+
8
+ suite "interface" do
9
+
10
+ suite "usher" do
11
+
12
+ suite "rack" do
13
+
14
+ setup do
15
+ module CRUDtree::Interface::Usher::Rack
16
+ public :compile_path
17
+ extend self
18
+ end
19
+
20
+ class TestObj < Object; end
21
+ class TestObj2 < Object; end
22
+ class Cont1 < Object; end
23
+ class Cont2 < Object; end
24
+ end
25
+
26
+ suite "#compile_path" do
27
+
28
+ setup :subnode, "a simple member subnode" do
29
+ @pre_path = ""
30
+ @node = OpenStruct.new(klass: TestObj, identifier: :id, paths: "testobj")
31
+ @subnode = EndNode.new(@node, type: :member, call: :foo, name: "foo")
32
+ @path = "/testobj/:id/foo"
33
+ @params = {conditions: {}}
34
+ @send = [:foo]
35
+ @master_params = {}
36
+ end
37
+
38
+ setup :subnode, "a more complex collection subnode" do
39
+ @pre_path = "/bar"
40
+ @node = OpenStruct.new(klass: TestObj, identifier: :id, paths: "testobj")
41
+ @subnode = EndNode.new(@node, type: :collection, call: :foo, name: "foo")
42
+ @path = "/bar/testobj/foo"
43
+ @params = {conditions: {}}
44
+ @send = [:dispatcher, :foo]
45
+ @master_params = {rango: true}
46
+ end
47
+
48
+ setup :subnode, "a nested collection subnode" do
49
+ @pre_path = ""
50
+ @node1 = Node.new(nil, klass: Cont1, identifier: :id, paths: "cont1", model: Object){:foo}
51
+ @node2 = Node.new(@node1, klass: TestObj, model: Object){:foo}
52
+ @subnode = EndNode.new(@node2, type: :collection, call: :foo, name: "foo", model: Object)
53
+ @path = "/cont1/:id/testobj/foo"
54
+ @params = {conditions: {}}
55
+ @send = [:dispatcher, :foo]
56
+ @master_params = {rango: true}
57
+ end
58
+
59
+ setup :subnode, "a nested member subnode" do
60
+ @pre_path = ""
61
+ @node1 = Node.new(nil, klass: Cont1, identifier: :id, paths: "cont1", model: Object){:foo}
62
+ @node2 = Node.new(@node1, klass: TestObj, model: Object){:foo}
63
+ @subnode = EndNode.new(@node2, type: :member, call: :foo, name: "foo")
64
+ @path = "/cont1/:id/testobj/:id/foo"
65
+ @params = {conditions: {}}
66
+ @send = [:foo]
67
+ @master_params = {}
68
+ end
69
+
70
+ setup :foo, "Foo" do
71
+ TestObj.expects(:send).with(*@send).returns(:yeah)
72
+ TestObj2.any_instance.expects(:to).with(:yeah)
73
+ CRUDtree::Interface::Usher::Rack.expects(:master_params).returns(@master_params)
74
+ CRUDtree::Interface::Usher::Rack.expects(:path).with(@path, @params).returns(TestObj2.new)
75
+ end
76
+
77
+ assert "compilaton with :subnode" do
78
+ CRUDtree::Interface::Usher::Rack.compile_path(@pre_path, @subnode)
79
+ true
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,27 @@
1
+ BareTest.suite "CRUDtree" do
2
+
3
+ suite "tree" do
4
+
5
+ suite "EndNode" do
6
+
7
+ suite "initialize" do
8
+
9
+ assert ":type should only accept :member or :collection" do
10
+ raises(ArgumentError) {EndNode.new(nil, type: :foo, call: :foo)}
11
+ end
12
+
13
+ assert ":call should not accept an empty argument" do
14
+ raises(ArgumentError) {EndNode.new(nil, type: :member, path: "/foo")}
15
+ end
16
+
17
+ assert ":path should default to :call" do
18
+ EndNode.new(nil, type: :member, call: :foo, name: :foo).path == "foo"
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,9 @@
1
+ BareTest.suite "CRUDtree" do
2
+
3
+ suite "tree" do
4
+
5
+ suite "master"
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,91 @@
1
+ BareTest.suite "CRUDtree" do
2
+
3
+ suite "tree" do
4
+
5
+ suite "node" do
6
+
7
+ suite "initialize" do
8
+
9
+ suite "paths" do
10
+
11
+ assert "defaults to the name of the class" do
12
+ Node.new(nil, klass: Node, model: Object){:foo}.paths == ["node"]
13
+ end
14
+
15
+ assert "raises if no path is given" do
16
+ raises(ArgumentError) {Node.new(nil, foo: :bar){:foo}}
17
+ end
18
+
19
+ end
20
+
21
+ assert "raises if no block is given" do
22
+ raises(ArgumentError) {Node.new(nil, foo: :bar)}
23
+ end
24
+
25
+ end
26
+
27
+ suite "extended subnodes" do
28
+
29
+ setup :method, "#member" do
30
+ @method = :member
31
+ end
32
+
33
+ setup :method, "#collection" do
34
+ @method = :collection
35
+ end
36
+
37
+ setup :node, "node" do
38
+ @node = Node.allocate
39
+ @params = {foo: :bar}
40
+ @result = @params.merge({type: @method})
41
+ @node.expects(:endnode).with(@result)
42
+ end
43
+
44
+ assert ":method" do
45
+ case @method
46
+ when :member
47
+ @node.member @params
48
+ when :collection
49
+ @node.collection @params
50
+ end
51
+ true
52
+ end
53
+
54
+ end
55
+
56
+ suite "tree walking" do
57
+
58
+ setup :master, "a simple master" do
59
+ @master = Master.new
60
+ @node = @master.node(klass: Object, model: Object){:foo}
61
+ @parents = []
62
+ @children = {}
63
+ end
64
+
65
+ suite "#parents" do
66
+
67
+ setup :master, "a more complex master" do
68
+ @master = Master.new
69
+ @master.node(klass: Object, model: Object){
70
+ node(klass: Object, model: Object){
71
+ node(klass: Object, model: Object){:foo}
72
+ node(klass: Object, model: Object){:foo}
73
+ }
74
+ }
75
+ @node = @master.nodes.first.nodes.first.nodes.first
76
+ @parents = [@master.nodes.first.nodes.first,@master.nodes.first]
77
+ end
78
+
79
+ assert "it find the parents in :master" do
80
+ equal(@node.parents, @parents)
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+
91
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: CRUDtree
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Simon Hafner aka Tass
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ date: 2010-01-25 00:00:00 +01:00
12
+ default_executable:
13
+ dependencies: []
14
+
15
+ description: ""
16
+ email: hafnersimon@gmail.com
17
+ executables: []
18
+
19
+ extensions: []
20
+
21
+ extra_rdoc_files: []
22
+
23
+ files:
24
+ - lib/crudtree/generator.rb
25
+ - lib/crudtree/helper.rb
26
+ - lib/crudtree/interface/usher/rack.rb
27
+ - lib/crudtree/interface/usher.rb
28
+ - lib/crudtree/interface.rb
29
+ - lib/crudtree/tree/endnode.rb
30
+ - lib/crudtree/tree/master.rb
31
+ - lib/crudtree/tree/node.rb
32
+ - lib/crudtree/tree.rb
33
+ - lib/crudtree.rb
34
+ - test/helper/suite/lib/generator.rb
35
+ - test/helper/suite/lib/integration.rb
36
+ - test/helper/suite/lib/interface/blackbox.rb
37
+ - test/setup.rb
38
+ - test/suite/lib/generator.rb
39
+ - test/suite/lib/integration.rb
40
+ - test/suite/lib/interface/blackbox.rb
41
+ - test/suite/lib/interface/usher/rack.rb
42
+ - test/suite/lib/interface/usher.rb
43
+ - test/suite/lib/tree/endnode.rb
44
+ - test/suite/lib/tree/master.rb
45
+ - test/suite/lib/tree/node.rb
46
+ - LICENSE
47
+ - README.rdoc
48
+ has_rdoc: true
49
+ homepage: http://github.com/Tass/CRUDtree
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: "1.9"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: A resource helper mainly for usher, but may be adapted for other routers as well.
76
+ test_files: []
77
+