Can a mobile mime type fall back to “html” in Rails?

后端 未结 15 1195
余生分开走
余生分开走 2020-12-23 22:17

I\'m using this code (taken from here) in ApplicationController to detect iPhone, iPod Touch and iPad requests:

before_filter :detect_mobile_request, :detec         


        
相关标签:
15条回答
  • 2020-12-23 22:32

    I have added a new answer for version 3.2.X. This answer is valid for <~ 3.0.1.

    I came to this question while looking to be able to have multiple fallbacks on the view. For example if my product can be white-labeled and in turn if my white-label partner is able to sell sponsorship, then I need a cascade of views on every page like this:

    • Sponsor View: .sponsor_html
    • Partner View: .partner_html
    • Default View: .html

    The answer by Joe, of just removing .html works (really well) if you only have one level above the default, but in actual application I needed 5 levels in some cases.

    There did not seem to be anyway to implement this short of some monkey patching in the same vein as Jeremy.

    The Rails core makes some fairly wide ranging assumptions that you only want one format and that it maps to a single extension (with the default of NO extension).

    I needed a single solution that would work for all view elements -- layouts, templates, and partials.

    Attempting to make this more along the lines of convention I came up with the following.

    # app/config/initializers/resolver.rb
    module ActionView
      class Base
        cattr_accessor :extension_fallbacks
        @@extension_fallbacks = nil
      end
    
      class PathResolver < Resolver
        private
          def find_templates_with_fallbacks(name, prefix, partial, details)
            fallbacks = Rails.application.config.action_view.extension_fallbacks
            format = details[:formats].first
    
            unless fallbacks && fallbacks[format]
              return find_templates_without_fallbacks(name, prefix, partial, details)
            end
    
            deets = details.dup
            deets[:formats] = fallbacks[format]
    
            path = build_path(name, prefix, partial, deets)
            query(path, EXTENSION_ORDER.map {|ext| deets[ext] }, details[:formats])
          end
          alias_method_chain :find_templates, :fallbacks
      end
    end
    
    # config/application.rb
    config.after_initialize do 
    config.action_view.extension_fallbacks = {
      html: [:sponsor_html, :partner_html, :html],
      mobile: [:sponsor_mobile, :partner_mobile, :sponsor_html, :partner_html, :html]
    }
    
    # config/initializers/mime_types.rb
    register_alias 'text/html', :mobile
    
    # app/controllers/examples_controller.rb
    class ExamplesController
      respond_to :html, :mobile
    
      def index
        @examples = Examples.all
    
        respond_with(@examples)
      end
    end
    

    Note: I did see the comments around alias_method_chain, and initially did make a call to super at the appropriate spot. This actually called ActionView::Resolver#find_templates (which raises a NotImplemented exception) rather than the ActionView::PathResolver#find_templates in some cases. I wasn't patient enough to track down why. I suspect its because of being a private method.

    Plus, Rails, at this time, does not report alias_method_chain as deprecated. Just that post does.

    I do not like this answer as it involves some very brittle implementation around that find_templates call. In particular the assumption that you only have ONE format, but this is an assumption made all over the place in the template request.

    After 4 days of trying to solve this and combing through the whole of the template request stack its the best I can come up with.

    0 讨论(0)
  • 2020-12-23 22:33

    I made a monkey patch for that, but now, I use a better solution :

    In application_controller.rb :

    layout :which_layout
    
    def which_layout
      mobile? ? 'mobile' : 'application'
    end
    

    With the mobile? method you can write.

    So I have a different layout but all the same views, and in the mobile.html.erb layout, I use a different CSS file.

    0 讨论(0)
  • 2020-12-23 22:35

    Yes, I'm pretty sure this is the right way to do this in rails. I've defined iphone formats this way before. That's a good question about getting the format to default back to :html if a template for iphone doesn't exist. It sounds simple enough, but I think you'll have to add in a monkeypath to either rescue the missing template error, or to check if the template exists before rendering. Take a look a the type of patches shown in this question. Something like this would probably do the trick (writing this code in my browser, so more pseudo code) but throw this in an initializer

    # config/initializers/default_html_view.rb
    module ActionView
      class PathSet
    
        def find_template_with_exception_handling(original_template_path, format = nil, html_fallback = true)
          begin
            find_template_without_exception_handling(original_template_path, format, html_fallback)
          rescue ActionView::MissingTemplate => e
            # Template wasn't found
            template_path = original_template_path.sub(/^\//, '')
            # Check to see if the html version exists
            if template = load_path["#{template_path}.#{I18n.locale}.html"]
              # Return html version
              return template
            else
              # The html format doesn't exist either
              raise e
            end
          end
        end
        alias_method_chain :find_template, :exception_handling
    
      end
    end
    
    0 讨论(0)
  • 2020-12-23 22:37

    Here is another example of how to do it, inspired by Simon's code, but a bit shorter and a bit less hacky:

    # application_controller.rb
    class ApplicationController < ActionController::Base
      # ...
    
      # When the format is iphone have it also fallback on :html
      append_view_path ExtensionFallbackResolver.new("app/views", :iphone => :html)
    
      # ...
    end
    

    and somewhere in an autoload_path or explicitly required:

    # extension_fallback_resolver.rb
    class ExtensionFallbackResolver < ActionView::FileSystemResolver
    
      attr_reader :format_fallbacks
    
      # In controller do append_view_path ExtensionFallbackResolver.new("app/views", :iphone => :html)
      def initialize(path, format_fallbacks = {})
        super(path)
        @format_fallbacks = format_fallbacks
      end
    
      private
    
        def find_templates(name, prefix, partial, details)
          fallback_details = details.dup
          fallback_details[:formats] = Array(format_fallbacks[details[:formats].first])
    
          path = build_path(name, prefix, partial, details)
          query(path, EXTENSION_ORDER.map { |ext| fallback_details[ext] }, details[:formats])
        end
    
    end
    

    The above is still a hack because it is using a private API, but possibly less fragile as Simon's original proposal.

    Note that you need to take care of the layout seperately. You will need to implement a method that chooses the layout based on the user agent or something similar. The will only take care of the fallback for the normal templates.

    0 讨论(0)
  • 2020-12-23 22:39

    Possible Duplicate of Changing view formats in rails 3.1 (delivering mobile html formats, fallback on normal html)

    However, I struggled with this exact same problem and came up with a fairly elegant solution that met my needs perfectly. Here is my answer from the other post.

    I think I've found the best way to do this. I was attempting the same thing that you were, but then I remembered that in rails 3.1 introduced template inheritance, which is exactly what we need for something like this to work. I really can't take much credit for this implementation as its all laid out there in that railscasts link by Ryan Bates.

    So this is basically how it goes.

    Create a subdirectory in app/views. I labeled mine mobile.

    Nest all view templates you want to override in the same structure format that they would be in the views directory. views/posts/index.html.erb -> views/mobile/posts/index.html.erb

    Create a before_filter in your Application_Controller and do something to this effect.

     before_filter :prep_mobile
     def is_mobile?
       request.user_agent =~ /Mobile|webOS|iPhone/
     end 
     def prep_mobile
       prepend_view_path "app/views/mobile" if is_mobile?
     end
    

    Once thats done, your files will default to the mobile views if they are on a mobile device and fallback to the regular templates if a mobile one is not present.

    0 讨论(0)
  • 2020-12-23 22:41

    I am adding another answer now that we have updated to 3.2.X. Leaving the old answer as it was in case someone needs that one. But, I will edit it to direct people to this one for current versions.

    The significant difference here is to make use of the "new" (since 3.1) availability of adding in custom path resolvers. Which does make the code shorter, as Jeroen suggested. But taken a little bit further. In particular the #find_templates is no longer private and it is expected that you will write a custom one.

    # lib/fallback_resolver.rb
    class FallbackResolver < ::ActionView::FileSystemResolver
      def initialize(path, fallbacks = nil)
        @fallback_list = fallbacks
        super(path)
      end
    
      def find_templates(name, prefix, partial, details)
        format = details[:formats].first
    
        return super unless @fallback_list && @fallback_list[format]
    
        formats = Array.wrap(@fallback_list[format])
        details_copy = details.dup
        details_copy[:formats] = formats
        path = Path.build(name, prefix, partial)
        query(path, details_copy, formats)
      end
    end
    
    # app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
      append_view_path 'app/views', {
        mobile: [:sponsor_mobile, :mobile, :sponsor_html, :html],
        html: [:sponsor_html, :html]
      }
      respond_to :html, :mobile
    
    # config/initializers/mime_types.rb
    register_alias 'text/html', :mobile
    
    0 讨论(0)
提交回复
热议问题