Search code examples
ruby-on-railsrubynested-resourcesnested-routes

How to define a controller function that works for instances of any model


Implementing versioning for a Rails app I'd like to have a view that displays all versions of a model with some extra functionality like reverting etc. I use the paper_trail gem for the versioning.

I know that I could do that by writing a controller function like versions and a view for every model but I'd like to do it for all models at once. This should be possible because the model.versions attribute is always structured identically.

Ideally the URL should look like /pages/testpage/versions while testpage is the page id.

This seems similar to the concept of nested routes in rails.

resources :pages do                                                    
    resources :versions                                                  
end

The problems with nested routes however are:

  • Needs extra configuration per model
  • I cannot access the testpage object without knowing of which model it is an instance. I also wasn't able to find a way to determine the model since the only thing that is provided to my versions controller is the params hash.

I'm completely open to alternative solutions that might not follow my initial ideas.


Solution

  • Write it in your ApplicationController and define it as a helper_method.

    For example

    class ApplicationController < ActionController::Base
      helper_method :current_time
    
      def current_time
        Time.now
      end
    end
    

    Now you can cal current_time everywhere in controllers or views.

    Also you can write separate Module/Class and define there your helpers methods. Than you should include this file into your ApplicationController as well

    UPD after theme is changed

    I didn't think about your actual question. But I can say that your approach is nod the best here.

    You should create new resource instead of creating new functionality which will hard to be tested. So create new resource (controller): versions and play around this controller.

    For example how it can work:

    /versions/pages/132
    /versions/comments/1003
    

    How to realize it:

    match "/versions/:model/:id", :to => "versions#index"
    

    In your controller:

    class VersionsController < ActionController::Base
      def index
        @object = my_type.find(params[:id])
        @versions = @object.versions
      end
    
      private
      def my_type
        params[:model].constantize
      end
    end
    

    Of course you can change routes the way you want:

    match "/:model/:id/versions", :to => "versions#show"
    

    So now your pretty /pages/testpage/versions will work fine for you without any new strange logic.

    UPD 2

    Imagine you have got this route:

    match "/:model/:id/versions", :to => "versions#index", :as => :versions
    

    And this objects:

    @page = Page.last
    @hotel = Hotel.find(123)
    @comment = @page.comments.first
    

    How will we create links for versions:

    <%= link_to "Versions of this page", versions_path(:model => @page.class.to_s, :id => @page.id) %>
    <%= link_to "Versions of this hotel", versions_path(:model => @hotel.class.to_s, :id => @hotel.id) %>
    <%= link_to "Versions of this comment", versions_path(:model => @comment.class.to_s, :id => @comment.id) %>