是否有可能在Ruby on Rails中编写一个可以用于不同模型的部分内容?

时间:2018-05-03 13:18:49

标签: ruby-on-rails

是否可以创建在语义相似但具有不同属性的表单中使用的部分?

解释这个想法的一个例子。

想象一下,我们有以下模型:

class UnitedStatesTemperature < ApplicationRecord
  # higher_fahrenheit
  # lower_fahrenheit
end

class BrazilTemperature < ApplicationRecord
  # higher_celsius
  # lower_celsius
end

和部分shared/_temperatures.html.erb

<%= form.label :higher_celsius, 'Higher' %>
<%= form.text_field :higher_celsius, id: 
:brazil_temperature_higher_celsius %>

<%= form.label :lower_celsius, 'Lower' %>
<%= form.text_field :lower_celsius, id: 
:brazil_temperature_lower_celsius %>

如果我们将此部分用于brazil_temperature形式,则运行正常

<%= form_with(model: brazil_temperature, local: true) do |form| %>

  <%= render 'shared/temperatures', form: form %>

  <%= form.submit %>
<% end %>

如果我们在united_states_temperature中使用相同的部分它不起作用(因为属性是不同的)。

我的问题是:

是否可以通用方式重写部分(shared/_temperatures.html.erb),以便可以在两种形式(brazil_temperatureunited_states_temperature)中使用?

2 个答案:

答案 0 :(得分:2)

答案

如果您将表单部分传递给属性名称,则可以使其更通用。

<% underscored_klass = form.object.class.name.underscore %>
<% form_id = ->(attr) { "#{underscored_klass}_#{attr}".to_sym } %>

<% # use the two lines below only if you want certain default attributes %>
<% lower_temp_attr = lower_temp_attr || :lower_celsius %>
<% higher_temp_attr = higher_temp_attr || :higher_celsius %>

<%= form.label higher_temp_attr, 'Higher' %>
<%= form.text_field higher_temp_attr, id: form_id[higher_temp_attr] %>

<%= form.label lower_temp_attr, 'Lower' %>
<%= form.text_field lower_temp_attr, id: form_id[lower_temp_attr] %>

然后按如下方式调用partial:

<%= form_with(model: brazil_temperature, local: true) do |form| %>

  <%= render 'shared/temperatures', form: form, lower_temp_attr: :lower_celsius, higher_temp_attr: :higher_celsius %>

  <%= form.submit %>
<% end %>

如果我没弄错,id会自动设置为model_name_attribute_name。这意味着您可以省略<% form_id = ->(attr) { "#{underscored_klass}_#{attr}".to_sym } %>id: form_id[higher_temp_attr]部分来清理代码。

的变化

变体的使用意味着稍微清理视图。这样,在为表单的不同变体呈现部分时,您不必传递lower_temp_attr: :lower_celsius, higher_temp_attr: :higher_celsius

使用助手

您可以创建一个帮助程序,根据对象返回属性名称。

应用程序/助手/ temperature_helper.rb

module TemperatureHelper

  def lower_temp_attr(temp) # variation #1
    attribute_names = temp.attribute_names

    return :lower_celsius if attribute_names.include?('lower_celsius')
    return :lower_fahrenheit if attribute_names.include?('lower_fahrenheit')

    raise ArgumentError,
          'Passed object should have either "lower_celsius" or ' +
          '"lower_fahrenheit" as attribute.'
  end

  def higher_temp_attr(temp) # variation #2
    attribute_names = temp.attribute_names
    options = %w[higher_celsius higher_fahrenheit]

    attr_name = attribute_names.find { |attr_name| options.include?(attr_name) }

    return attr_name.to_sym if attr_name

    raise ArgumentError,
          'Passed object should have either "lower_celsius" or ' +
          '"lower_fahrenheit" as attribute.'
  end

end

然后在您的部分中调用lower_temp_attr(form.object)

使用模型

最后但并非最不重要的是,您可以省略帮助程序,并确保每个温度模型都响应返回属性名称的某个接口方法。

应用程序/模型/ generic_temperature.rb

class GenericTemperature < ApplicationRecord
  self.abstract_class = true

  def lower_temp_attr # variation #1
    return :lower_celsius if attribute_names.include?('lower_celsius')
    return :lower_fahrenheit if attribute_names.include?('lower_fahrenheit')
    raise NotImplementedError
  end

  def higher_temp_attr # variation #2
    options = %w[higher_celsius higher_fahrenheit]
    attr_name = attribute_names.find { |attr_name| options.include?(attr_name) }
    attr_name&.to_sym or raise NotImplementedError
  end
end

让你的温度模型继承自 GenericTemperature

class UnitedStatesTemperature < GenericTemperature
end

并在您的部分内部致电form.object.lower_temp_attr

还有很多方法可以解决这个问题。例如,您也可以选择始终在NotImplementedError中引发GenericTemperature例外,并为每个模型实施该方法。在这种情况下,继承自GenericTemperature除了确保如果未在模型中定义继承的NotImplementedError异常时,不会执行任何操作。 UnitedStatesTemperature

如果由于某种原因你无法继承父模型(也许你将它用于不同的东西),你总是可以将代码放在一个模块中并包含它。

答案 1 :(得分:0)

首先定义一个返回温度标度的类方法

class UnitedStatesTemperature < ApplicationRecord
  # higher_fahrenheit
  # lower_fahrenheit

  def self.scale
    :fahrenheit
  end
end


class BrazilTemperature < ApplicationRecord
  # higher_celsius
  # lower_celsius

  def self.scale
    :celsius
  end
end

然后写下像shared/temperatures

下面的部分内容
<%
  higher_scale = "higher_#{form.object.class.scale}".to_sym
  lower_scale = "lower_#{form.object.class.scale}".to_sym
%>

<%= form.label higher_scale, 'Higher' %>
<%= form.text_field higher_scale, id: 
"#{form.object.class.table_name}_#{higher_scale}" %>

<%# same for lower --%>