jonoaustin
jonoaustin

Reputation: 45

Solution for different routes and keeping DRY

Would like to keep my views DRY!

Problem description:

I have successfully implemented AJAX pagination, however I have to use static paths in the example below - using root_path for the "load more products" link. This is fine when user is visiting root URL, but obviously breaks when visiting a /s/[storename] or /c/[collectionname]

I can't quite conceptually wrap my head around what I need to do to refactor this? I'd like to preserve the /t /s /c routing if possible.

Any thoughts?

routes

root_path        GET     /               products#tile
tag_view_path        GET     /t/:tag(.:format)       products#tile
store_view_path      GET     /s/:store_slug(.:format)    products#tile
collection_view_path     GET     /c/:collection_slug(.:format)   products#tile

products_controller

def tile

    # /t/:tag           Show all by tag
    # /s/:store_slug        Show all by store
    # /c/:collection_slug       Show all by collection
    # Nothing passed        Show all by rank

    if params[:tag]
        # find by tag
        # to be implemented
    elsif params[:store_slug]
        store = Store.friendly.find(params[:store_slug])
        @products = store.products.paginate(:page => params[:page]).order('rank DESC') 
        respond_to do |format|
            format.html
            format.js 
        end
    elsif params[:collection_slug]
        collection = Collection.friendly.find(params[:collection_slug])
        @products = collection.products.paginate(:page => params[:page]).order('rank DESC') 
        respond_to do |format|
            format.html
            format.js 
        end
    else
        @products = Product.paginate(:page => params[:page]).order('rank DESC') 
        respond_to do |format|
            format.html
            format.js 
        end

    end


end

tile.html.erb (next page link)

  <div><%= link_to 'Load More Products', root_path(:page => @products.next_page), :class    => 'load-more-products', :remote => true if @products.next_page %></div>

tile.js.erb (JS to append ajax response)

 $('.column-holder').append('<%= escape_javascript(render partial: "product", collection: @products) %>');
 $('a.load-more-products').attr('href', '<%= root_path page: @products.next_page %>');

routes as requested

  # product short URL /p/slug-name-for-title
  match 'p/:product_slug',          to: 'products#show',      via: 'get', as: 'product_view'

  # show products by tag /t/tagname
  match 't/:tag',                   to: 'products#tile',      via: 'get', as: 'tag_view'

  # show products by store /s/store-slug-name
  match 's/:store_slug',            to: 'products#tile',      via: 'get', as: 'store_view'

  # show products by collection /c/collection-slug-name
  match 'c/:collection_slug',       to: 'products#tile',      via: 'get', as: 'collection_view'

Upvotes: 1

Views: 55

Answers (1)

EnabrenTane
EnabrenTane

Reputation: 7466

You need to pass "where" you are to your view so you paginate the right thing.

I would look for a way to make this 3 or 4 different methods as well. Short term you can DRY it up a little like this.

# NOTE you may need to do something like
# helper ProductHelper # to get the method from the "other file" below
def tile

# /t/:tag           Show all by tag
# /s/:store_slug        Show all by store
# /c/:collection_slug       Show all by collection
# Nothing passed        Show all by rank

    if params[:tag]
        # find by tag
        # to be implemented
    elsif params[:store_slug]
        store = Store.friendly.find(params[:store_slug])
        @products = store.products.paginate(:page => params[:page]).order('rank DESC') 
    elsif params[:collection_slug]
        collection = Collection.friendly.find(params[:collection_slug])
        @products = collection.products.paginate(:page => params[:page]).order('rank DESC') 
    else
        @products = Product.paginate(:page => params[:page]).order('rank DESC') 
    end
    respond_to do |format|
        format.html
        format.js 
    end
end

Really the above is, #products_by_tag, #products_by_store, #products_by_collection, and #index for all. That is another matter though.

class ProductHelper
   def url_for_tile_type(options = {})
     if params[:tag]
       tag_view_path(options)
     elsif params[:store_slug]
       store_view_path(options)
     elsif params[:collection_slug]
        collection_view_path(options)
     else
       product_path(options)
     end
   end
end

Then your js view becomes,

$('.column-holder').append('<%= escape_javascript(render partial: "product", collection: @products) %>');
$('a.load-more-products').attr('href', '<%= url_for_tile_type page: @products.next_page %>');

Here we have implemented something that functions somewhat like polymorphic_url_for and sends us to the "right" next page.

In your routes.rb match is deprecated in rails 4 I think. If you are using rails 3+ you can do.

 # product short URL /p/slug-name-for-title
  get 'p/:product_slug',          to: 'products#show',      as: 'product_view'

  # show products by tag /t/tagname
  get 't/:tag',                   to: 'products#tile',      as: 'tag_view'

  # show products by store /s/store-slug-name
  get 's/:store_slug',            to: 'products#tile',      as: 'store_view'

  # show products by collection /c/collection-slug-name
  get 'c/:collection_slug',       to: 'products#tile',      as: 'collection_view'

Upvotes: 1

Related Questions