在 rails 3.1 中更改视图格式(提供移动 html 格式,回退到普通 html)

Posted

技术标签:

【中文标题】在 rails 3.1 中更改视图格式(提供移动 html 格式,回退到普通 html)【英文标题】:Changing view formats in rails 3.1 (delivering mobile html formats, fallback on normal html) 【发布时间】:2012-02-10 09:43:39 【问题描述】:

我正在我们的普通 html 网站旁边创建一个移动网站。使用导轨 3.1。移动站点在子域 m.site.com 中访问。

我已经定义了移动格式(Mime::Type.register_alias "text/html", :mobile)。

在 ApplicationController 中,我有“before_filter :mobile_site_before_filter”,它可以识别移动站点并根据它设置格式。

def mobile_site_before_filter
  if request.subdomains.first == 'm'
    original_format = request.format
    request.format = :mobile
    @mobile_site = true
    request.formats.push(original_format)
  end
end

在布局中,我有“main.html.erb”和“main.mobile.erb”。因此,移动网站使用移动布局。

现在它可以正常工作了。

在 UserController 我有自动选择 index.html.erb 或 index.mobile.erb 的索引操作。不需要在移动视图顶部进行额外编码。成功。

但我有很多其他视图,其中相同的模板可用于在移动布局中提供服务,但只需稍作改动。

例如在 MessagesController 中,相同的视图几乎适用于移动设备

In index.html.erb
Normal user info, common for mobile and html
<%= render(:partial => 'messages') %>

渲染messages.html.erb,不需要messages.mobile.erb。移动视图可以用 css 完成

<%# By default self.formats = [:html] because this is .html.erb partial, even in mobile site %>
<%= self.formats = [:mobile, :html] if @mobile_site # How to get rid of this? %>
<%= render(:partial => 'vote_form') %>
<!-- rest of page ... -->

现在我想根据网站呈现 vote_form.[mobile|html].erb...

现在,如果我可以在 vote_form.html.erb 或 vote_form.mobile.erb 之间进行选择,那么 index.html.erb 部分就可以在移动设备上使用。我可以choose to use mobile partial 使用 "self.formats = [:mobile, :html] if @mobile_site" 在 index.html.erb 的开头。但是在所有模板的开头写这个感觉很愚蠢。

所以问题是:

self.formats 在视图中的位置(最初设置它的原因)以及如何设置它始终是移动网站内的 [:mobile, :html]?为什么它和我在控制器 before_filter 中设置的不一样?我可以在控制器中以某种方式进行设置吗?

(小问题)

这种方法有问题吗?使用几乎相同的 html 视图,在某些特定情况下使用 mobile.erb 视图。为什么默认情况下这在 Rails 中不起作用?

如果发现 :mobile 视图可以退回到正常的 html 视图,是否还有其他方法可以选择呈现?甚至跨越部分,以便 html.erb-view 尝试使用 _partial.mobile.erb 部分。

【问题讨论】:

【参考方案1】:

您为什么不查看Responsive Web Design,而是使用 CSS 和媒体查询来呈现页面。这样你的控制器就不需要知道视图是否是移动的。

【讨论】:

响应式网页设计占有一席之地,但它并不总是能够为移动设备定制界面的替代方案。【参考方案2】:

您可以在 mime 类型初始化程序中为整个应用程序注册新格式:

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

现在您可以在模板中执行类似的操作来指定格式优先级(请参阅How do I render a partial of a different format in Rails?):

<% self.formats = [:mobile, :html] %>

在这种情况下,如果有移动模板,它将用于渲染,并回退到普通的 html 模板。现在您应该只确定用户是否正在通过移动浏览器浏览并有条件地执行此代码。或者您可以在 ApplicationController 中分配格式值过滤器,以便自动选择正确的模板。

更新:

现在似乎没有“合法”的方法可以使用 Rails 解决这个问题。这是 rails 存储库中的unclosed issue。在那里你可以找到可以解决你问题的补丁,但它使用私有 Rails API,所以它可能不稳定。

您也可以尝试实现自己的视图解析器,可能可以解决问题:http://jkfill.com/2011/03/11/implementing-a-rails-3-view-resolver/

【讨论】:

是的,这就是我所做的。但更成问题的问题是如何在控制器 before_filter 中设置它的应用范围。这样即使你渲染 .html.erb 模板。在模板内部,如果存在,它将选择 .mobile.erb 部分(在所有模板的开头不使用 self.formats) 该死,差点错过你的更新。这是正确的。感谢您找到问题和链接。很高兴知道。虽然努力/价值明智我可能会坚持 在所有模板的开头。以防其他人对编码感到好奇。问题中的猴子补丁似乎没问题。最初是它的 PathResolver 设置视图中/视图的格式 (github.com/rails/rails/blob/…)【参考方案3】:

我想我已经找到了最好的方法。我正在尝试和你一样的东西,但后来我记得在 rails 3.1 中引入了template inheritance,这正是我们需要这样的东西才能工作。我真的不能对这个实现给予太多赞誉,因为它全部都在 Ryan Bates 的 railscasts 链接中列出。

所以基本上就是这样。

app/views 中创建一个子目录。我标记了我的mobile

以与视图目录中相同的结构格式嵌套您要覆盖的所有视图模板。 views/posts/index.html.erb -&gt; views/mobile/posts/index.html.erb

在您的Application_Controller 中创建一个before_filter 并为此做一些事情。

 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

完成后,如果您的文件在移动设备上,它们将默认使用移动视图,如果移动设备不存在,则回退到常规模板。

【讨论】:

酷。如果您有很多文件,这可能是最好的方法。只有几个文件,文件位于不同的文件夹中有点烦人。可能会更难查看使用了哪些部分。 如果您有兴趣,我创建了a gem 来实现此解决方案(以及添加更多功能)。我还在my blog 上写了一些关于该策略的更多信息【参考方案4】:

如何在移动网站内设置它始终为 [:mobile, :html]?我可以在控制器中以某种方式设置它吗?

是的。

这是一个简单的解决方案,但它有点恶心。

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

事实证明,Rails (3.2.11) 已经为 :js 格式的请求添加了一个 :html 后备。这里是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

因此您可以自己覆盖格式,并且可以想象它不会比现有的 Rails 实现更加粗俗和笨拙。

self.formats 在视图中的位置(最初是什么设置)

ActionController::Rendering#process_action 从请求中分配格式数组(参见action_controller/metal/rendering.rb

# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
  self.formats = request.formats.map  |x| x.ref 
  super
end

为什么和我在控制器 before_filter 中设置的不一样?

这与您在 before_filter 中设置的不同,因为在运行过滤器之前 before #process_action(如您所料)。因此,无论您设置什么,都会被 #process_action 拉出请求的内容所破坏。

【讨论】:

【参考方案5】:

Rails 4.1 包含一个非常简洁的功能:

变体

允许您对相同的 mime 类型(例如 HTML)拥有不同的模板和操作响应。对于任何为移动客户端提供服务的 Rails 应用程序来说,这都是灵丹妙药。您现在可以为桌面、平板电脑和手机视图创建单独的模板,同时共享所有相同的控制器逻辑。

现在你可以这样做了:

class PostController < ApplicationController
  def show
    @post = Post.find(params[:id])

    respond_to do |format|
      format.json
      format.html               # /app/views/posts/show.html.erb
      format.html.phone         # /app/views/posts/show.html+phone.erb
      format.html.tablet do
        @show_edit_link = false
      end
    end
  end
end

您只需根据需要设置变体,例如在before_filter 中:

class ApplicationController < ActionController::Base
  before_action :detect_device_variant

  private

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

What's new in Rails 4.1

【讨论】:

以上是关于在 rails 3.1 中更改视图格式(提供移动 html 格式,回退到普通 html)的主要内容,如果未能解决你的问题,请参考以下文章

在 Rails 3.1 中为 Mailer 和 View 提供自定义助手

Rails 3.1:视图不在命名空间布局中呈现

CoffeeScript 未在 Rails 3.1 中更新

mass_assignment_authorizer 的更改导致 Ruby on Rails 3.1 中的错误

如何根据域名更改视图格式

如何在rails的js中更改视图变量