Tại sao phần tử đầu tiên luôn trống trong nhiều lựa chọn Rails của tôi, bằng cách sử dụng một mảng được nhúng?


84

Tôi đang sử dụng Rails 3.2.0.rc2 . Tôi có một Model, trong đó tôi có một tĩnh Arraymà tôi đang cung cấp thông qua một biểu mẫu để người dùng có thể chọn một tập hợp con Arrayvà lưu lựa chọn của họ vào cơ sở dữ liệu, được lưu trữ trong một cột duy nhất Model. Tôi đã sử dụng serialize trên cột cơ sở dữ liệu lưu trữ Arrayvà Rails đang chuyển đổi chính xác các lựa chọn của người dùng thành Yaml (và quay lại một mảng khi đọc cột đó). Tôi đang sử dụng đầu vào biểu mẫu nhiều lựa chọn để thực hiện các lựa chọn.

Vấn đề của tôi là, theo cách tôi hiện có, mọi thứ hoạt động như tôi mong đợi ngoại trừ mảng tập hợp con của người dùng luôn có phần tử trống đầu tiên khi nó được gửi đến máy chủ.

Đây không phải là vấn đề lớn và tôi có thể viết mã để loại bỏ điều đó sau khi thực tế, nhưng tôi cảm thấy như tôi chỉ đang mắc một số loại lỗi cú pháp vì dường như tôi không nghĩ rằng hành vi Rails mặc định là cố ý thêm phần tử trống này mà không có lý do. Tôi hẳn đã bỏ lỡ điều gì đó hoặc quên tắt một số loại cài đặt. Vui lòng giúp tôi hiểu những gì tôi đang thiếu (hoặc chỉ cho tôi một số tài liệu tốt mô tả điều này với độ sâu hơn những gì tôi có thể tìm thấy trên các ống).

Các 'mô hình' Bảng Cơ sở dữ liệu MySQL:

  • bao gồm một cột có tên subset_arraylà trường TEXT

Mô hình lớp bao gồm các cài đặt sau:

  • serialize :subset_array
  • ALL_POSSIBLE_VALUES = [value1, value2, value3, ...]

Biểu mẫu để chỉnh sửa Mô hình bao gồm tùy chọn đầu vào sau:

  • f.select :subset_array, Model::ALL_POSSIBLE_VALUES, {}, :multiple => true, :selected => @model.subset_array

PUT đến máy chủ từ máy khách trông giống như sau:

  • giả sử chỉ value1 và value3 được chọn
  • "model" => { "subset_array" => ["", value1, value3] }

Cập nhật cơ sở dữ liệu trông như thế này:

  • UPDATE 'models' SET 'subset_array' = '--- \n- \"\"\n- value1\n- value3\n'

Như bạn có thể thấy, có phần tử thừa, trống, này trong mảng đang được gửi và đặt trong cơ sở dữ liệu. Làm thế nào để tôi thoát khỏi điều đó? Có thông số nào tôi bị thiếu trong f.selectcuộc gọi của mình không?

Cảm ơn rất nhiều đánh giá cao :)

CHỈNH SỬA : Đây là mã HTML được tạo từ f.selectcâu lệnh. Có vẻ như có một đầu vào ẩn đang được tạo ra có thể là nguyên nhân gây ra sự cố của tôi? Tại sao lại ở đó?

<input name="model[subset_array][]" type="hidden" value>
<select id="model_subset_array" multiple="multiple" name="model[subset_array][]" selected="selected">
    <option value="value1" selected="selected">Value1</option>
    <option value="value2">Value2</option>
    <option value="value3" selected="selected">Value3</option>
    <option...>...</option>
</select>

Bạn có thể đăng đoạn mã HTML f.selectđang tạo không? Ngoài ra, hành vi này có xảy ra ngay cả khi tạo hay chỉ là khi cập nhật?
Mike A.

Đã thêm CHỈNH SỬA đánh dấu HTML đầu ra được tạo từf.select
robmclarty

@ mike-một hành vi được xác nhận tương tự cho cả hai tạo và cập nhật
robmclarty

Tôi tự hỏi liệu trình duyệt mà tôi đang sử dụng có thể là một phần của vấn đề: cách nó diễn giải và diễn đạt ý nghĩa của thẻ nhập ẩn có cùng tên với thẻ chọn. Vì vậy, tôi đã thử ứng dụng của mình trong Chrome, Safari, Firefox và Opera và mỗi ứng dụng đều cho kết quả giống nhau.
robmclarty

1
Xin lưu ý, tất cả các giải pháp sử dụng include_hidden: falseđều đi kèm với gotcha. Khi bạn xóa tất cả các giá trị khỏi hộp chọn, thành ngữ model.update(something_params)sẽ không bao gồm trường đó. TL; DR bạn sẽ không thể làm cho trường trống.
Damon Aw

Câu trả lời:


51

Trường ẩn là nguyên nhân gây ra sự cố. Nhưng nó có một lý do chính đáng: khi tất cả các giá trị được bỏ chọn, bạn vẫn nhận được một tham số subset_array. Từ tài liệu Rails (bạn có thể phải cuộn sang bên phải để xem tất cả những thứ này):

  # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
  # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
  # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
  # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
  # any mass-assignment idiom like
  #
  #   @user.update_attributes(params[:user])
  #
  # wouldn't update roles.
  #
  # To prevent this the helper generates an auxiliary hidden field before
  # every multiple select. The hidden field has the same name as multiple select and blank value.
  #
  # This way, the client either sends only the hidden field (representing
  # the deselected multiple select box), or both fields. Since the HTML specification
  # says key/value pairs have to be sent in the same order they appear in the
  # form, and parameters extraction gets the last occurrence of any repeated
  # key in the query string, that works for ordinary forms.

CHỈNH SỬA: Đoạn cuối gợi ý rằng bạn không nên nhìn thấy cái trống trong trường hợp một cái gì đó được chọn, nhưng tôi nghĩ nó đã sai. Người thực hiện cam kết này với Rails (xem https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ) đang cố gắng thực hiện cùng một thủ thuật mà Rails sử dụng cho các hộp kiểm (như đã đề cập ở đây: https://github.com / rails / rails / pull / 1552 ), nhưng tôi không nghĩ rằng nó có thể hoạt động cho một hộp nhiều lựa chọn vì các tham số được gửi qua tạo thành một mảng trong trường hợp này và vì vậy không có giá trị nào bị bỏ qua.

Vì vậy, cảm giác của tôi là đây là một lỗi.


1
Tôi đã tạo một ứng dụng ví dụ để chứng minh vấn đề này trong khi tôi cố gắng tìm hiểu làm thế nào để xử lý nó đúng: P
robmclarty

1
Tôi cảm thấy như lỗi không nhất thiết phải có trong Rails, mà là trong một đặc tả và triển khai không rõ ràng cho chức năng phần tử cụ thể này. Làm thế nào để tác nhân người dùng thể hiện sự thay đổi trạng thái mới trống cho trình xử lý biểu mẫu nếu các phần tử biểu mẫu trống (hoặc không được chọn) được coi là không phải là điều khiển thành công và do đó không được gửi cùng với nội dung biểu mẫu?
robmclarty

Vì vậy, nếu đây là một lỗi, nó có được ghi lại trong trình theo dõi vấn đề Rails không?
bmihelac

69

Trong Rails 4:

Bạn sẽ có thể vượt qua :include_hiddentùy chọn. https://github.com/rails/rails/pull/5414/files

Như một bản sửa lỗi nhanh hiện tại: bạn có thể sử dụng ngay bây giờ trong mô hình của mình:

before_validation do |model|
  model.subset_array.reject!(&:blank?) if model.subset_array
end

Điều này sẽ chỉ xóa tất cả các giá trị trống ở cấp mô hình.


Cảm ơn Bogdan. Tôi nghĩ đây là loại điều tôi sẽ triển khai trong ứng dụng của mình để giải quyết vấn đề. Điều này dễ thực hiện hơn rất nhiều so với việc cố gắng giải quyết các vấn đề với việc triển khai thông số kỹ thuật HTML của tác nhân người dùng hoặc thứ gì đó.
robmclarty

Mang mối quan tâm của bạn đến các thành viên cốt lõi trong nhóm. Họ được ủy quyền để xem xét và chấp nhận các bản vá và chịu trách nhiệm về các vấn đề do hậu quả.
Bogdan Gusiev

Bogdan, có cách nào để tắt trường ẩn không, nếu bội số là true? Điều này làm hỏng khá nhiều thứ trong ứng dụng của tôi khi nâng cấp lên 3.2. Tôi thực sự không thích việc tôi phải dọn dẹp những thứ trong bộ điều khiển vì phép thuật Rails thêm các giá trị trống.
lạng lách

Cập nhật câu trả lời của tôi với thông tin upcomming từ Rails 4
Bogdan Gusiev

2
@Donato bạn phải đặt include_hiised thành false (include_hiised: false)
Florian Widtmann

14

Trong bộ Rails 4+: include_hiised on select_tag thành false

<%= form.grouped_collection_select :employee_id, Company.all, :employees, :name, :id, :name, { include_hidden: false }, { size: 6, multiple: true } %>

Đây là câu trả lời đơn giản nhất! Cảm ơn!
William Hampshire

11

Một cách khắc phục nhanh chóng khác là sử dụng bộ lọc bộ điều khiển này:

def clean_select_multiple_params hash = params
  hash.each do |k, v|
    case v
    when Array then v.reject!(&:blank?)
    when Hash then clean_select_multiple_params(v)
    end
  end
end

Cách này có thể được sử dụng lại trên các bộ điều khiển mà không cần chạm vào lớp mô hình.


cảm ơn. Tôi đang thêm điều này vào túi thủ thuật của mình trong trường hợp tôi không muốn làm điều đó trong mô hình;)
robmclarty

5

http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-check_box

Gotcha

Đặc tả HTML cho biết các hộp kiểm bỏ chọn hoặc các lựa chọn không thành công và do đó trình duyệt web không gửi chúng. Thật không may, điều này giới thiệu một gotcha: nếu mô hình Hóa đơn có cờ trả tiền và trong biểu mẫu chỉnh sửa hóa đơn đã trả tiền, người dùng bỏ chọn hộp kiểm của nó, sẽ không có thông số trả tiền nào được gửi. Vì vậy, bất kỳ thành ngữ phân công hàng loạt nào như

@ bill.update (params [: Chemicals]) sẽ không cập nhật cờ.

Để ngăn chặn điều này, trình trợ giúp tạo một trường ẩn phụ trước hộp kiểm. Trường ẩn có cùng tên và các thuộc tính của nó bắt chước một hộp kiểm không được chọn.

Bằng cách này, máy khách chỉ gửi trường ẩn (đại diện cho hộp kiểm không được chọn) hoặc cả hai trường. Vì đặc tả HTML cho biết các cặp khóa / giá trị phải được gửi theo cùng một thứ tự mà chúng xuất hiện trong biểu mẫu và việc trích xuất tham số nhận được lần xuất hiện cuối cùng của bất kỳ khóa lặp lại nào trong chuỗi truy vấn, điều này hoạt động đối với các biểu mẫu thông thường.

Để xóa các giá trị trống:

  def myfield=(value)
    value.reject!(&:blank?)
    write_attribute(:myfield, value)
  end

3

Trong bộ điều khiển:

arr = arr.delete_if { |x| x.empty? }

0

Tôi đã sửa nó bằng cách sử dụng params[:review][:staff_ids].delete("")trong bộ điều khiển trước khi cập nhật.

Theo quan điểm của tôi:

= form_for @review do |f|
  = f.collection_select :staff_ids, @business.staff, :id, :full_name, {}, {multiple:true}
= f.submit 'Submit Review'

Trong bộ điều khiển của tôi:

class ReviewsController < ApplicationController
  def create
  ....
    params[:review][:staff_ids].delete("")
    @review.update_attribute(:staff_ids, params[:review][:staff_ids].join(","))
  ....
  end
end

0

Tôi làm cho nó hoạt động bằng cách viết điều này trong phần Javascript của trang:

$("#model_subset_array").val( <%= @model.subset_array %> );

Của tôi trông giống như sau:

$("#modela_modelb_ids").val( <%= @modela.modelb_ids %> );

Không chắc điều này có làm tôi đau đầu trong tương lai hay không nhưng bây giờ nó hoạt động tốt.


-3

Sử dụng jQuery:

$('select option:empty').remove(); 

Tùy chọn để xóa các tùy chọn trống từ trình đơn thả xuống.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.