代码之家  ›  专栏  ›  技术社区  ›  markquezada

在Rails中,移动mime类型可以回到“html”吗?

  •  22
  • markquezada  · 技术社区  · 14 年前

    我正在使用这个代码(取自 here )在ApplicationController中检测iPhone、iPod Touch和iPad请求:

    before_filter :detect_mobile_request, :detect_tablet_request
    
    protected
    
    def detect_mobile_request
      request.format = :mobile if mobile_request?
    end
    
    def mobile_request?
      #request.subdomains.first == 'm'
      request.user_agent =~ /iPhone/ || request.user_agent =~ /iPod/
    end
    
    def detect_tablet_request
      request.format = :tablet if tablet_request?
    end
    
    def tablet_request?
      #request.subdomains.first == 't'
      request.user_agent =~ /iPad/
    end
    

    这允许我使用show.html.erb、show.mobile.erb和show.tablet.erb等模板,这很好,但有一个问题:似乎我必须为每个mime类型定义每个模板。例如,在没有定义show.mobile.erb的情况下从iPhone请求“show”操作,即使定义了show.html.erb,也会抛出错误。如果缺少移动或平板电脑模板,我只想回到html模板。这似乎不太牵强,因为“mobile”在mime_types.rb中被定义为“text/html”的别名。

    1. 我做错了吗?或者,有没有更好的方法?
    2. 如果没有,如果没有移动或平板电脑文件,我可以让移动和平板电脑的mime类型回到html上吗?

    编辑: 我忘了提一件事:我最终将移动到单独的子域(如您在我的示例中看到的注释所示),因此模板加载实际上需要自动进行,而不管 before_filter 已经跑了。

    15 回复  |  直到 14 年前
        1
  •  17
  •   Community CDub    8 年前

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

    然而,我也遇到了同样的问题,并提出了一个相当优雅的解决方案,完全满足了我的需求。这是我从另一个帖子得到的答案。

    template inheritance ,这正是我们需要的东西,像这样的工作。我真的不太相信这个实现,因为它的所有内容都在赖安贝茨的铁路诈骗链接中。

    所以基本上就是这样。

    在中创建子目录 app/views mobile .

    views/posts/index.html.erb -> views/mobile/posts/index.html.erb

    创建 before_filter 在你的 Application_Controller 为此做点什么。

     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
    

    完成后,如果文件位于移动设备上,则默认为移动视图;如果不存在移动视图,则返回常规模板。

        2
  •  4
  •   raggi    14 年前

    你需要做一些事情来连接它,但是好消息是Rails3实际上使它比以前简单得多,你可以让路由器为你做大部分的艰苦工作。

    首先,您需要创建一个特殊的路由,为您设置正确的mime类型:

    # In routes.rb:
    resources :things, :user_agent => /iPhone/, :format => :iphone
    resources :things
    

    Mime::Type.register_alias "text/html", :iphone
    

    现在您的mime类型已经可以使用了,但是您的控制器可能还不知道您的新mime类型,因此您将看到406个响应。要解决此问题,只需在控制器顶部添加一个mime类型允许值,使用repsond_可以:

    class ThingsController < ApplicationController
      respond_to :html, :xml, :iphone
    

    目前除了已经讨论过的monkeypatch或非mime模板方法外,没有任何API可以轻松地执行自动回退。您可以使用专用响应程序类更清晰地连接重写。

    其他推荐阅读包括:

    https://github.com/plataformatec/responders

    http://www.railsdispatch.com/posts/rails-3-makes-life-better

        3
  •  3
  •   Joe    14 年前

    尝试从.html.erb和iPhone和browser中删除.html将返回到公共文件。

        4
  •  3
  •   Simon    13 年前

    此答案对<~3.0.1有效。

    • 赞助人视图:。赞助人html
    • 合作伙伴视图:.Partner_html
    • 默认视图:.html

    Joe的答案是,如果只有一个级别高于默认值,那么只删除.html是可行的,但在实际应用中,在某些情况下我需要5个级别。

    Rails核心做出了一些相当广泛的假设,即您只需要一种格式,并且它映射到一个扩展(默认情况下没有扩展)。

    为了使这更符合惯例,我提出了以下建议。

    # 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
    

    另外,此时Rails不会将alias_method_chain报告为已弃用。只是那篇文章。

    我不知道 这个答案涉及到一些非常脆弱的find_模板调用实现。特别是假设您只有一种格式,但这是模板请求中所有地方的假设。

        5
  •  2
  •   developish    14 年前

    我处理这件事的方式是 skip_before_filter 对于那些我知道我想呈现HTML视图的请求。很明显,这对部分词有用。

    ApplicationController 并在子类中跳过它们,但是如果只有少数操作具有特定于移动的视图,则应该 只有

        6
  •  2
  •   Samuel Danielson    14 年前

    如果你的操作系统有符号链接,你可以使用它们。

    $ ln -s show.html.erb show.mobile.erb
    
        7
  •  2
  •   Anton Simon    12 年前

    现在我添加了另一个答案,我们已经更新到3.2.X。保留原来的答案,以防有人需要那个答案。但是,我将对其进行编辑,以指导人们使用此版本的当前版本。

    # 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
    
        8
  •  2
  •   Will Madden    12 年前

    下面是一个更简单的解决方案:

    class ApplicationController
        ...
        def formats=(values)
            values << :html if values == [:mobile]
            super(values)
        end
        ...
    end
    

    原来Rails(3.2.11)为:js格式的请求添加了:html回退。其工作原理如下:

    • ActionView::LookupContext 35; formats=使用结果调用

    这里是ActionView::LookupContext#formats=,

    # Override formats= to expand ["*/*"] values and automatically
    # add :html as fallback to :js.
    def formats=(values)
      if values
        values.concat(default_formats) if values.delete "*/*"
        values << :html if values == [:js]
      end
      super(values)
    end
    

        9
  •  1
  •   Community CDub    8 年前

    是的,我很确定这是在rails中正确的方法。我以前就这样定义过iphone格式。这是一个很好的问题,如果iphone的模板不存在,那么让格式默认为:html。这听起来很简单,但是我认为您必须添加monkeypath来修复丢失的模板错误,或者在呈现之前检查模板是否存在。查看a中显示的修补程序类型 this question

    # 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
    
        10
  •  1
  •   Jeroen van Dijk Ali    13 年前

    下面是另一个如何做到这一点的例子,灵感来自西蒙的代码,但要短一点,也要少一点老套:

    # 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
    

    在自动加载路径中的某个位置或显式需要:

    # 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
    

    以上仍然是一个黑客,因为它使用的是私有API,但可能不像Simon最初的提议那么脆弱。

    请注意,您需要单独处理布局。您将需要实现一个基于用户代理或类似内容选择布局的方法。将只处理普通模板的回退。

        11
  •  1
  •   S. A.    11 年前

    Rails 4.1 包括 变体 before_action 让变体发挥魔力:

    before_action :detect_device_variant
    
    def detect_device_variant
      case request.user_agent
      when /iPad/i
        request.variant = :tablet
      when /iPhone/i
        request.variant = :phone
      end
    end
    

    那么,在你的行动中:

    respond_to do |format|
      format.json
      format.html               # /app/views/the_controller/the_action.html.erb
      format.html.phone         # /app/views/the_controller/the_action.html+phone.erb
      format.html.tablet do
        @some_tablet_specific_variable = "foo"
      end
    end
    

    here .

        12
  •  0
  •   shingara    14 年前

    在这种情况下,您可以将格式设置为html。举例来说,您希望始终在user show方法中使用html

    class UserController
    
      def show
        ..your_code..
        render :show, :format => :html
      end
    end
    

    如果您也希望通过示例呈现JSON,可以对您的类型进行一些测试,例如:

    class UserController
    
      def show
        ..your_code..
        if [:mobile, :tablet, :html].include?(request.format)
          render :show, :format => :html
        else
          respond_with(@user)
        end
      end
    
    end
    
        13
  •  0
  •   Dorian    13 年前

    monkey patch 为此,但现在,我使用了一个更好的解决方案:

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

    mobile?

    所以我有一个不同的布局,但是所有的视图都是一样的,在mobile.html.erb布局中,我使用了一个不同的CSS文件。

        14
  •  0
  •   Mike P.    12 年前

    https://github.com/rails/rails/issues/3855 并遵循它的线索/gist/gems。

    这就是我在Rails 3.1和引擎上所做的。此解决方案允许您将*.mobile.haml(或*.mobile.erb等)放置在与其他视图文件相同的位置,而不需要两个层次结构(一个用于常规层次结构,另一个用于移动层次结构)。

    在我的“基础”引擎中我添加了这个 config/initializers/resolvers.rb :

        module Resolvers
          # this resolver graciously shared by jdelStrother at
          # https://github.com/rails/rails/issues/3855#issuecomment-5028260
          class MobileFallbackResolver < ::ActionView::FileSystemResolver
            def find_templates(name, prefix, partial, details)
              if details[:formats] == [:mobile]
                # Add a fallback for html, for the case where, eg, 'index.html.haml' exists, but not 'index.mobile.haml'
                details = details.dup
                details[:formats] = [:mobile, :html]
              end
              super
            end
          end
        end
    
        ActiveSupport.on_load(:action_controller) do
          tmp_view_paths = view_paths.dup # avoid endless loop as append_view_path modifies view_paths
          tmp_view_paths.each do |path|
            append_view_path(Resolvers::MobileFallbackResolver.new(path.to_s))
          end
        end
    

        def mobile?
            request.user_agent && request.user_agent.downcase =~ /mobile|iphone|webos|android|blackberry|midp|cldc/ && request.user_agent.downcase !~ /ipad/
        end
    

    还有这个 before_filter :

        before_filter :set_layout
    
        def set_layout
          request.format = :mobile if mobile?
        end
    

    最后,我把这个添加到 config/initializers/mime_types.rb :

        Mime::Type.register_alias "text/html", :mobile
    

    用法

    • app/views/layouts/application.mobile.haml
    • 从任何角度来看 .mobile.haml 而不是 .html.haml

    我甚至可以使用特定的移动布局,如果我在任何控制器中设置它: 布局“移动”

    它将使用 app/views/layouts/mobile.html.haml (甚至 mobile.mobile.haml

        15
  •  -2
  •   Jan    13 年前

    我在应用程序控制器中使用此前置过滤器解决了此问题:

    def set_mobile_format
      request.formats.unshift(Mime::MOBILE) if mobile_client?
    end
    

    这将移动格式放在可接受格式列表的前面。所以,解决者更喜欢 .mobile.erb 模板,但将返回到 .html.erb 如果找不到移动版本。

    #mobile_client? 功能和放置 Mime::Type.register_alias "text/html", :mobile config/initializers/mime_types.rb