mholling-subdomain_routes 0.1.0 → 0.2.0

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/README.textile CHANGED
@@ -1,12 +1,10 @@
1
- h1. SubdomainRoutes - Adding Subdomains to Rails Routing
2
-
3
1
  The Rails "routing system":http://api.rubyonrails.org/classes/ActionController/Routing.html is a pretty impressive piece of code. There's a fair bit of magic going on to make your routes so easy to define and use in the rest of your Rails application. One area in which the routing system is limited however is its use of subdomains: it's pretty much assumed that your site will be using a single, fixed domain.
4
2
 
5
3
  There are times where it is preferable to spread a website over multiple subdomains. One common idiom in URL schemes is to separate aspects of the site under different subdomains, representative of those aspect. It many cases a simple, fixed subdomain scheme is desirable: _support.whatever.com_, _forums.whatever.com_, _gallery.whatever.com_ and so on. On some international sites, the subdomain is used to select the language and localization: _en.wikipedia.org_, _fr.wikipedia.org_, _ja.wikipedia.org_. Other schemes allocate each user of the site their own subdomain, so as to personalise the experience (_blogspot.com_ is a good example of this).
6
4
 
7
5
  A couple of plugins currently exists for Rails developers wishing to incorporate subdomains into their routes. The de facto standard is "SubdomainFu":http://www.intridea.com/2008/6/23/subdomainfu-a-new-way-to-tame-the-subdomain. (I'll admit - I haven't actually used this plugin myself.) There's also "SubdomainAccount":http://github.com/shuber/subdomain_account/tree/master.
8
6
 
9
- I've recently completed work on a subdomain library which fully incorporates subdomains into the rails routing environment - in URL generation, route recognition *and* in route definition, something I don't believe is currently available. As an added bonus, if offers the ability to define routes for _dynamic subdomains_, which are recognised based on the contents of your database, or on other dynamic conditions.
7
+ I've recently completed work on a subdomain library which fully incorporates subdomains into the rails routing environment - in URL generation, route recognition *and* in route definition, something I don't believe is currently available. As an added bonus, if offers the ability to define subdomain routes which are keyed to a model (user, category, etc.) stored in your database.
10
8
 
11
9
  h2. Installation
12
10
 
@@ -76,7 +74,7 @@ support_tickets_path(:subdomain => :www)
76
74
 
77
75
  Notice that, by necessity, requesting a path still results in an URL if the subdomain of the route is different. If you try and override the subdomain manually, you'll get an error, because the resulting URL would be invalid and would not be recognized. This is a good thing - you don't want to be linking to invalid URLs by mistake!
78
76
 
79
- In other words, <code>url_for</code> and your named routes will *never* generate an invalid URL. This is one major benefit of the SubdomainRoutes gem: it offers a smart way of switching subdomains, requiring them to be specified manually only when absolutely necessary. (It gets even better with dynamic subdomains, which I will describe later.)
77
+ In other words, <code>url_for</code> and your named routes will *never* generate an invalid URL. This is one major benefit of the SubdomainRoutes gem: it offers a smart way of switching subdomains, requiring them to be specified manually only when absolutely necessary.
80
78
 
81
79
  h2. Mapping Multiple Subdomains
82
80
 
@@ -163,125 +161,74 @@ end
163
161
 
164
162
  Your <code>admin_role_users_path(@role)</code> will automatically generate with the correct _admin_ subdomain if required, and paths such as _/roles/1/users_ will only be recognised when under the _admin_ subdomain. Note that both the block form and the <code>:has_many</code> form of nested resources will work in this manner. (In fact, under the hood, the latter just falls through to the former.) Any other (non-resource) routes you nest under a resource will also inherit the subdomain conditions.
165
163
 
166
- h2. Setting Up Your Development Environment
167
-
168
- To develop your app using SudomainRoutes, you'll need to set up your machine to point some test domains to the server on your machine (i.e. to the local loopback address, 127.0.0.1). On a Mac, you can do this by editing <code>/etc/hosts</code>. Let's say you want to use the subdomains _www_, _dvd_, _game_, _book_ and _cd_, with a domain of _reviews.local_. Adding these lines to <code>/etc/hosts</code> will do the trick:
169
-
170
- <pre>
171
- 127.0.0.1 reviews.local
172
- 127.0.0.1 www.reviews.local
173
- 127.0.0.1 dvd.reviews.local
174
- 127.0.0.1 game.reviews.local
175
- 127.0.0.1 book.reviews.local
176
- 127.0.0.1 cd.reviews.local
177
- </pre>
178
-
179
- You'll need to flush your DNS cache for these changes to take effect:
180
-
181
- <pre>
182
- dscacheutil -flushcache
183
- </pre>
184
-
185
- Then fire up your <code>script/server</code>, point your browser to _www.reviews.local:3000_ and your app should be up and running. If you're using "Passenger":http://www.modrails.com to "serve your apps in development":http://www.google.com/search?q=rails+passenger+development (and I highly recommend that you do), you'll need to add a Virtual Host to your Apache .conf file. (Don't forget to alias all the subdomains and restart the server.)
186
-
187
- If you're using dynamic subdomain routes (covered in my next article), you may want to use a catch-all (wildcard) subdomain. Setting this up is not so easy, since wildcards (like _*.reviews.local_) won't work in your <code>/etc/hosts</code> file. There are a couple of work-around for this:
188
-
189
- # Use a "proxy.pac":http://en.wikipedia.org/wiki/Proxy.pac file in your browser so that it proxies _*.reviews.local_ to localhost. How to do this will depend on the browser you're using.
190
- # Set up a local DNS server with an A record for the domain. This may be a bit involved.
191
-
192
- h2. Dynamic Subdomain Recognition
193
-
194
- SubdomainRoutes also offers a full complement of features for recognizing subdomain URLS which are not known in advance. (I'll refer to these cases as _dynamic subdomains_.) For example, if you are keying each user of your website to their own subdomain, your routes will need to recognise new subdomains as new users sign up. Another example is if you want to key your URL subdomains to a set of categories (language, city, product type, etc.) which may expand as your site grows. In each case, your subdomains will depend on records in the database.
164
+ h2. Defining Model-Based Subdomain Routes
195
165
 
196
- Let's take a hypothetical example of a blogging site which lets users show and manage their blog under their own subdomains (think _blogspot.com_). Assume our <code>User</code> model has a <code>subdomain</code> attribute which contains the user's custom subdomain. In our routes we'll still use the <code>subdomain</code> mapper, but instead of specifying one or more subdomains, we just specify a <code>:proc</code> option:
166
+ The idea here is to have the subdomain of the URL keyed to an ActiveRecord model. Let's take a hypothetical example of a site which lists items under different categories, each category being represented under its own subdomain. Assume our <code>Category</code> model has a <code>subdomain</code> attribute which contains the category's custom subdomain. In our routes we'll still use the <code>subdomain</code> mapper, but instead of specifying one or more subdomains, we just specify a <code>:model</code> option:
197
167
 
198
168
  <pre>
199
169
  ActionController::Routing::Routes.draw do |map|
200
- map.subdomain :proc => :blog do |blog|
201
- blog.resources :articles, :has_many => :comments
170
+ map.subdomain :model => :category do |category|
171
+ category.resources :items
202
172
  ...
203
173
  end
204
174
  end
205
-
206
- ActionController::Routing::Routes.recognize_subdomain :blog do |subdomain|
207
- User.exists?(:username => subdomain)
208
- end
209
175
  </pre>
210
176
 
211
- Note the last few lines, which are crucial. Here we are defining a method for recognizing blog subdomains. This is done using a new method, <code>recognize_subdomain</code>, which the gem adds to <code>ActionController::Routing::Routes</code>.
212
-
213
- As arguments, the <code>recognize_subdomain</code> method takes a subdomain label, as used in the route (in this case, <code>:blog</code>), and a block. The block should take a single argument (the subdomain to be recognised), and should return true or false depending on whether the subdomain should be recognised for that route. In this case, we simply look up the subdomain in the <code>User</code> model to see if it's in use.
177
+ As before, the namespace and name prefix for all the nested routes will default to the name of the model (you can override these in the options). The routes will match under any subdomain, and that subdomain will be passed to the controller in the <code>params</code> hash as <code>params[:category_id]</code>. For example, a GET request to _dvds.example.com/items_ will go to the <code>Category::ItemsController#index</code> action with <code>params[:category_id]</code> set to <code>"dvds"</code>.
214
178
 
215
- That's all there is to it! Now our blog routes will recognize more subdomains as more users sign up.
179
+ h2. Generating Model-Based Subdomain URLs
216
180
 
217
- How does URL _generation_ work with these routes? Pretty much as you'd expect. Let's say our awesome new blogging site, _awesomeblogs.com_, has only two users, who've selected subdomains _mholling_ and _rails_. In controllers and views, our named routes will work as follows:
181
+ So how does URL _generation_ work with these routes? That's the best bit: just the same way as you're used to! The routes are fully integrated with your named routes, as well as the <code>form_for</code>, <code>redirect_to</code> and <code>polymorphic_path</code> helpers. The only thing you have to do is make sure your model's <code>to_param</code> returns the subdomain field for the user:
218
182
 
219
183
  <pre>
220
- # when the current host is mholling.awesomeblogs.com:
221
- blog_articles_path
222
- => "/articles"
223
-
224
- blog_articles_path(:subdomain => "rails")
225
- => "http://rails.awesomblogs.com/articles"
226
-
227
- blog_articles_path(:subdomain => "www")
228
- # ActionController::RoutingError: Route failed to generate
229
- # (subdomain "www" is invalid)
230
-
231
- # when the host is www.awesomeblogs.com:
232
- blog_articles_path(:subdomain => "rails")
233
- => "http://rails.awesomblogs.com/articles"
234
-
235
- blog_articles_path
236
- # ActionController::RoutingError: Route failed to generate
237
- # (subdomain "www" is invalid)
184
+ class Category < ActiveRecord::Base
185
+ ...
186
+ alias_method :to_param, :subdomain
187
+ ...
188
+ end
238
189
  </pre>
239
190
 
240
- As you can see, the subdomain recognition method that we defined is being used to check for an invalid subdomain when the URL is being written. Nice!
191
+ Now, in the above example, let's say our site has _dvd_ and _cd_ su categories, with subdomains _dvds_ and _cds_. In our controller:
241
192
 
242
- h3. Caching of Dynamic Subdomain Recognition
193
+ <pre>
194
+ @dvd
195
+ => #<Category id: 1, subdomain: "dvds", ... >
243
196
 
244
- Internally, the results of the subdomain recognition block are cached. If this weren't done, the block could be run many time per request (once for each route until one is recognised). That would be bad, since in most cases the database will be hit each time the block is called. (Sure, Rails will probably cache the database queries, but you may as well cache as high up the chain as you can.)
197
+ @cd
198
+ => #<Category id: 2, subdomain: "cds", ... >
245
199
 
246
- The problem then becomes - when does the cache get flushed? By default, this will occur once per request, which should be fine for many applications. If that's the case, you don't need to do anything.
200
+ # when the current host is dvds.example.com:
201
+ category_items_path(@dvd)
202
+ => "/items"
247
203
 
248
- To optimise performance however, you can disable the per-request cache flush and do it manually. Set this option in your configuration:
204
+ polymorphic_path [ @dvd, @dvd.items.first ]
205
+ => "/items/2"
249
206
 
250
- <pre>
251
- SubdomainRoutes::Config.manual_flush = true
252
- </pre>
253
-
254
- In the above example we might want to choose this route, so that we can flush the cache only when a user is created or destroyed (or changes the subdomain). We could do this easily using an observer:
207
+ category_items_path(@cd)
208
+ => "http://cds.example.com/items"
255
209
 
256
- <pre>
257
- class UserObserver < ActiveRecord::Observer
258
- observe :user
210
+ polymorphic_path [ @cd, @cd.items.first ]
211
+ => "http://cds.example.com/items/10"
212
+ </pre>
259
213
 
260
- after_save(user)
261
- if user.subdomain_changed?
262
- ActionController::Routing::Routes.subdomain_procs.flush!
263
- end
264
- end
214
+ As you can see, the first argument for the named routes (and polymorphic paths) feeds directly into the subdomain for the URL. No more passing <code>:subdomain</code> options. Nice!
265
215
 
266
- after_destroy(user)
267
- ActionController::Routing::Routes.subdomain_procs.flush!
268
- end
269
- end
216
+ h2. ActiveRecord Validations
270
217
 
271
- # and in config/environment.rb:
272
- config.active_record.observers = [ :user_observer ]
273
- </pre>
218
+ SubdomainRoutes also gives you a couple of utility validations for your ActiveRecord models:
274
219
 
275
- Another way to reduce the number of calls to the subdomain recognition block is by defining the dynamic subdomain route as late in <code>routes.rb</code> as possible. That way, the block will only be called if none of the other routes have resolved.
220
+ * <code>validates_subdomain_format_of</code> ensures a subdomain field uses only legal characters in an allowed format; and
221
+ * <code>validates_subdomain_not_reserved</code> ensures the field does not take a value already in use by your fixed-subdomain routes.
276
222
 
277
- h3. ActiveRecord Validations
223
+ (Undoubtedly, you'll want to throw in a <code>validates_uniqueness_of</code> as well.)
278
224
 
279
- SubdomainRoutes also gives you a utility validation, <code>validates_subdomain_format_of</code>, for your ActiveRecord models. If you're storing a subdomain in a model (as we have been with our <code>User</code> example), you can use this validation to make sure that a subdomain field uses only legal characters and is in an allowed format. (Undoubtedly, you'll want to throw in a <code>validates_uniqueness_of</code> as well.)
225
+ Let's take an example of a site where each user gets a dedicated subdomain. Validations for the <code>subdomain</code> attribute of the <code>User</code> model would be:
280
226
 
281
227
  <pre>
282
228
  class User < ActiveRecord::Base
283
229
  ...
284
230
  validates_subdomain_format_of :subdomain
231
+ validates_subdomain_not_reserved :subdomain
285
232
  validates_uniqueness_of :subdomain
286
233
  ...
287
234
  end
@@ -289,35 +236,53 @@ end
289
236
 
290
237
  The library currently uses a simple regexp to limit subdomains to lowercase alphanumeric characters and dashes (except on either end). If you want to conform more precisely to the URI specs, you can override the <code>SubdomainRoutes.valid_subdomain?</code> method and implement your own.
291
238
 
292
- h2. Using Static and Dynamic Subdomain Routes Together
239
+ h2. Using Fixed and Model-Based Subdomain Routes Together
293
240
 
294
- Let's try using static and dynamic subdomain routes together. Say we want to reserve some subdomains (say _support_ and _admin_) for administrative functions, with the remainder keyed to user accounts. Our routes:
241
+ Let's try using fixed and model-based subdomain routes together. Say we want to reserve some subdomains (say _support_ and _admin_) for administrative functions, with the remainder keyed to user accounts. Our routes:
295
242
 
296
243
  <pre>
297
244
  ActionController::Routing::Routes.draw do |map|
298
245
  map.subdomain :support do |support|
299
246
  ...
300
247
  end
248
+
301
249
  map.subdomain :admin do |admin|
302
250
  ...
303
251
  end
304
- map.subdomain :proc => :user do |user|
252
+
253
+ map.subdomain :model => :user do |user|
305
254
  ...
306
255
  end
307
256
  end
257
+ </pre>
308
258
 
309
- ActionController::Routing::Routes.recognize_subdomain :blog do |subdomain|
310
- User.exists?(:username => subdomain)
311
- end
259
+ These routes will co-exist quite happily together. We've made sure our static subdomain routes are listed first though, so that they get matched first. In the <code>User</code> model we'd add the validations above, which in this case would prevent users from taking _www_ or _support_ as a subdomain. (We could also validate for a minimum and maximum length using <code>validates_length_of</code>.)
260
+
261
+ h2. Setting Up Your Development Environment
262
+
263
+ To develop your app using SudomainRoutes, you'll need to set up your machine to point some test domains to the server on your machine (i.e. to the local loopback address, 127.0.0.1). On a Mac, you can do this by editing <code>/etc/hosts</code>. Let's say you want to use the subdomains _www_, _dvd_, _game_, _book_ and _cd_, with a domain of _reviews.local_. Adding these lines to <code>/etc/hosts</code> will do the trick:
264
+
265
+ <pre>
266
+ 127.0.0.1 reviews.local
267
+ 127.0.0.1 www.reviews.local
268
+ 127.0.0.1 dvd.reviews.local
269
+ 127.0.0.1 game.reviews.local
270
+ 127.0.0.1 book.reviews.local
271
+ 127.0.0.1 cd.reviews.local
312
272
  </pre>
313
273
 
314
- These routes will co-exist quite happily together. (We've made sure our static subdomain routes are listed first though.) In the User model we'll add an extra validation to prevent users from taking the reserved subdomains:
274
+ You'll need to flush your DNS cache for these changes to take effect:
315
275
 
316
276
  <pre>
317
- validates_exclusion_of :subdomain, :in => %w{support admin www}
277
+ dscacheutil -flushcache
318
278
  </pre>
319
279
 
320
- (We could also validate for a minimum and maximum length.)
280
+ Then fire up your <code>script/server</code>, point your browser to _www.reviews.local:3000_ and your app should be up and running. If you're using "Passenger":http://www.modrails.com to "serve your apps in development":http://www.google.com/search?q=rails+passenger+development (and I highly recommend that you do), you'll need to add a Virtual Host to your Apache .conf file. (Don't forget to alias all the subdomains and restart the server.)
281
+
282
+ If you're using model-based subdomain routes (covered next), you may want to use a catch-all (wildcard) subdomain. Setting this up is not so easy, since wildcards (like _*.reviews.local_) won't work in your <code>/etc/hosts</code> file. There are a couple of work-around for this:
283
+
284
+ # Use a "proxy.pac":http://en.wikipedia.org/wiki/Proxy.pac file in your browser so that it proxies _*.reviews.local_ to localhost. How to do this will depend on the browser you're using.
285
+ # Set up a local DNS server with an A record for the domain. This may be a bit involved.
321
286
 
322
287
 
323
288
  Copyright (c) 2009 Matthew Hollingworth. See LICENSE for details.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :patch: 0
3
3
  :major: 0
4
- :minor: 1
4
+ :minor: 2
@@ -1,6 +1,5 @@
1
1
  module SubdomainRoutes
2
2
  module Config
3
3
  mattr_accessor :domain_length
4
- mattr_accessor :manual_flush
5
4
  end
6
5
  end
@@ -4,11 +4,13 @@ module SubdomainRoutes
4
4
  module Mapper
5
5
  def subdomain(*subdomains, &block)
6
6
  options = subdomains.extract_options!
7
- name = nil
8
7
  if subdomains.empty?
9
- if subdomain = options.delete(:proc)
10
- subdomain_options = { :subdomains => { :proc => subdomain } }
11
- name = subdomain
8
+ if model = options.delete(:model)
9
+ raise ArgumentError, "Invalid model name" if model.blank?
10
+ models = model.to_s.downcase.pluralize
11
+ model = models.singularize
12
+ model_id = model.foreign_key.to_sym
13
+ subdomain_options = { :subdomains => model_id, :name_prefix => "#{model}_", :namespace => "#{model}/" }
12
14
  else
13
15
  raise ArgumentError, "Please specify at least one subdomain!"
14
16
  end
@@ -22,12 +24,12 @@ module SubdomainRoutes
22
24
  if subdomains.include? ""
23
25
  raise ArgumentError, "Can't specify a nil subdomain unless you set Config.domain_length!" unless Config.domain_length
24
26
  end
25
- name = subdomains.reject(&:blank?).first
26
27
  subdomain_options = { :subdomains => subdomains }
28
+ name = subdomains.reject(&:blank?).first
29
+ name = options.delete(:name) if options.has_key?(:name)
30
+ name = name.to_s.downcase.gsub(/[^(a-z0-9)]/, ' ').squeeze(' ').strip.gsub(' ', '_') unless name.blank?
31
+ subdomain_options.merge! :name_prefix => "#{name}_", :namespace => "#{name}/" unless name.blank?
27
32
  end
28
- name = options.delete(:name) if options.has_key?(:name)
29
- name = name.to_s.downcase.gsub(/[^(a-z0-9)]/, ' ').squeeze(' ').strip.gsub(' ', '_') unless name.blank?
30
- subdomain_options.merge! :name_prefix => "#{name}_", :namespace => "#{name}/" unless name.blank?
31
33
  with_options(subdomain_options.merge(options), &block)
32
34
  end
33
35
  alias_method :subdomains, :subdomain
@@ -4,7 +4,6 @@ module SubdomainRoutes
4
4
 
5
5
  def subdomain
6
6
  subdomain_for_host(host)
7
- # TODO: catch HostNotSupplied and TooManySubdomains and deal with them somehow!
8
7
  end
9
8
  end
10
9
  end
@@ -4,46 +4,32 @@ module SubdomainRoutes
4
4
  include SplitHost
5
5
 
6
6
  def self.included(base)
7
- [ :extract_request_environment, :add_route, :clear!, :call ].each { |method| base.alias_method_chain method, :subdomains }
7
+ [ :extract_request_environment, :add_route ].each { |method| base.alias_method_chain method, :subdomains }
8
8
  end
9
9
 
10
10
  def extract_request_environment_with_subdomains(request)
11
11
  extract_request_environment_without_subdomains(request).merge(:subdomain => subdomain_for_host(request.host))
12
12
  end
13
-
13
+
14
14
  def add_route_with_subdomains(*args)
15
15
  options = args.extract_options!
16
16
  if subdomains = options.delete(:subdomains)
17
17
  options[:conditions] ||= {}
18
- options[:conditions][:subdomains] = subdomains
19
18
  options[:requirements] ||= {}
19
+ options[:conditions][:subdomains] = subdomains
20
20
  options[:requirements][:subdomains] = subdomains
21
21
  end
22
22
  with_options(options) { |routes| routes.add_route_without_subdomains(*args) }
23
23
  end
24
24
 
25
- def subdomain_procs
26
- @subdomain_procs ||= SubdomainRoutes::ProcSet.new
27
- end
28
-
29
- def recognize_subdomain(name, &block)
30
- subdomain_procs.add_recognizer(name, &block)
31
- end
32
-
33
- def clear_with_subdomains!
34
- subdomain_procs.clear!
35
- clear_without_subdomains!
36
- end
37
-
38
- def call_with_subdomains(*args)
39
- subdomain_procs.flush! unless SubdomainRoutes::Config.manual_flush
40
- call_without_subdomains(*args)
25
+ def reserved_subdomains
26
+ routes.map(&:reserved_subdomains).flatten.uniq
41
27
  end
42
28
  end
43
29
 
44
30
  module Route
45
31
  def self.included(base)
46
- base.alias_method_chain :recognition_conditions, :subdomains
32
+ [ :recognition_conditions, :segment_keys, :recognition_extraction ].each { |method| base.alias_method_chain method, :subdomains }
47
33
  end
48
34
 
49
35
  def recognition_conditions_with_subdomains
@@ -51,13 +37,27 @@ module SubdomainRoutes
51
37
  case conditions[:subdomains]
52
38
  when Array
53
39
  result << "conditions[:subdomains].include?(env[:subdomain])"
54
- when Hash
55
- if subdomain = conditions[:subdomains][:proc]
56
- result << %Q{ActionController::Routing::Routes.subdomain_procs.recognize(#{subdomain.inspect}, env[:subdomain])}
57
- end
40
+ when Symbol
41
+ result << "(subdomain = env[:subdomain] unless env[:subdomain].blank?)"
58
42
  end
59
43
  result
60
44
  end
45
+
46
+ def segment_keys_with_subdomains
47
+ result = segment_keys_without_subdomains
48
+ result.unshift(:subdomain) if conditions[:subdomains].is_a? Symbol
49
+ result
50
+ end
51
+
52
+ def recognition_extraction_with_subdomains
53
+ result = recognition_extraction_without_subdomains
54
+ result.unshift "\nparams[#{conditions[:subdomains].inspect}] = subdomain\n" if conditions[:subdomains].is_a? Symbol
55
+ result
56
+ end
57
+
58
+ def reserved_subdomains
59
+ conditions[:subdomains].is_a?(Array) ? conditions[:subdomains] : []
60
+ end
61
61
  end
62
62
  end
63
63
  end
@@ -1,8 +1,5 @@
1
1
  module SubdomainRoutes
2
2
  class TooManySubdomains < StandardError
3
- # TODO: should this just be an ActionController::RoutingError instead? Any benefit to having a separate error type?
4
- # OK, keep the special codes, but catch and re-case them in UrlWriter.
5
- # (The errors are also raised in extract_request_environment...)
6
3
  end
7
4
 
8
5
  module SplitHost
@@ -1,6 +1,5 @@
1
1
  module SubdomainRoutes
2
2
  class HostNotSupplied < StandardError
3
- # TODO: should this just be an ActionController::RoutingError instead? Any benefit to having a separate error type?
4
3
  end
5
4
 
6
5
  module RewriteSubdomainOptions
@@ -13,7 +12,7 @@ module SubdomainRoutes
13
12
  def rewrite_subdomain_options(options, host)
14
13
  if subdomains = options[:subdomains]
15
14
  old_subdomain, domain = split_host(host)
16
- new_subdomain = options.has_key?(:subdomain) ? options[:subdomain].to_s.downcase : old_subdomain
15
+ new_subdomain = options.has_key?(:subdomain) ? options[:subdomain].to_param.to_s.downcase : old_subdomain
17
16
  begin
18
17
  case subdomains
19
18
  when Array
@@ -24,11 +23,11 @@ module SubdomainRoutes
24
23
  new_subdomain = subdomains.first
25
24
  end
26
25
  end
27
- when Hash
28
- unless subdomain_procs.recognize(subdomains[:proc], new_subdomain) && (new_subdomain.blank? || SubdomainRoutes.valid_subdomain?(new_subdomain))
26
+ when Symbol
27
+ unless new_subdomain.blank? || SubdomainRoutes.valid_subdomain?(new_subdomain)
29
28
  raise ActionController::RoutingError, "subdomain #{new_subdomain.inspect} is invalid"
30
- end
31
- end
29
+ end
30
+ end
32
31
  rescue ActionController::RoutingError => e
33
32
  raise ActionController::RoutingError, "Route for #{options.inspect} failed to generate (#{e.message})"
34
33
  end
@@ -1,12 +1,15 @@
1
1
  module SubdomainRoutes
2
2
  def self.valid_subdomain?(subdomain)
3
3
  subdomain.to_s =~ /^([a-z]|[a-z][a-z0-9]|[a-z]([a-z0-9]|\-[a-z0-9])*)$/
4
- # # TODO: could we use URI::parse instead?:
4
+ end
5
+
6
+ # # Alternatively, we use URI::parse instead. This gives more lenient subdomains however:
7
+ # def self.valid_subdomain?(subdomain)
5
8
  # URI.parse "http://#{subdomain}.example.com"
6
9
  # rescue URI::InvalidURIError
7
10
  # false
8
- end
9
-
11
+ # end
12
+
10
13
  module Validations
11
14
  module ClassMethods
12
15
  def validates_subdomain_format_of(*attr_names)
@@ -19,6 +22,17 @@ module SubdomainRoutes
19
22
  end
20
23
  end
21
24
  end
25
+
26
+ def validates_subdomain_not_reserved(*attr_names)
27
+ configuration = { :on => :save }
28
+ configuration.update(attr_names.extract_options!)
29
+
30
+ validates_each(attr_names, configuration) do |record, attr_name, value|
31
+ if ActionController::Routing::Routes.reserved_subdomains.include? value
32
+ record.errors.add(attr_name, :is_a_reserved_subdomain, :default => configuration[:message], :value => value)
33
+ end
34
+ end
35
+ end
22
36
  end
23
37
  end
24
38
  end
@@ -5,10 +5,8 @@ require 'action_controller'
5
5
  require 'subdomain_routes/config'
6
6
  require 'subdomain_routes/split_host'
7
7
  require 'subdomain_routes/mapper'
8
- require 'subdomain_routes/proc_set'
9
8
  require 'subdomain_routes/routes'
10
9
  require 'subdomain_routes/resources'
11
10
  require 'subdomain_routes/url_writer'
12
11
  require 'subdomain_routes/request'
13
12
  require 'subdomain_routes/validations'
14
- require 'subdomain_routes/mailer'
@@ -57,33 +57,24 @@ describe "subdomain route recognition" do
57
57
  end
58
58
  end
59
59
 
60
- context "for a :proc subdomain" do
60
+ context "for a :model subdomain" do
61
61
  before(:each) do
62
- @user_block = lambda { |user| } # this block will be stubbed
63
- ActionController::Routing::Routes.recognize_subdomain(:user, &@user_block)
64
- map_subdomain(:proc => :user) { |user| user.resources :articles }
65
- @request.request_uri = "/articles"
66
- @request.host = "mholling.example.com"
62
+ map_subdomain(:model => :user) { |user| user.resources :articles }
67
63
  end
68
-
69
- it "should match the route if the recognize proc returns true or an object" do
70
- [ true, Object.new ].each do |value|
71
- ActionController::Routing::Routes.subdomain_procs.should_receive(:recognize).any_number_of_times.with(:user, "mholling").and_return(value)
64
+
65
+ context "when a nested route is requested" do
66
+ before(:each) do
67
+ @request.host = "mholling.example.com"
68
+ @request.request_uri = "/articles"
69
+ end
70
+
71
+ it "should match the route if there is a subdomain" do
72
72
  lambda { recognize_path(@request) }.should_not raise_error
73
73
  end
74
- end
75
74
 
76
- it "should not match the route if the recognize proc returns false or nil" do
77
- [ false, nil ].each do |value|
78
- ActionController::Routing::Routes.subdomain_procs.should_receive(:recognize).any_number_of_times.with(:user, "mholling").and_return(value)
79
- lambda { recognize_path(@request) }.should raise_error(ActionController::RoutingError)
75
+ it "should put the subdomain into the params as :model_id" do
76
+ recognize_path(@request)[:user_id].should == "mholling"
80
77
  end
81
78
  end
82
-
83
- it "should raise any error that the recognize proc raises" do
84
- error = StandardError.new
85
- ActionController::Routing::Routes.subdomain_procs.should_receive(:recognize).any_number_of_times.with(:user, "mholling").and_raise(error)
86
- lambda { recognize_path(@request) }.should raise_error { |e| e.should == error }
87
- end
88
79
  end
89
80
  end
@@ -29,7 +29,7 @@ describe "resource" do
29
29
  end
30
30
  end
31
31
 
32
- it "should include the subdomains in the routing conditions" do
32
+ it "should include the subdomains in the routing requirements" do
33
33
  ActionController::Routing::Routes.routes.each do |route|
34
34
  route.requirements[:subdomains].should == [ "admin" ]
35
35
  end
data/spec/routes_spec.rb CHANGED
@@ -45,9 +45,9 @@ describe "subdomain routes" do
45
45
  it "should downcase the subdomains" do
46
46
  map_subdomain(:Admin, "SUPPORT") { |map| map.options[:subdomains].should == [ "admin", "support" ] }
47
47
  end
48
-
49
- it "should accept a :proc option as the subdomain" do
50
- map_subdomain(:proc => :name) { |name| name.options[:subdomains].should == { :proc => :name } }
48
+
49
+ it "should raise ArgumentError if a nil :model option is specified as the subdomain" do
50
+ lambda { map_subdomain(:model => "") { } }.should raise_error(ArgumentError)
51
51
  end
52
52
 
53
53
  it "should raise ArgumentError if no subdomain is specified" do
@@ -132,117 +132,40 @@ describe "subdomain routes" do
132
132
  end
133
133
  end
134
134
  end
135
-
136
- context "for a single specified subdomain" do
137
- before(:each) do
138
- map_subdomain(:admin) do |map|
139
- map.resources :articles, :has_many => :comments
140
- map.foobar "foobar", :controller => "foo", :action => "bar"
141
- map.named_route "foobaz", "foobaz", :controller => "foo", :action => "baz"
142
- map.connect "/:controller/:action/:id"
143
- end
135
+
136
+ context "for a :model subdomain" do
137
+ it "should accept a :model option as the subdomain and turn it into a foreign key symbol" do
138
+ map_subdomain(:model => :city) { |city| city.options[:subdomains].should == :city_id }
144
139
  end
145
140
 
146
- it "should add the specified subdomain to the route recognition conditions" do
147
- ActionController::Routing::Routes.routes.each do |route|
148
- route.conditions[:subdomains].should == [ "admin" ]
149
- end
141
+ it "should singularize a plural model name" do
142
+ map_subdomain(:model => :cities) { |city| city.options[:subdomains].should == :city_id }
150
143
  end
151
-
152
- it "should add the subdomain to the route generation requirements" do
153
- ActionController::Routing::Routes.routes.each do |route|
154
- route.requirements[:subdomains].should == [ "admin" ]
155
- end
156
- end
157
- end
158
-
159
- context "for multiple specified subdomains" do
160
- before(:each) do
161
- map_subdomain(:support, :admin) do |map|
162
- map.resources :articles, :has_many => :comments
163
- map.foobar "foobar", :controller => "foo", :action => "bar"
164
- map.named_route "foobaz", "foobaz", :controller => "foo", :action => "baz"
165
- map.connect "/:controller/:action/:id"
166
- end
167
- end
168
-
169
- it "should add the specified subdomain to the route recognition conditions" do
170
- ActionController::Routing::Routes.routes.each do |route|
171
- route.conditions[:subdomains].should == [ "support", "admin" ]
172
- end
173
- end
174
-
175
- it "should not add a subdomain to the route generation requirements" do
176
- ActionController::Routing::Routes.routes.each do |route|
177
- route.requirements[:subdomains].should == [ "support", "admin" ]
178
- end
179
- end
180
- end
181
-
182
- context "for a :proc subdomain" do
183
- it "should set the value a namespace" do
184
- map_subdomain(:proc => :city) { |city| city.options[:namespace].should == "city/" }
144
+
145
+ it "should accept a string model name" do
146
+ map_subdomain(:model => "city") { |city| city.options[:subdomains].should == :city_id }
185
147
  end
186
-
187
- it "should prefix the value to named routes" do
188
- map_subdomain(:proc => :city) { |city| city.options[:name_prefix].should == "city_" }
148
+
149
+ it "should set the model name as a namespace" do
150
+ map_subdomain(:model => :city) { |city| city.options[:namespace].should == "city/" }
189
151
  end
190
152
 
191
- it "should set a namespace to the name if specified" do
192
- map_subdomain(:proc => :city, :name => :something) { |city| city.options[:namespace].should == "something/" }
193
- end
194
-
195
- it "should prefix the name to named routes if specified" do
196
- map_subdomain(:proc => :city, :name => :something) { |city| city.options[:name_prefix].should == "something_" }
197
- end
198
-
199
- it "should add the specified proc to the route recognition conditions" do
200
- map_subdomain(:proc => :city) { |city| city.resources :events }
201
- ActionController::Routing::Routes.routes.each do |route|
202
- route.conditions[:subdomains].should == { :proc => :city }
203
- end
204
- end
205
-
206
- it "should add the specified proc to the route generation requirements" do
207
- map_subdomain(:proc => :city) { |city| city.resources :events }
208
- ActionController::Routing::Routes.routes.each do |route|
209
- route.requirements[:subdomains].should == { :proc => :city }
210
- end
153
+ it "should prefix the model name to named routes" do
154
+ map_subdomain(:model => :city) { |city| city.options[:name_prefix].should == "city_" }
211
155
  end
212
156
  end
213
157
  end
214
158
 
215
159
  describe "ActionController::Routing::Routes" do
216
160
  before(:each) do
161
+ SubdomainRoutes::Config.stub!(:domain_length).and_return(2)
217
162
  ActionController::Routing::Routes.clear!
218
- end
219
-
220
- it "should allow a subdomain recognition method to be added" do
221
- ActionController::Routing::Routes.subdomain_procs.recognizes?(:city).should be_false
222
- ActionController::Routing::Routes.recognize_subdomain(:city) { |city| city == "perth" }
223
- ActionController::Routing::Routes.subdomain_procs.recognizes?(:city).should be_true
224
- end
225
-
226
- it "should allow subdomain recognition methods to be cleared" do
227
- ActionController::Routing::Routes.recognize_subdomain(:city) { |city| city == "perth" }
228
- ActionController::Routing::Routes.clear!
229
- ActionController::Routing::Routes.subdomain_procs.recognizes?(:city).should be_false
163
+ map_subdomain(:www, nil) { |www| www.root :controller => "homes", :action => "show" }
230
164
  end
231
165
 
232
- it "should flush the cache when called" do
233
- ActionController::Routing::Routes.subdomain_procs.should_receive(:flush!)
234
- begin
235
- ActionController::Routing::Routes.call(ActionController::TestRequest.new.env)
236
- rescue ActionController::RoutingError # no routes defined so the call will raise a routing error
237
- end
238
- end
239
-
240
- it "should not flush the cache when called if SubdomainRoutes::Config.manual_flush is set" do
241
- SubdomainRoutes::Config.stub!(:manual_flush).and_return(true)
242
- ActionController::Routing::Routes.subdomain_procs.should_not_receive(:flush!)
243
- begin
244
- ActionController::Routing::Routes.call(ActionController::TestRequest.new.env)
245
- rescue ActionController::RoutingError # no routes defined so the call will raise a routing error
246
- end
166
+ it "should know a list of its reserved subdomains" do
167
+ ActionController::Routing::Routes.reserved_subdomains.should == [ "www", "" ]
168
+ ActionController::Routing::Routes.clear!
169
+ ActionController::Routing::Routes.reserved_subdomains.should be_empty
247
170
  end
248
171
  end
@@ -123,54 +123,44 @@ describe "URL writing" do
123
123
  with_host("www.example.com") { users_url(:subdomain => mixedcase).should == "http://#{lowercase}.example.com/users" }
124
124
  end
125
125
  end
126
-
127
- context "when a :proc subdomain is specified" do
126
+
127
+ context "when a :model subdomain is specified" do
128
128
  before(:each) do
129
- map_subdomain(:proc => :city) { |city| city.resources :events }
129
+ map_subdomain(:model => :city) { |city| city.resources :events }
130
+ class City < ActiveRecord::Base; end
131
+ @boston = City.new
132
+ @boston.stub!(:new_record?).and_return(false)
133
+ @boston.stub!(:to_param).and_return("boston")
130
134
  end
131
-
132
- it "should raise a routing error without a recognize proc" do
135
+
136
+ it "should not change the host if the object has the same to_param as the current subdomain" do
133
137
  with_host "boston.example.com" do
134
- lambda { city_events_url }.should raise_error(ActionController::RoutingError)
135
- lambda { city_events_path }.should raise_error(ActionController::RoutingError)
138
+ city_events_url(@boston).should == "http://boston.example.com/events"
139
+ city_events_path(@boston).should == "/events"
136
140
  end
137
141
  end
138
-
139
- context "and a recognize proc is defined" do
140
- before(:each) do
141
- ActionController::Routing::Routes.recognize_subdomain(:city) { |city| } # this block will be stubbed
142
- end
143
-
144
- it "should not change the host if the recognize proc returns true" do
145
- with_host "boston.example.com" do
146
- ActionController::Routing::Routes.subdomain_procs.should_receive(:recognize).twice.with(:city, "boston").and_return(true)
147
- city_events_url.should == "http://boston.example.com/events"
148
- city_events_path.should == "/events"
149
- end
150
- end
151
-
152
- it "should raise a routing error if the recognize proc returns false" do
153
- with_host "www.example.com" do
154
- ActionController::Routing::Routes.subdomain_procs.should_receive(:recognize).twice.with(:city, "www").and_return(false)
155
- lambda { city_events_url }.should raise_error(ActionController::RoutingError)
156
- lambda { city_events_path }.should raise_error(ActionController::RoutingError)
157
- end
142
+
143
+ it "should force the host if the object has a different to_param from the current subdomain" do
144
+ with_host "example.com" do
145
+ city_events_url(@boston).should == "http://boston.example.com/events"
146
+ city_events_path(@boston).should == "http://boston.example.com/events"
158
147
  end
159
-
160
- it "should force the host if the recognize proc returns false but a matching subdomain is supplied" do
161
- with_host "www.example.com" do
162
- ActionController::Routing::Routes.subdomain_procs.should_receive(:recognize).twice.with(:city, "boston").and_return(true)
163
- city_events_url(:subdomain => :boston).should == "http://boston.example.com/events"
164
- city_events_path(:subdomain => :boston).should == "http://boston.example.com/events"
165
- end
148
+ end
149
+
150
+ it "should raise an error if the object to_param is an invalid subdomain" do
151
+ @newyork = City.new
152
+ @newyork.stub!(:new_record?).and_return(false)
153
+ @newyork.stub!(:to_param).and_return("new york")
154
+ with_host "www.example.com" do
155
+ lambda { city_events_url(@newyork) }.should raise_error(ActionController::RoutingError)
156
+ lambda { city_events_path(@newyork) }.should raise_error(ActionController::RoutingError)
166
157
  end
167
-
168
- it "should raise a routing error if the recognize proc returns false and a non-matching subdomain is supplied" do
169
- with_host "www.example.com" do
170
- ActionController::Routing::Routes.subdomain_procs.should_receive(:recognize).twice.with(:city, "hobart").and_return(false)
171
- lambda { city_events_url(:subdomain => :hobart) }.should raise_error(ActionController::RoutingError)
172
- lambda { city_events_path(:subdomain => :hobart) }.should raise_error(ActionController::RoutingError)
173
- end
158
+ end
159
+
160
+ it "should not allow the subdomain to be manually overridden" do
161
+ with_host "www.example.com" do
162
+ city_events_url(@boston, :subdomain => :canberra).should == "http://boston.example.com/events"
163
+ city_events_path(@boston, :subdomain => :canberra).should == "http://boston.example.com/events"
174
164
  end
175
165
  end
176
166
  end
@@ -1,12 +1,29 @@
1
1
  describe "ActiveRecord::Base" do
2
- it "should have validates_subdomain_format_of which runs SubdomainRoutes.valid_subdomain? against the attributes" do
2
+ before(:each) do
3
+ ActionController::Routing::Routes.clear!
4
+ SubdomainRoutes::Config.stub!(:domain_length).and_return(2)
3
5
  class User < ActiveRecord::Base
4
6
  attr_accessor :subdomain
5
- User.validates_subdomain_format_of :subdomain
6
7
  end
8
+ end
9
+
10
+ it "should have validates_subdomain_format_of which runs SubdomainRoutes.valid_subdomain? against the attributes" do
11
+ User.validates_subdomain_format_of :subdomain
7
12
  SubdomainRoutes.should_receive(:valid_subdomain?).with("mholling").and_return(true)
8
- User.new(:subdomain => "mholling").valid?.should be_true
13
+ User.new(:subdomain => "mholling").should be_valid
9
14
  SubdomainRoutes.should_receive(:valid_subdomain?).with("mholling").and_return(nil)
10
- User.new(:subdomain => "mholling").valid?.should be_false
15
+ User.new(:subdomain => "mholling").should_not be_valid
16
+ end
17
+
18
+ it "should have validates_subdomain_not_reserved which checks the attributes against the fixed-subdomain routes" do
19
+ User.validates_subdomain_not_reserved :subdomain
20
+ reserved = [ "", "www", "support", "admin" ]
21
+ map_subdomain(*reserved) { |map| map.resource :home }
22
+ reserved.each do |subdomain|
23
+ User.new(:subdomain => subdomain).should_not be_valid
24
+ end
25
+ [ "mholling", "edmondst" ].each do |subdomain|
26
+ User.new(:subdomain => subdomain).should be_valid
27
+ end
11
28
  end
12
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mholling-subdomain_routes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Hollingworth
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-30 00:00:00 -07:00
12
+ date: 2009-06-02 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -38,9 +38,7 @@ files:
38
38
  - VERSION.yml
39
39
  - lib/subdomain_routes.rb
40
40
  - lib/subdomain_routes/config.rb
41
- - lib/subdomain_routes/mailer.rb
42
41
  - lib/subdomain_routes/mapper.rb
43
- - lib/subdomain_routes/proc_set.rb
44
42
  - lib/subdomain_routes/request.rb
45
43
  - lib/subdomain_routes/resources.rb
46
44
  - lib/subdomain_routes/routes.rb
@@ -48,8 +46,6 @@ files:
48
46
  - lib/subdomain_routes/url_writer.rb
49
47
  - lib/subdomain_routes/validations.rb
50
48
  - spec/extraction_spec.rb
51
- - spec/mailer_spec.rb
52
- - spec/proc_set_spec.rb
53
49
  - spec/recognition_spec.rb
54
50
  - spec/resources_spec.rb
55
51
  - spec/routes_spec.rb
@@ -84,8 +80,6 @@ specification_version: 2
84
80
  summary: A Rails library for incorporating subdomains into route generation and recognition.
85
81
  test_files:
86
82
  - spec/extraction_spec.rb
87
- - spec/mailer_spec.rb
88
- - spec/proc_set_spec.rb
89
83
  - spec/recognition_spec.rb
90
84
  - spec/resources_spec.rb
91
85
  - spec/routes_spec.rb
@@ -1,17 +0,0 @@
1
- module SubdomainRoutes
2
- module MailerMethods
3
- def self.included(base)
4
- base.alias_method_chain :create!, :subdomains
5
- end
6
-
7
- def create_with_subdomains!(*args)
8
- ActionController::Routing::Routes.subdomain_procs.flush! unless SubdomainRoutes::Config.manual_flush
9
- create_without_subdomains!(*args)
10
- end
11
- end
12
- end
13
-
14
- if defined? ActionMailer::Base
15
- ActionMailer::Base.send :include, SubdomainRoutes::MailerMethods
16
- end
17
-
@@ -1,33 +0,0 @@
1
- module SubdomainRoutes
2
- class ProcSet
3
- extend ActiveSupport::Memoizable
4
-
5
- def initialize
6
- clear!
7
- end
8
-
9
- def add_recognizer(name, &block)
10
- @recognizers[name] = block
11
- end
12
-
13
- def recognizes?(name)
14
- @recognizers.has_key?(name)
15
- end
16
-
17
- def recognize(name, subdomain)
18
- @recognizers[name].call(subdomain) if recognizes?(name)
19
- end
20
-
21
- memoize :recognize
22
- private :flush_cache
23
-
24
- def flush!
25
- flush_cache :recognize
26
- end
27
-
28
- def clear!
29
- @recognizers = {}
30
- flush!
31
- end
32
- end
33
- end
data/spec/mailer_spec.rb DELETED
@@ -1,15 +0,0 @@
1
- describe "ActionMailer::Base" do
2
- before(:each) do
3
- @mailer_class = Class.new(ActionMailer::Base) { def test; body "test"; end }
4
- end
5
- it "should flush the subdomain procs cache each time a mailer is created" do
6
- ActionController::Routing::Routes.subdomain_procs.should_receive(:flush!)
7
- @mailer_class.create_test
8
- end
9
-
10
- it "should not flush the subdomain procs cache if SubdomainRoutes::Config.manual_flush is set" do
11
- SubdomainRoutes::Config.stub!(:manual_flush).and_return(true)
12
- ActionController::Routing::Routes.subdomain_procs.should_not_receive(:flush!)
13
- @mailer_class.create_test
14
- end
15
- end
@@ -1,61 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe "subdomain proc set" do
4
- before(:each) do
5
- @proc_set = SubdomainRoutes::ProcSet.new
6
- end
7
-
8
- context "with a subdomain recognizer" do
9
- before(:each) do
10
- @city_block = lambda { |city| } # this recognizer block will be stubbed out
11
- @proc_set.add_recognizer(:city, &@city_block)
12
- end
13
-
14
- it "should indicate what subdomain it recognizes" do
15
- @proc_set.recognizes?(:city).should be_true
16
- @proc_set.recognizes?(:user).should be_false
17
- end
18
-
19
- it "should run the recognizer" do
20
- @city_block.should_receive(:call).with("boston").and_return(true)
21
- @proc_set.recognize(:city, "boston").should == true
22
- end
23
-
24
- it "should raise any error that the recognizer raises" do
25
- error = StandardError.new
26
- @city_block.stub!(:call).and_raise(error)
27
- lambda { @proc_set.recognize(:city, "hobart") }.should raise_error { |e| e.should == error }
28
- end
29
-
30
- it "should return nil if it can't recognize the name" do
31
- @proc_set.recognize(:user, "mholling").should be_nil
32
- end
33
-
34
- it "should call the recognize proc only once for multiple recognitions" do
35
- @city_block.should_receive(:call).with("boston").once
36
- 2.times { @proc_set.recognize(:city, "boston") }
37
- end
38
-
39
- it "should return the cached value according to the arguments" do
40
- @city_block.should_receive(:call).with("boston").once.and_return(true)
41
- @city_block.should_receive(:call).with("hobart").once.and_return(false)
42
- 2.times do
43
- @proc_set.recognize(:city, "boston").should == true
44
- @proc_set.recognize(:city, "hobart").should == false
45
- end
46
- end
47
-
48
- it "should call the recognize proc again once the cache is flushed" do
49
- @city_block.should_receive(:call).with("boston").twice
50
- 5.times { @proc_set.recognize(:city, "boston") }
51
- @proc_set.flush!
52
- 5.times { @proc_set.recognize(:city, "boston") }
53
- end
54
- end
55
-
56
- it "can be cleared of its procs" do
57
- @proc_set.add_recognizer(:city) { |city| }
58
- @proc_set.clear!
59
- @proc_set.recognizes?(:city).should be_false
60
- end
61
- end