乐者为王

Do one thing, and do it well.

Rails 2.3和Paperclip实现多图片上传(改进版)

从Rails 2.3开始,有了新的方法来处理多模型表单,这就是accepts_nested_attributes_for,它允许直接赋值到子对象上,对于标准的属性使用相同的哈希格式。

例如,一个parent有多个children,包含所有类的POST数据将会是下面这样:

1
2
3
4
5
6
7
8
9
10
11
params =>
  action => update
  id => 1
  controller => parents
  parent =>
    first_name => John
    last_name => Doe
    age => 40
    children_attributes =>
      1 => { id => 16, :name => Jack }
      2 => { id => 18, :name => Mary }

注意,那个children_attributes元素在parent的元素中。fields_for会为这些子元素生成必要的POST数据,这些数据会被转换成方便accepts_nested_attributes_for解释的哈希。children_attributes不是数组而是哈希,它的键是一个简单的索引(不是模型的ID),被用来从一个单一的实体聚合属性。

不用ID的理由是简单的:正在被编辑的模型可能不会被保存,发送到了客户端,这时它的ID为nil。因此为每个被显示的children的id关联一个隐藏字段是个好的实践。

下面我们就用accepts_nested_attributes_for来改写上传多图片的代码。

修改albums_controller.rb中的new方法,删除下面的这句代码:

1
1.upto(3) { @album.photos.build }

将album.rb中的

1
2
3
4
5
def photo_attributes=(photo_attributes)
  photo_attributes.each do |attributes|
    photos.build(attributes)
  end
end

代码改成

1
accepts_nested_attributes_for :photos

修改_form.html.erb中的

1
2
3
4
5
6
7
<div id="photos">
  <% if @album.new_record? %>
    <%= render :partial => 'photo', :collection => @album.photos %>
  <% end %>
</div>

<%= link_to_function "Add Photo" do |page| page.insert_html :bottom, :photos, :partial => 'photo', :object => Photo.new end %>

为下面的代码

1
2
3
4
5
6
7
<div id="photos">
  <% if @album.new_record? %>
    <%= render :partial => 'photo', :locals => { :form => f, :photo => @album.photos.build } %>
  <% end %>
</div>

<%= add_object_link("Add Photo", f, @album.photos.build, "photo", "#photos") %>

再将_photo.html.erb中的

1
2
3
4
5
<% fields_for "album[photo_attributes][]", photo do |p| %>
  <%= p.label :photo %><br />
  <%= p.file_field :data, :index => nil %>
  <%= link_to_function "delete", "remove_field($(this), ('.photo'))" %>
<% end %>

改为

1
2
3
4
5
<% form.fields_for :photos, photo, :child_index => (photo.new_record? ? "index_to_replace_with_js" : nil) do |p| %>
  <%= p.label :photo %><br />
  <%= p.file_field :data %>
  <%= link_to_function "delete", "remove_field($(this), ('.photo'))" %>
<% end %>

在albums_helper中添加两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def add_object_link(name, form, object, partial, where)
  html = render(:partial => partial, :locals => { :form => form }, :object => object)
  link_to_function name, %{
    var new_object_id = new Date().getTime();
    var html = jQuery(#{js html}.replace(/index_to_replace_with_js/g, new_object_id)).hide();
    html.appendTo(jQuery("#{where}")).slideDown('slow');
  }
end

def js(data)
  if data.respond_to? :to_json
    data.to_json
  else
    data.inspect.to_json
  end
end

到这里就修改完成了。试试看,是不是和原来的效果一样。

Comments