Может ли мобильный mime-тип вернуться к «html» в Rails? - PullRequest
22 голосов
/ 31 октября 2010

Я использую этот код (взятый из здесь ) в 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, и это здорово, но есть проблема: кажется, я должен определить каждый шаблон для каждого типа пантомимы. Например, запрос действия «show» у iPhone без определения show.mobile.erb вызовет ошибку, даже если определен show.html.erb. Если шаблон для мобильного телефона или планшета отсутствует, я хотел бы просто вернуться к HTML. Кажется, это не слишком далеко, поскольку «mobile» определяется как псевдоним «text / html» в mime_types.rb.

Итак, несколько вопросов:

  1. Я делаю это неправильно? Или есть лучший способ сделать это?
  2. Если нет, могу ли я использовать типы mime для мобильных устройств и планшетов для html, если файл для мобильного телефона или планшета отсутствует?

Если это имеет значение, я использую Rails 3.0.1. Заранее спасибо за любые указатели.

РЕДАКТИРОВАТЬ: Кое-что я забыл упомянуть: в конечном итоге я перейду к отдельным поддоменам (как вы можете видеть закомментировано в моем примере), поэтому загрузка шаблона действительно должна происходить автоматически независимо от того, который before_filter бежал.

Ответы [ 15 ]

17 голосов
/ 06 марта 2012

Возможный дубликат Изменение форматов представления в рельсах 3.1 (доставка мобильных форматов html, откат к обычному html)

Однако я боролся с той же самой проблемой и придумал довольно элегантное решение, которое полностью отвечало моим потребностям. Вот мой ответ из другого поста.

Я думаю, что нашел лучший способ сделать это. Я пытался сделать то же самое, что и вы, но потом вспомнил, что в rails 3.1 введено наследование шаблонов , что именно то, что нам нужно для того, чтобы что-то подобное заработало. Я действительно не могу отдать должное этой реализации, так как все это изложено в этой ссылке на Railscasts Райаном Бейтсом.

Так что в основном так и происходит.

Создать подкаталог в 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

Как только это будет сделано, ваши файлы по умолчанию будут отображаться на мобильных устройствах, если они находятся на мобильном устройстве, и откроются на обычных шаблонах, если они отсутствуют на мобильных устройствах.

4 голосов
/ 06 марта 2011

Вам нужно сделать несколько вещей, чтобы подключить это, но хорошая новость заключается в том, что Rails 3 фактически делает это намного проще, чем раньше, и вы можете позволить маршрутизатору выполнять большую часть тяжелой работы за вас.

Прежде всего, вам нужно составить специальный маршрут, который настроит для вас правильный тип пантомимы:

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

Теперь у вас есть доступ к агенту пользователя iphone, помеченный типом mime iphone. Rails взорвется у вас за отсутствующий тип MIME, поэтому зайдите в config / initializers / mime_types.rb и раскомментируйте iphone:

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

Теперь ваш mime-тип готов к использованию, но ваш контроллер, вероятно, еще не знает о вашем новом mime-типе, и поэтому вы увидите 406 ответов. Чтобы решить эту проблему, просто добавьте разрешение mime-типа в верхней части контроллера, используя repsond_to:

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

Теперь вы можете просто использовать блоки response_to или response_with как обычно.

В настоящее время не существует API для простого выполнения автоматического отката, кроме уже рассмотренных подходов с шаблонами monkeypatch или non-mime. Возможно, вам удастся подключить переопределение более аккуратно, используя специальный класс респондента.

Другие рекомендуемые значения включают в себя:

https://github.com/plataformatec/responders

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

3 голосов
/ 09 марта 2011

Я добавил новый ответ для версии 3.2.X. Этот ответ действителен для <~ 3.0.1. </p>

Я пришел к этому вопросу, пытаясь найти возможностьнесколько откатов на вид.Например, если мой продукт может иметь белую маркировку, и, в свою очередь, если мой партнер по белой марке может продать спонсорство, тогда мне нужен каскад просмотров на каждой странице, подобный этому:

  • Представление спонсора:.sponsor_html
  • Представление партнера: .partner_html
  • Представление по умолчанию: .html

Ответ Джо на простое удаление .html работает (очень хорошо), если вытолько на один уровень выше значения по умолчанию, но в реальных приложениях мне в некоторых случаях требовалось 5 уровней.

Похоже, в любом случае не было возможности реализовать такое короткое исправление обезьяны в том же духе, что и у Джереми.

Ядро Rails делает несколько довольно широких предположений о том, что вам нужен только один формат и что он сопоставляется с одним расширением (по умолчанию используется расширение NO).

Мне нужно было одно решение, котороебудет работать для всех элементов представления - макеты, шаблоны и частичные.

Попытка сделать это больше в соответствии с соглашением, я придумал следующее.

# 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

Примечание: Iдействительно видел комментарии вокруг alias_method_chain, и первоначально сделал вызов super в соответствующем месте.На самом деле это называется ActionView :: Resolver # find_templates (что вызывает исключение NotImplemented), а не ActionView :: PathResolver # find_templates в некоторых случаях.Я не был достаточно терпелив, чтобы отследить почему.Я подозреваю, что это из-за того, что это закрытый метод.

Плюс, Rails в настоящее время не сообщает alias_method_chain как устаревший.Просто этот пост делает.

Мне не нравится этот ответ, поскольку он включает в себя очень хрупкую реализацию вокруг вызова find_templates.В частности, предположение, что у вас есть только ОДИН формат, но это предположение сделано повсеместно в запросе шаблона.

После 4 дней попыток решить эту проблему и прочесать весь стек запроса шаблонаэто лучшее, что я могу придумать.

3 голосов
/ 19 ноября 2010

Попытка удаления файла .html из файла .html.erb и iPhone, и браузера приведет к возврату к общему файлу.

2 голосов
/ 09 марта 2013

Вот более простое решение:

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

Оказывается, Rails (3.2.11) добавляет запасной вариант: html для запросов в формате: js.Вот как это работает:

  • ActionController :: Rendering # process_action назначает массив форматов из запроса (см. Action_controller / metal / render.rb)
  • ActionView :: LookupContext # format =вызывается с результатом

Вот ActionView :: LookupContext # format =,

# 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запрос MIME-типа «mobile» в качестве форматеров [: mobile,: html] - и Rails уже делает это таким образом.

2 голосов
/ 06 марта 2012

Я добавляю еще один ответ сейчас, когда мы обновили до 3.2.X. Оставить старый ответ таким, каким он был на тот момент. Но я отредактирую это, чтобы направить людей к этому для текущих версий.

Существенным отличием здесь является использование «новой» (начиная с версии 3.1) возможности добавления в собственные средства разрешения путей. Что делает код короче, как предложил Йерун. Но взяли немного дальше. В частности, #find_templates больше не является частным, и ожидается, что вы напишите собственный.

# 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
2 голосов
/ 09 февраля 2011

Если в вашей ОС есть символические ссылки, вы можете использовать их.

$ ln -s show.html.erb show.mobile.erb
2 голосов
/ 19 ноября 2010

Способ, которым я работаю с этим, заключается в простом skip_before_filter запросах, для которых я знаю, что хочу визуализировать представления HTML. Очевидно, что это будет работать с частями.

Если на вашем сайте много просмотров с мобильных устройств и / или планшетов, вы, вероятно, захотите установить фильтр на ApplicationController и пропустить их в подклассах, но если только несколько действий имеют специфичные для мобильных устройств представления, вам следует только вызовите фильтр before для тех действий / контроллеров, которые вам нужны.

1 голос
/ 09 апреля 2014

Rails 4.1 включает в себя Варианты , это отличная функция, которая позволяет вам устанавливать различные виды для одного и того же MIME.Теперь вы можете просто добавить 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

Дополнительная информация здесь .

1 голос
/ 23 марта 2011

Вот еще один пример того, как это сделать, вдохновленный кодом Саймона, но немного короче и чуть менее хакерский:

# 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

и где-то в autoload_path или явно требуется:

# 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, но, возможно, менее хрупкое, как первоначальное предложение Саймона.

Обратите внимание, что вы должны заботиться о макете отдельно. Вам нужно будет реализовать метод, который выбирает макет на основе пользовательского агента или чего-то подобного. Он позаботится только о резерве для обычных шаблонов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...