CRUDtree 0.1

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