The original: http://pastie.textmate.org/private/lqvrlyyvkv2kbugoxtiz6w Send your submissions to me by email or on #merb. I''ll blog it, and pick a winner. The winner does not mean it makes it into Merb, just means I personally like it best. But it might make it into Merb. # Scenario: # You have a simple application that lists all the monkeys at the zoo. # Every monkey has a name, and that name is unique. # It is currently working. # # Your boss comes and asks you to change the "ugly" /monkeys/44 URLs # to be "pretty" and "SEO optimized" URLs like /monkeys/koko, with the # monkey''s name in the URL. # # Challenge: # Design your perfect API for doing this, using any combination of # controller, model, and router changes. Submit it as a diff of this pastie. # # Degree of difficulty: # Don''t complain that doing /monkeys/koko is dumb, or what happens # when there are two monkeys named koko. # # Submit your diffs to #merb # router: r.resources :monkeys class Monkey < MythicalORM end class Monkeys < Application def show(id) @monkey = Monkey.find(id) end def create(monkey) @monkey = Monkey.create(monkey) redirect url(:monkey, at monkey) end end
# psudo-untested-ruby-while-having-coffee code version by chrisfarms # # produces urls like /monkeys/3-your-keywords # # works for SEO, while maintaining all the benifits of a solid numeric ID # plus points for SEO... if the keywords are changed, 302 redirects will automatically # move the client to the right page.... solving the duplicate content issue or # 404 errors after fixing a typo in a ''slug'' # # controller code should be untouched http://pastie.textmate.org/private/gcet24o9er4milpqemjjq On 16 Nov 2007, at 02:26, Michael D. Ivey wrote:> The original: > http://pastie.textmate.org/private/lqvrlyyvkv2kbugoxtiz6w > > Send your submissions to me by email or on #merb. I''ll blog it, and > pick a winner. The winner does not mean it makes it into Merb, just > means I personally like it best. > > But it might make it into Merb. > > > > # Scenario: > # You have a simple application that lists all the monkeys at the zoo. > # Every monkey has a name, and that name is unique. > # It is currently working. > # > # Your boss comes and asks you to change the "ugly" /monkeys/44 URLs > # to be "pretty" and "SEO optimized" URLs like /monkeys/koko, with the > # monkey''s name in the URL. > # > # Challenge: > # Design your perfect API for doing this, using any combination of > # controller, model, and router changes. Submit it as a diff of this > pastie. > # > # Degree of difficulty: > # Don''t complain that doing /monkeys/koko is dumb, or what happens > # when there are two monkeys named koko. > # > # Submit your diffs to #merb > > > # router: > r.resources :monkeys > > class Monkey < MythicalORM > > end > > > class Monkeys < Application > def show(id) > @monkey = Monkey.find(id) > end > > def create(monkey) > @monkey = Monkey.create(monkey) > redirect url(:monkey, at monkey) > end > end > _______________________________________________ > Merb-devel mailing list > Merb-devel at rubyforge.org > http://rubyforge.org/mailman/listinfo/merb-devel-------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/merb-devel/attachments/20071116/8724d694/attachment.html
On Thu, Nov 15, 2007 at 08:26:47PM -0600, Michael D. Ivey wrote:> # Scenario: > # You have a simple application that lists all the monkeys at the zoo. > # Every monkey has a name, and that name is unique. > # It is currently working.Presumably it also has something like def index @monkeys = Monkey.find(:all) end> # Your boss comes and asks you to change the "ugly" /monkeys/44 URLs > # to be "pretty" and "SEO optimized" URLs like /monkeys/koko, with the > # monkey''s name in the URL. > # > # Challenge: > # Design your perfect API for doing thisWhat do you mean by ''API'' here? Or is the idea to explore all the possibilities which might be implied? As far as I can see: (1) you could use an ORM which permits use of an arbitrary field as the primary key; in this case only the model would need to change. class Monkey < MythicalORM self.primary_key = ''name'' end (In the case of ActiveRecord, I believe that non-integer ID fields are not supported. Also, if you intend retaining the old integer ''id'' column in parallel with the monkey name primary key, beware that AR may no longer generate the next integer id for you automatically, so you may need to do this explicitly in a before_create hook) (2) modify Merb so the url() helper can use an arbitrary model field as the resource id, e.g. r.resources :monkeys, :resource_id => :name class Monkeys < Application def show(id) @monkey = Monkey.find_by_name(id) || (raise "No such monkey") end end The :resource_id is used by the url() helper to pick the correct attribute to put into the URL. But if the idea is to add some feature to Merb for this, I would suggest first a generalisation of the problem to allow for composite primary keys. e.g. if each monkey has a first name and last name, and the tuple (first name, last name) is unique, then allow resource URLs of the form /monkeys/koko/pops or /monkeys/koko;pops This introduces further difficulties (*), but is realistic when wanting to map a web application onto an existing schema which was designed in this way. (*) in particular: the database may have primary key tuples of the form ("","foo") and/or (null,"foo")> # Don''t complain that doing /monkeys/koko is dumb, or what happens > # when there are two monkeys named kokoOr what happens when a monkey''s name contains a forward slash. (If you replace monkeys with car parts, IDs of the form xxx/yyy are not uncommon; with Rails at least, even encoding as %2F is treated as a path separator) Which reminds me of: http://xkcd.com/327/ Incidentally, when I wanted to do something similar to this in a Rails app, I ended up using URLs of the form /monkeys?name=koko Then if a single monkey matches, I redirect to /monkeys/44; if multiple monkeys match, I list them. Something like this: MAX_RESULT = 10 def index if params[:name] @monkeys = Monkey.find(:all, :conditions=>[''name=?'',params[:name]], :limit => MAX_RESULT, :order => ''name'') render_monkeys(''Monkeys by name'') else @monkeys = Monkey.find(:all) end end def render_monkeys(title = ''Monkeys'') @title = title if @monkeys.size == 1 redirect_to monkey_url(@monkeys.first) elsif @monkey.size == 0 flash.now[:notice] = ''Monkey not found'' else if @monkeys.size == MAX_RESULT flash.now[:notice] = "Only the first #{MAX_RESULT} monkeys shown" end end You can extend the ''if'' statement in index to allow for other ways of referring to the monkey, e.g. /monkeys?asset_tag=12345. Now, this suggests a question back to the boss: after the proposed change of URLs, what happens if you want to fetch a monkey given its original ID of 44? Would we need to provide a second URL of the form /monkeys?id=44 ? Or will the old ID just become a normal data attribute? Or is this change of "primary key" going to be so pervasive that we will delete the old id column from the database entirely? If multiple ''primary keys'' are to be exposed, you could argue that this should be done orthogonally, e.g. /monkeys/id/44 /monkeys/name/koko /monkeys/asset_tag/12345 But this still begs the question of which form will be generated by the url() helper. So ultimately one of these is going to take precedence over all the others. Regards, Brian.
On Nov 16, 2007, at 3:14 AM, Chris Farmiloe wrote:> http://pastie.textmate.org/private/gcet24o9er4milpqemjjqThanks, Chris, got it on the list. I wasn''t going to comment on entries, but since this is only a sorta- contest (there will be a prize!) I figured it was OK. I had never thought of pushing it all into the model. It may be a "violation of MVC" to raise controller exceptions from inside models, but is that a bad thing? Raising "Moved" with the new name is excellent. So I guess the MVC purist in me was horrified, and the Resource guy in me thought this was super sexy. Have you used a pattern like this in production yet? BTW: Merb already defines MovedPermanently and MovedTemporarily, but they don''t take the arg for the new URL. We should add that: raise MovedPermanently.new(new_url)
On 16 Nov 2007, at 14:52, Michael D. Ivey wrote:> I had never thought of pushing it all into the model. It may be a > "violation of MVC" to raise controller exceptions from inside models, > but is that a bad thing? Raising "Moved" with the new name is > excellent.I''ve been playing with a fork of merb for one production app, and for me ControllerExceptions are more like ApplicationExceptions not specific to controller-land> Have you used a pattern like this in production yet?On a few rails apps, I''ve used something similar, although without merb''s exception controllers its a bit messy. try: http://www.africacollection.co.uk/pages/1-holidays-to-southern-africa-the-indian-ocean then http://www.africacollection.co.uk/pages/1-holidays-to-southern-africa-blah-blah-blah> BTW: Merb already defines MovedPermanently and MovedTemporarily, but > they don''t take the arg for the new URL. We should add that: > raise MovedPermanently.new(new_url)A built in handler for the Moved__ error classes would be nice too. raise MovedTemporaily.new(url) would be roughly equal to redirect(url) and throw(:halt) -------------- next part -------------- An HTML attachment was scrubbed... URL: http://rubyforge.org/pipermail/merb-devel/attachments/20071116/7f7c3761/attachment-0001.html
On Nov 16, 2007, at 3:48 AM, Brian Candler wrote:> Presumably it also has something like > def index > @monkeys = Monkey.find(:all) > endYep just left them out for brevity. They''re the standard CRUD actions.> What do you mean by ''API'' here? Or is the idea to explore all the > possibilities which might be implied?By "Perfect API" I meant, "Assume Merb is your Perfect Framework, and works exactly as it should. What would you have to change in your app, using Merb''s perfect API, to accomplish your task?"> (2) modify Merb so the url() helper can use an arbitrary model > field as the > resource id, e.g. > > r.resources :monkeys, :resource_id => :nameYou''re at least the third person to suggest attaching this to the routes, so that seems like an approach worth exploring for Merb.> But if the idea is to add some feature to Merb for thisWell, it''s both. Merb needs some way to handle not using the id attribute of a model in URLs. Especially in the case where the models aren''t even database backed. But this is also a contest, for making up cool and pretty APIs.> I would suggest first a generalisation of the problem to allow for > composite primary keys. > e.g. if each monkey has a first name and last name, and the tuple > (first name, last name) is unique, then allow resource URLs of the > form > > /monkeys/koko/pops > or /monkeys/koko;popsTuples as URL segments...interesting. My brain immediately went to /monkeys/[koko,pops] but that''s not legal. After spending too much time in the spec when I should be getting ready for work, I came up with this: /monkeys;koko,pops ; signifies params for a path segment....so it says "the collection of monkeys, limited by this tuple" ... legal, semantic....kinda pretty if you''re a URI geek, too. I can''t decide without more linking and parsing if / monkeys/;koko,pops would also be legal. Anyway, interesting stuff, thanks for the email. I didn''t see an official entry into the contest, though. :)
On Fri, Nov 16, 2007 at 09:19:11AM -0600, Michael D. Ivey wrote:> Tuples as URL segments...interesting. My brain immediately went to > /monkeys/[koko,pops] but that''s not legal. After spending too much > time in the spec when I should be getting ready for work, I came up > with this: > > /monkeys;koko,pops > > ; signifies params for a path segment....so it says "the collection > of monkeys, limited by this tuple" ... legal, semantic....kinda > pretty if you''re a URI geek, too.FWIW, Rails have dropped their use of the semicolon, apparently because of some compatibility problems. i.e. they now use /monkeys/123/squawk instead of /monkeys/123;squawk See: http://weblog.rubyonrails.org/2007/10/5/rails-1-2-4-maintenance-release Given that a tuple is always of fixed size, I would be inclined towards /monkeys/koko/pops, if only because (a) in the degenerate case of the single-key tuple it''s the same as now, and (b) it doesn''t introduce any more characters which need escaping if they occur within the key columns. Given a composite key, perhaps the router should set the ''id'' parameter to an array? This would be convenient if the ORM supports composite keys in this way, as you could still do Monkey.find(id) # id = [''koko'',''pops''] I don''t know which, if any, ORMs would support this. But in any case you can always do Monkey.find_by_firstname_and_lastname(*id) || (raise "No banana") But in any case maybe it''s better to name the key components explicitly, e.g. r.resources :monkeys, :resource_id=>[:firstname,:lastname] which could just build a pattern of monkeys/:firstname/:lastname and the routing code wouldn''t have to change.> Anyway, interesting stuff, thanks for the email. I didn''t see an > official entry into the contest, though. :)I don''t know what the prize is, but I can probably live without it :-) Regards, Brian.
On Nov 16, 2007, at 9:15 AM, Chris Farmiloe wrote:>> BTW: Merb already defines MovedPermanently and MovedTemporarily, but >> they don''t take the arg for the new URL. We should add that: >> raise MovedPermanently.new(new_url) > > A built in handler for the Moved__ error classes would be nice too. > > raise MovedTemporaily.new(url) > > would be roughly equal to > > redirect(url) and throw(:halt)Yep, that should be in the default generated Exceptions controller. What sensible default should MovedPermanently use for the url? I guess just nil, and if you don''t specify one moved_permanently would display the error. I''ll work on this next week if no one else does.
I just remembered a major problem with the boss'' suggestion: what if a monkey''s name collides with a controller action? For example, is /monkeys/new a ''new'' action on the whole collection, or a ''show'' action on an individual monkey whose name is ''new''? This pretty much *requires* IDs to be numeric, unless we accept a further rejigging of URLs, such as /monkeys/new /monkeys/show/koko /monkeys/edit/koko or /monkeys/*/new /monkeys/koko /monkeys/koko/edit or even /monkeys/new /monkey/koko /monkey/koko/edit The first of these most closely retains the controller/action semantic, but does not match the boss'' URL spec, and it moves away fm the idea of an object as a ''resource'' like a file in a filesystem. For another example of ambiguity, have a look at this bit of routing code, and consider what happens if a monkey''s name is ''edit'': next_level.match("/:id/edit").to_route.name(:"edit_#{route_singular_name}") next_level.match("/:action/:id").to_route.name(:"custom_#{route_singular_name}") (Unlikely? Well ''Edith'' is a name, so I can imagine that ''Edit'' might be a name in some other languages :-) I think these problems go away if semicolons are required for named actions, i.e. /monkeys;new /monkeys/koko /monkeys/koko;edit but at the moment Merb and Rails accept both ; and / Regards, Brian.
> /monkeys/new > /monkey/koko > /monkey/koko/editI''ve always preferred this style. The collection resource and the individual resource are different things. What about: Collection: /monkeys New form: /new-monkey Member: /monkey/koko Edit form: /monkey-editor/koko We''re way off track from the Challenge, but I love talking about URLs.