从共享的局部视图或替代解决方案中删除条件逻辑

Removing conditional logic from a shared partial view or alternative solution

对于当前项目,我在视图之间有重复代码,我不确定重构它的最佳途径。

我似乎可以在多个 .html.erb 文件中使用重复代码,或者我可以将相同的代码放入部分文件中并使用条件语句。我一直听说逻辑应该远离视图。这两种选择似乎都不理想,而且我目前不知道有其他选择。

为了说明我的问题,我创建了一个名为 animals 的简单 rails 应用程序。我搭建了两种模型:一种用于 cat,一种用于 dog。图像显示其相应的属性:

显示@cats@dogs几乎是一样的。 Cats 仅具有 meows 的列,而 Dogs 具有 barks 的列,而 dog 具有附加属性列 plays_catch

假设我们选择通过创建部分共享视图来减少显示猫和狗的重复代码:

#views/shared/_animal.html.erb
<tr>
  <td><%= animal.name %></td>
  <td><%= animal.age %> </td>
  <% if animal.class == Cat %>
    <td><%= animal.meows %> </td>
  <% end %>
  <% if animal.class == Dog %>
    <td><%= animal.barks %> </td>
    <td><%= animal.plays_catch %> </td>
  <% end %>
</tr>

然后渲染@cats = Cat.all:

<%= render partial: "shared/animal", collection: @cats %>

然后渲染@dogs = Dog.all:

<%= render partial: "shared/animal", collection: @dogs %>

显然,对于这个特定的例子做这样的事情会有点矫枉过正,但我​​正在应用它的现实世界项目不会矫枉过正。

总体问题是:如何删除几乎相同的迭代集合的代码,其中唯一的区别是 adding/removing 一列信息?将逻辑放在视图本身中感觉不对,而留下重复感觉不对。

您可以使用 respond_to? 来解决更一般的问题。当它更通用时,视图逻辑不会感觉那么错误。

<% [:meows, :barks, :plays_catch].each do |method| %>
  <% if animal.respond_to?(method) %>
    <td><%= animal.send(method) %> </td>
  <% end %>
<% end %>

我不知道有什么规范的方法可以做到这一点,但我会通过以下方式使用一个 partial

<tr>
  <% animal.attributes.each do |_, value| %>
    <td><%= value %></td>
  <% end %>
</tr>

您可以通过在局部变量中提供具有预先获得的模型属性的局部变量来摆脱重复的 attributes 调用。

编辑:如果你只想显示一些属性。

# Declare whitelist of attributes
# (you can also declare a blacklist and just calculate the difference between two array: all_attributes - blacklist_attributes):
<% whitelist = [:name, :age, :barks] %>

<%= render partial: 'shared/animal',
           collection: @dogs,
           locals: {attrs: (@dogs.first.attributes.keys.map(&:to_sym) & whitelist)} %>

views/shared/_animal.html.erb:

<tr>
  <% attrs.each do |attr| %>
    <td><%= animal[attr] %></td>
  <% end %>
</tr>

您可以使用装饰器并添加 return 额外列的方法:

class DogDecorator < Draper::Decorator
  def extra_columns
    [:barks, plays_catch]
  end
end

class CatDecorator < Draper::Decorator
  def extra_columns
    [:meows]
  end
end

...
<% animal.extra_columns.each do |column| %>
  <td><%= animal.attributes[column.to_s] %>
<% end %>
...
<% @cats = CatDecorator.decorate_collection(Cat.all)
<%= render partial: "shared/animal", collection: @cats %>

您可以向 Cat 和 Dog 添加一个同名的方法 类,这将 return 特定的实例属性名称和值。我建议 returning 两个数组(一个包含字段的名称,另一个包含字段的值,反之亦然),因为散列不是完全有序的。这样您就可以控制它们在视图中的显示顺序。 例如:

#models/cat.rb

def fields_and_attributes
  fields = ["Name","Age","Meows"]
  attributes = [self.name, self.age]
  if self.meows
    attributes.push("Yes")
  else
    attributes.push("No")
  end
  [fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end

#models/dog.rb

def fields_and_attributes
  fields = ["Name","Age","Plays catch"]
  attributes = [self.name, self.age]
  if self.plays_catch
    attributes.push("Yes")
  else
    attributes.push("No")
  end
  [fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end

#controllers/animals_controller.rb

def display_animals
  @animals = Cat.all + Dog.all # an array containing the different animals
end

#views/display_animals.html.erb

for i in (0...@animals.size)
  fields_and_attributes = @animals[i].fields_and_attributes
  for f in (0...fields_and_attributes[0].size)
    <p><%= fields_and_attributes[0][f] %> : <%= fields_and_attributes[1][f] %></p>
  end
end

在这里,我们首先遍历所有动物并调用该特定记录的 .fields_and_attributes 方法;然后我们迭代调用该方法的结果,以与方法中定义的顺序相同的顺序显示字段和属性,并保证代码将显示每个字段和每个属性,而不管字段总数的差异每个不同的动物。

以下是我查看发布的答案后的答案。基本上:

  • 我在每个脚手架模型的索引页中留下了差异
  • 我为公共 table headers 和 table 数据制作了共享部分

代码如下:


#app/views/cats/index.html.erb
<h1>Listing Cats</h1>

<table>
  <thead>
    <tr>
      <%= render partial: "shared/cat_dog_table_headers" %>
      <th>Meows</th>
    </tr>
  </thead>

  <tbody>
    <% @cats.each do |cat| %>
      <tr>
        <%= render partial: "shared/cat_dog_table_data", locals: {animal: cat} %>
        <td><%= cat.meows %></td>

      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Cat', new_cat_path %>

对于狗来说:

#app/views/dogs/index.html.erb
<h1>Listing Dogs</h1>

<table>
  <thead>
    <tr>
      <%= render partial: "shared/cat_dog_table_headers" %>
      <th>Barks</th>
      <th>Plays catch</th>
    </tr>
  </thead>

  <tbody>
    <% @dogs.each do |dog| %>
      <tr>
        <%= render partial: "shared/cat_dog_table_data", locals: {animal: dog} %>
        <td><%= dog.barks %></td>
        <td><%= dog.plays_catch %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Dog', new_dog_path %>

猫狗共享tableheaders:

#app/views/shared/_cat_dog_table_headers
<td><%= Name %></td>
<td><%= Age %></td>

猫和狗的共享 table 数据:

#app/views/shared/_cat_dog_table_data_headers
<td><%= animal.name %></td>
<td><%= animal.age %></td>