Подтвердить что ты не робот

Может ли мобильный тип mime вернуться к "html" в Rails?

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

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

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

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

EDIT: Что-то я забыл упомянуть: в конечном итоге я перейду к отдельным поддоменам (как вы можете видеть в моем примере), поэтому загрузка шаблона действительно должна произойти автоматически, независимо от того, из которых before_filter.

4b9b3361

Ответ 1

Возможный дубликат Изменение форматов форматов в rails 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

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

Ответ 2

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

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

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

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

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

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

Ответ 4

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

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

  • Спонсорский вид:.sponsor_html
  • Partner View:.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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

$ ln -s show.html.erb show.mobile.erb

Ответ 7

Теперь я добавляю еще один ответ, который мы обновили до 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

Ответ 8

Здесь более простое решение:

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

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

  • ActionController:: Rendering # process_action присваивает массив форматов из запроса (см. action_controller/metal/rendering.rb)
  • ActionView:: LookupContext # 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

Это решение является грубым, но я не знаю лучшего способа заставить Rails интерпретировать запрос MIME-типа "mobile" как formatters [: mobile,: html] - и Rails уже делает это таким образом.

Ответ 9

Да, я уверен, что это правильный способ сделать это в рельсах. Я уже определил форматы iphone. Это хороший вопрос о возврате формата к умолчанию: html, если шаблон для iphone не существует. Это звучит достаточно просто, но я думаю, вам придется добавить в путь обезьяны, чтобы либо спасти отсутствующую ошибку шаблона, либо проверить, существует ли шаблон перед рендерингом. Взгляните на тип патчей, показанных в на этот вопрос. Что-то вроде этого, вероятно, сделало бы трюк (написав этот код в моем браузере, поэтому еще псевдо-код), но бросьте это в инициализаторе

# 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

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

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

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

Ответ 11

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

Подробнее здесь.

Ответ 12

В этом случае вы можете использовать формат html. В качестве примера вы хотите всегда использовать html в методе показа пользователей

class UserController

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

В этом случае, если вы запрашиваете показ на User controller, вы все время показываете версию html.

Если вы хотите визуализировать 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

Я сделал патч обезьяны для этого, но теперь я использую лучшее решение:

В application_controller.rb:

layout :which_layout

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

С помощью метода mobile? вы можете написать.

Итак, у меня есть другой макет, но все те же представления, и в макете mobile.html.erb я использую другой файл CSS.

Ответ 14

Мне нужно то же самое. Я исследовал это, включая этот вопрос (и другой подобный), а также выполнил поток рельсов (как упоминалось в этом вопросе) в https://github.com/rails/rails/issues/3855 и следовал за его нитями/gists/драгоценными камнями.

Вот что я делал, работая с 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

Я решил эту проблему, используя этот before_filter в моем ApplicationController:

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

Это переносит формат мобильного телефона в начало списка допустимых форматов. Таким образом, Resolver предпочитает шаблоны .mobile.erb, но возвращается к .html.erb, если мобильная версия не найдена.

Конечно, для этого вам нужно реализовать какую-то функцию #mobile_client? и поместить Mime::Type.register_alias "text/html", :mobile в свой config/initializers/mime_types.rb