How to create app-wide slug routing for Rails app?

前端 未结 3 1339
清酒与你
清酒与你 2020-12-03 13:07

I have a number of different models in the Rails app I\'m working on. I\'ve seen a number of sites use an app-wide slug routing approach. What do I mean by this?



        
相关标签:
3条回答
  • 2020-12-03 13:27

    I'd probably approach this as follows, at least as a first pass:

    • Use friendly_id or similar to generate slugs for each model involved
    • Hook up a catch-all route for /([-_a-zA-Z0-9]+) and point it to something like EntitiesController#show
    • Hook up a higher priority route for /developers pointing to Users#index
    • In EntitiesController#show:

      @entity = User.find(params[:id]) or Company.find(params[:id]) or raise ActionController::RoutingError.new('Not Found')

    • Then, based on the type of entity you've got:

      render "VIEW_PATH_BASED_ON_ENTITY_CLASS/show"

    I would also order the finds from most to least frequently accessed (guess first, then use data later to tweak the order).

    Finally, probably obvious but make sure you're indexing the slug column in each table since you'll often be doing multiple finds per request.

    FWIW I'd love to know a better way to approach this as well; this is simply how I'd attack the problem initially.

    0 讨论(0)
  • 2020-12-03 13:39

    There may be a way to do this with freindly_id but i think the problem with friendly id is things are scoped by model.

    If I wanted truely sitewide slugging I would create a slugs table with a polymorphic relationship to all my models.

    Sluggable_type and sluggable_id and then a slug field with the complete permalink/slug.

    +---------------------------------------------+
    | sluggable_type | sluggable_id |     slug    |
    |      user      |       13     |  users/john |
    +---------------------------------------------+
    

    Now i could do do a wildcard catch all route or create the routes for all my slugs at runtime and force a route refresh when a model is updated that was under this sluggable control.

    routes.rb

      get "/*segments",
                   :controller => 'slugs',
                   :action => 'dynamicroute'
    

    Now in your SlugsController implement a method like

    def dynamicroute
      segments = params[:segments]
      slugs.find_by_slug(segments)
      slug.sluggable_type.constantize.find(slug.sluggable_id) #retrive real record
      #some magic to handle the slugged item maybe redirect to the appropriate
      #controller or somehow call the show view for that controller
    end
    

    OR

    routes.rb

    begin  
      Slug.all.each do |s|
        begin
          get "#{s.slug}" => "#{s.sluggable_type.demodulize.pluralize.camelize}#show"
        rescue
        end
      end
    rescue
    end
    

    If you use the 2nd approach to routing make sure you call

    YOUR_APP_NAME::Application.reload_routes!

    After editing any slugged record to refresh the routing table.

    We've had similar issues and we may try our hand at gemifying this approach.

    0 讨论(0)
  • 2020-12-03 13:39

    My first reaction is to create a new Slug model. This model would have a polymorphic belongs_to:

    belongs_to :sluggable, :polymorphic => true
    

    At the least this table would have:

    • value - Or some better name than this. The value of the slug itself.
    • sluggable_type and sluggable_id - The polymorphic foreign keys.

    Your company, user, etc. models models would just have a slug:

    has_one :slug
    

    This gives us a few advantages off the bat:

    • It's now easy to make a unique constraint on the slug value. If the slug were kept as a property of all the different sluggable models, your unique constraint would have to check all the other sluggable tables for uniqueness. Makes for a bad time.
    • The routing is simple enough, since you can use a normal resource route off of the root level namespace. You would want to keep it low/at-the-end of the route file though so other more specific routes take precedence. Edit: This routing is basically the first routing method j_mcnally suggests.
    • All the logic for a slug, like what makes a valid slug, is kept in this one model. Good separation of concerns instead of polluting say a User model. Especially if the slug rules are the same for everyone as they are here.

    In terms of how the controller would work, I'd go with what Kyle said and key off the sluggable_type field to find the view you want to render.

    0 讨论(0)
提交回复
热议问题