Rails routes base on model attributes

2019-09-03 08:18发布

问题:

with reference to Rails url options based on model attribute, I have a proposed solution but I am not sure if this is the way to proceed. I am also looking to create routes based on model's attributes for seo purpose. Here it goes.

I have a business models, and the routes generated is /businesses/id. With friendlyid gem, it becomes businesses/slug

However, I want the route to become /business-type/business-name-and-address for example.

So with reference to Code Connoisseur's blog

# app/models/dynamic_router.rb
  class DynamicRouter
    def self.load
      Ttt::Application.routes.draw do
        Business.all.each do |biz|
          get "/#{biz.slug}", :to => "businesses#show", defaults: { id: biz.id, name: biz.name, type: biz.type, address: biz.address }
        end
      end
    end

    def self.reload
      Ttt::Application.routes_reloader.reload!
    end
  end

# config/routes.rb
  DynamicRouter.load
  resources :businesses # still preserve old routes for 301 redirect in businesses#show

# app/controller/businesses_controller.rb
  def show
    @business = Business.cached_find(params[:id])
      if request.path != @business.slug
        redirect_to @business.slug, :status => :moved_permanently
      else
        ...
      end
    end
  end

# app/models/business.rb
  after_save :reload_routes

  def normalize_friendly_id text
    # slug is changed here and works in conjunction with FriendlyID
    # and keeps record of old routes (/business/xxx-xxx) for 301 redirects
    "/#{self.type.parameterize}/sg/#{self.name.parameterize}-#{self.address.parameterize}"
  end

  def reload_routes
    if slug.blank? || name_changed? || address_changed? || type_changed?
      DynamicRouter.reload
    end
  end

# app/helper/business_helper and application_controller.rb
  # to still be able to use business_path just like the defauilt url_helper,
  # override it in the relevant files
  def business_path business, options={}
    path = ""
    path = "#{business.slug}"

    # this part can be and should be more robust
    if options[:some_param]
      path += "?some_param=#{options[:some_param]}"
    end

    path += "##{options[:anchor]}" if options[:anchor]
    return path
  end

I can now access the business#show pages via /businesses/xxx or the new /type/name-address routes, with the former being able to be 301 redirected to the latter.

Whenever a new business is created, the routes will reload via after_save callback and a new route to that new business will be created without having to reload the application.

If the business is updated again, and its route changed from /type1/name1-address1 to /type1/name2-address1, the first route will become a deadlink. To deal with this I use high_voltage gem, and override the invalid_page method to redirect this route back to businesses#show

# app/controller/pages_controller
  class PagesController < ApplicationController
    include HighVoltage::StaticPage
      def invalid_page
        slug = FriendlyId::Slug.find_by_slug("/" + params[:id])
        if slug && slug.sluggable_type == "Business"
          redirect_to slug.sluggable.slug, :status => :moved_permanently
        else
          raise ActionController::RoutingError, "No such page: #{params[:id]}"
        end
      end
    end
  end

Doing this allows me to achieve what I want but Im not sure if this is the way to go. For one, this method will create as many routes as number of rows in the business table. Would this adversely impact the performance or other aspects of the application if any?

Love to hear from everyone!

回答1:

and the routes generated (friendlyid gem is used)

The generated routes are still the same as they were without friendly_id:

The difference is friendly_id introduces several REALLY cool methods which populate the path helpers & queries (finders) with your slug :)

So in actuality, you're still passing params[:id] to your controller.

Just wanted to clear that up.


I want the route to become /business-type/business-name-business-address

The simple solution is a case of changing the slug in the db (custom slugs):

#app/models/business.rb
class Business < ActiveRecord::Base
   extend FriendlyId
   friendly_id :name_and_address, use: :slugged

   def name_and_location
    "#{name}-#{address}"
   end
end

The example given is as follows:

bob = Person.create! :name => "Bob Smith", :location => "New York City"
bob.friendly_id #=> "bob-smith-from-new-york-city"

If you did this, you could affect the changes in your db by running the following commands in cmd:

$ rails c
$ Business.find_all(&:save)

In regards your dynamic routes - they may work, but they look seriously hacky.

If you want me to critique the custom code, I'll write an update; I think the above will achieve what you want.