GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Rubygem
Description: Merb Core: All you need. None you don't.
Homepage: http://www.merbivore.com
Clone URL: git://github.com/wycats/merb-core.git
Added array identifiers for generating routes

This allows resources with multi-column keys to generate the related
routes by only passing an instance of the object in question. For
example:

Merb::Router.prepare do
  identify(User => [:name, :age]) do
    resources :users
  end
end

resource(User.new(:name => "carl", :age => 25))
  /users/carl/25
carllerche (author)
Mon Oct 06 17:53:54 -0700 2008
commit  338d731645bf9bde7f9bc82f855925fb96f16792
tree    79e0b3bd7b94ca0a833b1c0bf4e5b6964cbcc9f2
parent  1e9ee1f7ccb149b87e957cf89b9ff89630940c76
...
79
80
81
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
84
85
86
87
 
 
 
 
88
89
90
91
92
93
94
95
96
 
 
 
 
 
 
 
 
97
98
99
...
101
102
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
 
105
106
107
...
258
259
260
261
 
262
263
264
...
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
...
79
80
81
 
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
 
 
 
 
116
117
118
119
120
121
122
123
124
125
126
...
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
...
325
326
327
 
328
329
330
331
...
334
335
336
 
 
 
 
 
 
 
 
 
 
337
338
339
0
@@ -79,21 +79,48 @@ module Merb
0
         @name
0
       end
0
       
0
- # === Compiled method ===
0
+ # Generates the URL for the route given the passed arguments. The
0
+ # method will first match the anonymous parameters to route params
0
+ # and will convert all the parameters according to the specifed
0
+ # object identifiers.
0
+ #
0
+ # Then the named parameters are passed to a compiled generation proc.
0
+ #
0
+ # ==== Parameters
0
+ # args<Array>::
0
+ # The arguments passed to the public #url method with the name
0
+ # of the route removed. This is an array of the anonymous parameters
0
+ # followed by a hash containing the named parameters.
0
+ #
0
+ # defaults<Hash>::
0
+ # A hash of parameters to use to generate the route if there are
0
+ # any missing required parameters. This is usually the parameters
0
+ # for the current request
0
+ #
0
+ # ==== Returns
0
+ # String:: The generated URL.
0
       def generate(args = [], defaults = {})
0
         raise GenerationError, "Cannot generate regexp Routes" if regexp?
0
         
0
         params = extract_options_from_args!(args) || { }
0
         
0
+ params.each do |k, v|
0
+ params[k] = identify(v, k)
0
+ end
0
+
0
         # Support for anonymous params
0
         unless args.empty?
0
           # First, let's determine which variables are missing
0
           variables = @variables - params.keys
0
           
0
- raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if args.length > variables.length
0
-
0
- args.each_with_index do |param, i|
0
- params[variables[i]] ||= param
0
+ args.each do |param|
0
+ raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if variables.empty?
0
+
0
+ if identifier = identifier_for(param) and identifier.is_a?(Array)
0
+ identifier.each { |ident| params[variables.shift] = param.send(ident) }
0
+ else
0
+ params[variables.shift] ||= identify(param)
0
+ end
0
           end
0
         end
0
         
0
@@ -101,7 +128,47 @@ module Merb
0
         uri = Merb::Config[:path_prefix] + uri if Merb::Config[:path_prefix]
0
         uri
0
       end
0
+
0
+ # Identifies the object according to the identifiers set while building
0
+ # the routes. Identifying an object means picking an instance method to
0
+ # call on the object that will return a string representation of the
0
+ # object for the route being generated. If the identifier is an array,
0
+ # then a param_key must be present and match one of the elements of the
0
+ # identifier array.
0
+ #
0
+ # param_keys that end in _id are treated slightly differently in order
0
+ # to get nested resources to work correctly.
0
+ def identify(obj, param_key = nil)
0
+ identifier = identifier_for(obj)
0
+ if identifier.is_a?(Array)
0
+ # First check if the param_key exists as an identifier
0
+ return obj.send(param_key) if identifier.include?(param_key)
0
+ # If the param_key ends in _id, just return the object id
0
+ return obj.id if "#{param_key}" =~ /_id$/
0
+ # Otherwise, raise an error
0
+ raise GenerationError, "The object #{obj.inspect} cannot be identified with #{identifier.inspect} for #{param_key}"
0
+ else
0
+ identifier ? obj.send(identifier) : obj
0
+ end
0
+ end
0
+
0
+ # Returns the identifier for the passed object. Built in core ruby classes are
0
+ # always identified with to_s. The method will return nil in that case (since
0
+ # to_s is the default for objects that do not have identifiers.)
0
+ def identifier_for(obj)
0
+ return if obj.is_a?(String) || obj.is_a?(Symbol) || obj.is_a?(Numeric) ||
0
+ obj.is_a?(TrueClass) || obj.is_a?(FalseClass) || obj.is_a?(NilClass) ||
0
+ obj.is_a?(Array) || obj.is_a?(Hash)
0
+
0
+ @identifiers.each do |klass, identifier|
0
+ return identifier if obj.is_a?(klass)
0
+ end
0
+
0
+ return nil
0
+ end
0
 
0
+ # Returns the if statement and return value for for the main
0
+ # Router.match compiled method.
0
       def compiled_statement(first)
0
         els_if = first ? ' if ' : ' elsif '
0
 
0
@@ -258,7 +325,7 @@ module Merb
0
           segments.each_with_index do |segment, i|
0
             bits << case
0
               when segment.is_a?(String) then segment
0
- when segment.is_a?(Symbol) then '#{param_for_route(cached_' + segment.to_s + ')}'
0
+ when segment.is_a?(Symbol) then '#{cached_' + segment.to_s + '}'
0
               when segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) } then "\#{#{@opt_segment_stack.last.shift}}"
0
               else ""
0
             end
0
@@ -267,16 +334,6 @@ module Merb
0
           bits
0
         end
0
         
0
- def param_for_route(param)
0
- case param
0
- when String, Symbol, Numeric, TrueClass, FalseClass, NilClass
0
- param
0
- else
0
- _, identifier = @identifiers.find { |klass, _| param.is_a?(klass) }
0
- identifier ? param.send(identifier) : param
0
- end
0
- end
0
-
0
       end
0
 
0
     # === Conditions ===
...
1
2
 
 
3
4
5
...
19
20
21
22
23
24
 
 
 
 
 
 
25
26
27
...
31
32
33
34
35
36
37
38
39
40
41
42
 
 
 
 
 
 
 
 
43
 
 
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
 
 
 
 
 
 
 
 
74
75
76
77
78
79
80
81
82
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
85
 
 
86
87
88
89
90
91
92
93
94
95
96
 
 
 
 
97
98
 
 
99
100
101
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
104
 
105
106
107
108
...
1
2
3
4
5
6
7
...
21
22
23
 
 
 
24
25
26
27
28
29
30
31
32
...
36
37
38
 
 
 
39
 
 
 
 
 
40
41
42
43
44
45
46
47
48
49
50
51
 
 
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
54
55
56
57
58
59
60
61
62
 
 
 
 
 
 
 
 
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
 
 
 
 
 
 
 
 
 
104
105
106
107
108
109
110
111
112
113
 
 
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
 
161
162
 
163
164
0
@@ -1,5 +1,7 @@
0
 require File.join(File.dirname(__FILE__), '..', "spec_helper")
0
 
0
+# Namespacing everything so that all the spec classes aren't shared
0
+# across other specs
0
 module ID
0
   class ORM ; end
0
 
0
@@ -19,9 +21,12 @@ module ID
0
     def url ; "awesome" ; end
0
   end
0
 
0
- class User < ORM
0
- def to_s ; "user" ; end
0
- def name ; "carl" ; end
0
+ class User < ORM
0
+ def id ; 10 ; end
0
+ def to_s ; "user" ; end
0
+ def name ; "carl" ; end
0
+ def first_name ; "john" ; end
0
+ def last_name ; "doe" ; end
0
   end
0
 
0
   class Something
0
@@ -31,77 +36,128 @@ module ID
0
   class WithInclusions
0
     include Resource
0
   end
0
-end
0
-
0
-describe "When generating URLs," do
0
   
0
- before(:each) do
0
- Merb::Router.prepare do
0
- identify ID::Account => :url, ID::User => :name, ID::ORM => :id, ID::Resource => :identifier do
0
- match("/:account") do
0
- resources :users
0
+ describe "When generating URLs," do
0
+
0
+ before(:each) do
0
+ Merb::Router.prepare do
0
+ identify Account => :url, User => :name, ORM => :id, Resource => :identifier do
0
+ match("/:account") do
0
+ resources :users
0
+ end
0
         end
0
+
0
+ match("/resources/:id").name(:resource)
0
       end
0
-
0
- match("/resources/:id").name(:resource)
0
     end
0
- end
0
-
0
- describe "a route with custom identifiers" do
0
-
0
- it "should use #to_s if no other identifier is set" do
0
- url(:resource, :id => ID::Article.new).should == "/resources/article"
0
- url(:resource, :id => ID::Account.new).should == "/resources/account"
0
- url(:resource, :id => ID::User.new).should == "/resources/user"
0
- url(:resource, :id => ID::Something.new).should == "/resources/hello"
0
- end
0
-
0
- it "should use the identifier for the object" do
0
- url(:user, :account => ID::Account.new, :id => ID::User.new).should == "/awesome/users/carl"
0
- end
0
-
0
- it "should be able to use identifiers for parent classes" do
0
- url(:user, :account => ID::Article.new, :id => 1).should == "/10/users/1"
0
- end
0
-
0
- it "should be able to use identifiers for included modules" do
0
- url(:user, :account =>ID:: WithInclusions.new, :id => '1').should == "/included/users/1"
0
- end
0
-
0
- it "should not require a block" do
0
- Merb::Router.prepare do
0
- identify(ID::Account => :url).match("/:account").name(:account)
0
+
0
+ describe "a route with custom identifiers" do
0
+
0
+ it "should use #to_s if no other identifier is set" do
0
+ url(:resource, :id => Article.new).should == "/resources/article"
0
+ url(:resource, :id => Account.new).should == "/resources/account"
0
+ url(:resource, :id => User.new).should == "/resources/user"
0
+ url(:resource, :id => Something.new).should == "/resources/hello"
0
       end
0
       
0
- url(:account, :account => ID::Account.new).should == "/awesome"
0
- end
0
-
0
- it "should combine identifiers when nesting" do
0
- Merb::Router.prepare do
0
- identify ID::Account => :url do
0
- identify ID::User => :name do
0
- match("/:account").resources :users
0
+ it "should use #to_s if no other identifier is set when params are anonymous" do
0
+ url(:resource, Article.new).should == "/resources/article"
0
+ url(:resource, Account.new).should == "/resources/account"
0
+ url(:resource, User.new).should == "/resources/user"
0
+ url(:resource, Something.new).should == "/resources/hello"
0
+ end
0
+
0
+ it "should use the identifier for the object" do
0
+ url(:user, :account => Account.new, :id => User.new).should == "/awesome/users/carl"
0
+ end
0
+
0
+ it "should use the identifier for the object when the params are anonymous" do
0
+ url(:user, Account.new, User.new).should == "/awesome/users/carl"
0
+ end
0
+
0
+ it "should be able to use identifiers for parent classes" do
0
+ url(:user, :account => Article.new, :id => 1).should == "/10/users/1"
0
+ end
0
+
0
+ it "should be able to use identifiers for parent classes when the params are anonymous" do
0
+ url(:user, Article.new, 1).should == "/10/users/1"
0
+ end
0
+
0
+ it "should be able to use identifiers for included modules" do
0
+ url(:user, :account => WithInclusions.new, :id => '1').should == "/included/users/1"
0
+ end
0
+
0
+ it "should be able to use identifiers for included modules when the params are anonymous" do
0
+ url(:user, WithInclusions.new, '1').should == "/included/users/1"
0
+ end
0
+
0
+ it "should be able to specify an array of identifiers" do
0
+ Merb::Router.prepare do
0
+ identify(User => [:last_name, :first_name]) do
0
+ match("/users/:last_name/:first_name").name(:users)
0
           end
0
         end
0
+
0
+ url(:users, :last_name => User.new, :first_name => User.new).should == "/users/doe/john"
0
       end
0
       
0
- url(:user, :account => ID::Account.new, :id => ID::User.new).should == "/awesome/users/carl"
0
- end
0
-
0
- it "should retain previously set conditions" do
0
- Merb::Router.prepare do
0
- match("/:account") do
0
- register.name(:account)
0
- identify ID::Account => :url do
0
- resources :users
0
+ it "should be able to specify an array of identifiers when the params are anonymous" do
0
+ Merb::Router.prepare do
0
+ identify(User => [:last_name, :first_name]) do
0
+ match("/users/:last_name/:first_name").name(:users)
0
           end
0
         end
0
+
0
+ url(:users, User.new).should == "/users/doe/john"
0
       end
0
       
0
- url(:account, :account => ID::Account.new).should == "/account"
0
- url(:user, :account => ID::Account.new, :id => ID::User.new).should == "/awesome/users/user"
0
+ it "should be able to treat :id correctly with Array identifiers" do
0
+ Merb::Router.prepare do
0
+ identify(User => [:name, :id]) do
0
+ resources :users, :keys => [:name, :id] do
0
+ resources :comments
0
+ end
0
+ end
0
+ end
0
+
0
+ url(:user_comments, :name => User.new, :user_id => User.new).should == "/users/carl/10/comments"
0
+ end
0
+
0
+ it "should not require a block" do
0
+ Merb::Router.prepare do
0
+ identify(Account => :url).match("/:account").name(:account)
0
+ end
0
+
0
+ url(:account, :account => Account.new).should == "/awesome"
0
+ end
0
+
0
+ it "should combine identifiers when nesting" do
0
+ Merb::Router.prepare do
0
+ identify Account => :url do
0
+ identify User => :name do
0
+ match("/:account").resources :users
0
+ end
0
+ end
0
+ end
0
+
0
+ url(:user, :account => Account.new, :id => User.new).should == "/awesome/users/carl"
0
+ end
0
+
0
+ it "should retain previously set conditions" do
0
+ Merb::Router.prepare do
0
+ match("/:account") do
0
+ register.name(:account)
0
+ identify Account => :url do
0
+ resources :users
0
+ end
0
+ end
0
+ end
0
+
0
+ url(:account, :account => Account.new).should == "/account"
0
+ url(:user, :account => Account.new, :id => User.new).should == "/awesome/users/user"
0
+ end
0
+
0
     end
0
-
0
+
0
   end
0
-
0
 end
0
\ No newline at end of file

Comments

    No one has commented yet.