Add has_many and has_and_belongs_to_many associations support.

This commit is contained in:
José Valim 2009-12-11 10:43:43 -02:00
parent 7ad4b08608
commit 7235f4a177
6 changed files with 97 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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