Add has_many and has_and_belongs_to_many associations support.
This commit is contained in:
parent
7ad4b08608
commit
7235f4a177
|
@ -5,12 +5,6 @@
|
|||
* Add support to default label method
|
||||
* :country, :time_zone, :group and :file types support
|
||||
|
||||
== Associations
|
||||
|
||||
* has_many
|
||||
* has_and_belongs_to_many
|
||||
* belongs_to
|
||||
|
||||
== Validations
|
||||
|
||||
* Add automatic :required check
|
||||
|
|
|
@ -35,7 +35,6 @@ module SimpleForm
|
|||
# Generate the input through the mapped option. Apply correct behaviors
|
||||
# for collections and add options whenever the input requires it.
|
||||
def content
|
||||
options[:options] ||= {}
|
||||
mapping = self.class.mappings[input_type]
|
||||
raise "Invalid input type #{input_type.inspect}" unless mapping
|
||||
|
||||
|
@ -56,7 +55,7 @@ module SimpleForm
|
|||
collection = (options[:collection] || self.class.boolean_collection).to_a
|
||||
detect_collection_methods(collection, options)
|
||||
|
||||
options[:include_blank] = true unless options.key?(:include_blank)
|
||||
options[:include_blank] = true unless skip_include_blank?
|
||||
args.push(collection, options[:value_method], options[:label_method])
|
||||
end
|
||||
|
||||
|
@ -77,6 +76,11 @@ module SimpleForm
|
|||
args << html_options
|
||||
end
|
||||
|
||||
def skip_include_blank?
|
||||
input_type != :select || options.key?(:prompt) || options.key?(:include_blank) ||
|
||||
options[:input_html].try(:[], :multiple)
|
||||
end
|
||||
|
||||
# Detect the right method to find the label and value for a collection.
|
||||
# If no label or value method are defined, will attempt to find them based
|
||||
# on default label and value methods that can be configured through
|
||||
|
|
|
@ -110,18 +110,30 @@ module SimpleForm
|
|||
def association(attribute, options={})
|
||||
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
|
||||
|
||||
options[:as] ||= :select
|
||||
@reflection = find_association_reflection(attribute)
|
||||
raise "Association not found #{attribute.inspect}" unless @reflection
|
||||
raise "Association #{attribute.inspect} not found" unless @reflection
|
||||
|
||||
attribute = @reflection.options[:foreign_key] || :"#{@reflection.name}_id"
|
||||
case @reflection.macro
|
||||
when :belongs_to
|
||||
attribute = @reflection.options[:foreign_key] || :"#{@reflection.name}_id"
|
||||
when :has_one
|
||||
raise ":has_one association are not supported by f.association"
|
||||
else
|
||||
attribute = :"#{@reflection.name}_ids"
|
||||
|
||||
if options[:as] == :select
|
||||
html_options = options[:input_html] ||= {}
|
||||
html_options[:size] ||= 5
|
||||
html_options[:multiple] = true unless html_options.key?(:multiple)
|
||||
end
|
||||
end
|
||||
|
||||
options[:collection] ||= begin
|
||||
find_options = options.slice(:conditions, :order)
|
||||
|
||||
klass = Array(options[:scope]).inject(@reflection.klass) do |klass, scope|
|
||||
klass.send(scope)
|
||||
end
|
||||
|
||||
klass.all(find_options)
|
||||
end
|
||||
|
||||
|
|
|
@ -187,12 +187,22 @@ class InputTest < ActionView::TestCase
|
|||
|
||||
test 'input should automatically set include blank' do
|
||||
with_input_for @user, :age, :select, :collection => 18..30
|
||||
assert_select 'select option[value=]', ""
|
||||
assert_select 'select option[value=]', ''
|
||||
end
|
||||
|
||||
test 'input should not set include blank if otherwise is told' do
|
||||
with_input_for @user, :age, :select, :collection => 18..30, :include_blank => false
|
||||
assert_no_select 'select option[value=]', ""
|
||||
assert_no_select 'select option[value=]', ''
|
||||
end
|
||||
|
||||
test 'input should not set include blank if prompt is given' do
|
||||
with_input_for @user, :age, :select, :collection => 18..30, :prompt => "Please select foo"
|
||||
assert_no_select 'select option[value=]', ''
|
||||
end
|
||||
|
||||
test 'input should not set include blank if multiple is given' do
|
||||
with_input_for @user, :age, :select, :collection => 18..30, :input_html => { :multiple => true }
|
||||
assert_no_select 'select option[value=]', ''
|
||||
end
|
||||
|
||||
test 'input should detect label and value on collections' do
|
||||
|
|
|
@ -344,7 +344,8 @@ class FormBuilderTest < ActionView::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
test 'builder should allow creating an association input generating collection' do
|
||||
# ASSOCIATIONS - BELONGS TO
|
||||
test 'builder creates a select for belongs_to associations' do
|
||||
with_association_for @user, :company
|
||||
assert_select 'form select.select#user_company_id'
|
||||
assert_select 'form select option[value=1]', 'Company 1'
|
||||
|
@ -352,6 +353,22 @@ class FormBuilderTest < ActionView::TestCase
|
|||
assert_select 'form select option[value=3]', 'Company 3'
|
||||
end
|
||||
|
||||
test 'builder allows collection radio for belongs_to associations' do
|
||||
with_association_for @user, :company, :as => :radio
|
||||
assert_select 'form input.radio#user_company_id_1'
|
||||
assert_select 'form input.radio#user_company_id_2'
|
||||
assert_select 'form input.radio#user_company_id_3'
|
||||
end
|
||||
|
||||
test 'builder marks the record which already belongs to the user' do
|
||||
@user.company_id = 2
|
||||
with_association_for @user, :company, :as => :radio
|
||||
assert_no_select 'form input.radio#user_company_id_1[checked=checked]'
|
||||
assert_select 'form input.radio#user_company_id_2[checked=checked]'
|
||||
assert_no_select 'form input.radio#user_company_id_3[checked=checked]'
|
||||
end
|
||||
|
||||
# ASSOCIATIONS - FINDERS
|
||||
test 'builder should allow passing conditions to find collection' do
|
||||
with_association_for @user, :company, :conditions => { :id => 1 }
|
||||
assert_select 'form select.select#user_company_id'
|
||||
|
@ -368,7 +385,7 @@ class FormBuilderTest < ActionView::TestCase
|
|||
assert_select 'form select option[value=3]'
|
||||
end
|
||||
|
||||
test 'builder should allow overriding condition to association input' do
|
||||
test 'builder should allow overriding collection to association input' do
|
||||
with_association_for @user, :company, :include_blank => false,
|
||||
:collection => [Company.new(999, 'Teste')]
|
||||
assert_select 'form select.select#user_company_id'
|
||||
|
@ -377,10 +394,32 @@ class FormBuilderTest < ActionView::TestCase
|
|||
assert_select 'form select option', :count => 1
|
||||
end
|
||||
|
||||
test 'builder with association input should allow using radios' do
|
||||
with_association_for @user, :company, :as => :radio
|
||||
assert_select 'form input.radio#user_company_id_1'
|
||||
assert_select 'form input.radio#user_company_id_2'
|
||||
assert_select 'form input.radio#user_company_id_3'
|
||||
# ASSOCIATIONS - has_*
|
||||
test 'builder does not allow has_one associations' do
|
||||
assert_raise RuntimeError do
|
||||
with_association_for @user, :first_company, :as => :radio
|
||||
end
|
||||
end
|
||||
|
||||
test 'builder creates a select with multiple options for collection associations' do
|
||||
with_association_for @user, :tags
|
||||
assert_select 'form select.select#user_tags_ids'
|
||||
assert_select 'form select[multiple=multiple][size=5]'
|
||||
assert_select 'form select option[value=1]', 'Tag 1'
|
||||
assert_select 'form select option[value=2]', 'Tag 2'
|
||||
assert_select 'form select option[value=3]', 'Tag 3'
|
||||
end
|
||||
|
||||
test 'builder allows size to be overwritten for collection associations' do
|
||||
with_association_for @user, :tags, :input_html => { :size => 10 }
|
||||
assert_select 'form select[multiple=multiple][size=10]'
|
||||
end
|
||||
|
||||
test 'builder marks all selected records which already belongs to user' do
|
||||
@user.tags_ids = [1, 2]
|
||||
with_association_for @user, :tags
|
||||
assert_select 'form select option[value=1][selected=selected]'
|
||||
assert_select 'form select option[value=2][selected=selected]'
|
||||
assert_no_select 'form select option[value=3][selected=selected]'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require 'ostruct'
|
||||
|
||||
Column = Struct.new(:name, :type, :limit)
|
||||
Association = Struct.new(:klass, :name, :options)
|
||||
Association = Struct.new(:klass, :name, :macro, :options)
|
||||
|
||||
class Company < Struct.new(:id, :name)
|
||||
def self.all(options={})
|
||||
|
@ -12,13 +12,15 @@ class Company < Struct.new(:id, :name)
|
|||
end
|
||||
end
|
||||
|
||||
class User < OpenStruct
|
||||
attr_reader :id
|
||||
|
||||
def initialize(attributes={})
|
||||
@id = attributes.delete(:id)
|
||||
super
|
||||
class Tag < Struct.new(:id, :name)
|
||||
def self.all(options={})
|
||||
(1..3).map{|i| Tag.new(i, "Tag #{i}")}
|
||||
end
|
||||
end
|
||||
|
||||
class User < OpenStruct
|
||||
# Get rid of deprecation warnings
|
||||
undef_method :id
|
||||
|
||||
def new_record!
|
||||
@new_record = true
|
||||
|
@ -61,7 +63,14 @@ class User < OpenStruct
|
|||
end
|
||||
|
||||
def self.reflect_on_association(association)
|
||||
Association.new(Company, association, {}) if association == :company
|
||||
case association
|
||||
when :company
|
||||
Association.new(Company, association, :belongs_to, {})
|
||||
when :tags
|
||||
Association.new(Tag, association, :has_many, {})
|
||||
when :first_company
|
||||
Association.new(Company, association, :has_one, {})
|
||||
end
|
||||
end
|
||||
|
||||
def errors
|
||||
|
|
Loading…
Reference in New Issue