selectable_attr_rails 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ require 'selectable_attr_rails/helpers/abstract_selection_helper'
2
+ module SelectableAttrRails::Helpers
3
+ module CheckBoxGroupHelper
4
+ class Builder < SelectableAttrRails::Helpers::AbstractSelectionBuilder
5
+
6
+ def initialize(object, object_name, method, options, template)
7
+ super(object, object_name, method, options, template)
8
+ @entry_hash_array ||= enum_hash_array_from_object
9
+ @param_name = "#{@base_name}_ids"
10
+ @check_box_options = @options.delete(:check_box) || {}
11
+ end
12
+
13
+ def each(&block)
14
+ @entry_hash_array.each do |@entry_hash|
15
+ @tag_value = @entry_hash[:id].to_s.gsub(/\s/, "_").gsub(/\W/, "")
16
+ @check_box_id = "#{@object_name}_#{@param_name}_#{@tag_value}"
17
+ yield(self)
18
+ end
19
+ end
20
+
21
+ def check_box(options = nil)
22
+ options = update_options({
23
+ :id => @check_box_id, :type => 'checkbox', :value => @tag_value,
24
+ :name => "#{@object_name}[#{@param_name}][]"
25
+ }, @check_box_options, options)
26
+ options[:checked] = 'checked' if @entry_hash[:select]
27
+ @template.content_tag("input", nil, options)
28
+ end
29
+
30
+ def label(text = nil, options = nil)
31
+ @template.content_tag("label", text || @entry_hash[:name],
32
+ update_options({:for => @check_box_id}, options))
33
+ end
34
+ end
35
+
36
+ module Base
37
+ def check_box_group(object_name, method, options = nil, &block)
38
+ object = (options || {})[:object] || instance_variable_get("@#{object_name}")
39
+ builder = Builder.new(object, object_name, method, options, @template)
40
+ if block_given?
41
+ yield(builder)
42
+ return nil
43
+ else
44
+ result = ''
45
+ builder.each do
46
+ result << builder.check_box
47
+ result << '&nbsp;'
48
+ result << builder.label
49
+ result << '&nbsp;'
50
+ end
51
+ return result
52
+ end
53
+ end
54
+ end
55
+
56
+ module FormBuilder
57
+ def check_box_group(method, options = nil, &block)
58
+ @template.check_box_group(@object_name, method,
59
+ (options || {}).merge(:object => @object), &block)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,55 @@
1
+ module SelectableAttrRails::Helpers
2
+ module RadioButtonGroupHelper
3
+ class Builder < SelectableAttrRails::Helpers::AbstractSelectionBuilder
4
+
5
+ def initialize(object, object_name, method, options, template)
6
+ super(object, object_name, method, options, template)
7
+ @entry_hash_array ||= enum_hash_array_from_class
8
+ end
9
+
10
+ def each(&block)
11
+ @entry_hash_array.each do |entry_hash|
12
+ @entry_hash = entry_hash
13
+ tag_value = @entry_hash[:id].to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
14
+ @radio_button_id = "#{@object_name}_#{@method}_#{tag_value}"
15
+ yield(self)
16
+ end
17
+ end
18
+
19
+ def radio_button(options = nil)
20
+ @template.radio_button(@object_name, @method, @entry_hash[:id],
21
+ update_options({:id => @radio_button_id}, options))
22
+ end
23
+
24
+ def label(text = nil, options = nil)
25
+ @template.content_tag("label", text || @entry_hash[:name],
26
+ update_options({:for => @radio_button_id}, options))
27
+ end
28
+ end
29
+
30
+ module Base
31
+ def radio_button_group(object_name, method, options = nil, &block)
32
+ object = (options || {})[:object] || instance_variable_get("@#{object_name}")
33
+ builder = Builder.new(object, object_name, method, options, self)
34
+ if block_given?
35
+ yield(builder)
36
+ return nil
37
+ else
38
+ result = ''
39
+ builder.each do
40
+ result << builder.radio_button
41
+ result << builder.label
42
+ end
43
+ return result
44
+ end
45
+ end
46
+ end
47
+
48
+ module FormBuilder
49
+ def radio_button_group(method, options = nil, &block)
50
+ @template.radio_button_group(@object_name, method,
51
+ (options || {}).merge(:object => @object), &block)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,72 @@
1
+ module SelectableAttrRails::Helpers
2
+ module SelectHelper
3
+ module Base
4
+ def self.included(base)
5
+ base.module_eval do
6
+ alias_method_chain :select, :attr_enumeable
7
+ end
8
+ end
9
+
10
+ # def select_with_attr_enumeable(object, method, choices, options = {}, html_options = {})
11
+ def select_with_attr_enumeable(object_name, method, *args, &block)
12
+ if args.length > 3
13
+ raise ArgumentError, "argument must be " <<
14
+ "(object, method, choices, options = {}, html_options = {}) or " <<
15
+ "(object, method, options = {}, html_options = {})"
16
+ end
17
+ return select_without_attr_enumeable(object_name, method, *args, &block) if args.length == 3
18
+ return select_without_attr_enumeable(object_name, method, *args, &block) if args.first.is_a?(Array)
19
+ options, html_options = *args
20
+ options = update_enum_select_options(options, object_name, method)
21
+ object, base_name = options[:object], options[:base_name]
22
+ return multi_enum_select(object_name, method, options, html_options, &block) if object.respond_to?("#{base_name}_hash_array")
23
+ return single_enum_select(object_name, method, options, html_options, &block) if object.class.respond_to?("#{base_name}_hash_array")
24
+ raise ArgumentError, "invaliad argument"
25
+ end
26
+
27
+ def single_enum_select(object_name, method, options = {}, html_options = {}, &block)
28
+ options = update_enum_select_options(options, object_name, method)
29
+ object = options.delete(:object)
30
+ base_name = options.delete(:base_name)
31
+ entry_hash_array = options.delete(:entry_hash_array) || object.class.send("#{base_name}_hash_array")
32
+ container = entry_hash_array.map{|hash| [hash[:name].to_s, hash[:id]]}
33
+ select_without_attr_enumeable(object_name, method, container, options, html_options || {}, &block)
34
+ end
35
+
36
+ def multi_enum_select(object_name, method, options = {}, html_options = {}, &block)
37
+ html_options = {:size => 5, :multiple => 'multiple'}.update(html_options || {})
38
+ options = update_enum_select_options(options, object_name, method)
39
+ object = options.delete(:object)
40
+ base_name = options.delete(:base_name)
41
+ entry_hash_array = options.delete(:entry_hash_array) || object.send("#{base_name}_hash_array")
42
+ container = entry_hash_array.map{|hash| [hash[:name].to_s, hash[:id].to_s]}
43
+ attr = "#{base_name}_ids"
44
+ select_without_attr_enumeable(object_name, attr, container, options, html_options, &block)
45
+ end
46
+
47
+ def update_enum_select_options(options, object_name, method)
48
+ options ||= {}
49
+ object = (options[:object] ||= instance_variable_get("@#{object_name}"))
50
+ options[:base_name] ||= object.class.enum_base_name(method.to_s)
51
+ options
52
+ end
53
+ end
54
+
55
+ module FormBuilder
56
+ def self.included(base)
57
+ base.module_eval do
58
+ alias_method_chain :select, :attr_enumeable
59
+ end
60
+ end
61
+
62
+ # def select_with_attr_enumeable(method, choices, options = {}, html_options = {}, &block)
63
+ def select_with_attr_enumeable(method, *args, &block)
64
+ options = args.first.is_a?(Array) ? (args[1] ||= {}) : (args[0] ||= {})
65
+ object = (options || {}).delete(:object) || @object || instance_variable_get(@object_name) rescue nil
66
+ options.update({:object => object})
67
+ options[:selected] = object.send(method) if object
68
+ @template.select(@object_name, method, *args, &block)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ module SelectableAttrRails
2
+ module Helpers
3
+ autoload :SelectHelper, 'selectable_attr_rails/helpers/select_helper'
4
+ autoload :CheckBoxGroupHelper, 'selectable_attr_rails/helpers/check_box_group_helper'
5
+ autoload :RadioButtonGroupHelper, 'selectable_attr_rails/helpers/radio_button_group_helper'
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ require 'selectable_attr_rails/validatable'
2
+
3
+ module SelectableAttrRails
4
+ module Validatable
5
+ module Base
6
+ def self.included(mod)
7
+ mod.extend(ClassMethods)
8
+ mod.instance_eval do
9
+ alias :define_enum_without_validatable :define_enum
10
+ alias :define_enum :define_enum_with_validatable
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def define_enum_with_validatable(context)
16
+ enum = context[:enum]
17
+ if options = enum.validates_format_options
18
+ options[:with] = Regexp.union(*enum.entries.map{|entry| /#{Regexp.escape(entry.id)}/})
19
+ entry_format = options.delete(:entry_format) || '#{entry.name}'
20
+ entries = enum.entries.map{|entry| instance_eval("\"#{entry_format}\"")}.join(', ')
21
+ message = options.delete(:message) || 'is invalid, must be one of #{entries}'
22
+ options[:message] = instance_eval("\"#{message}\"")
23
+ validates_format_of(context[:attr], options)
24
+ end
25
+ end
26
+ end
27
+
28
+
29
+ end
30
+ end
31
+ end
32
+
33
+
@@ -0,0 +1,16 @@
1
+ require 'selectable_attr_rails/validatable'
2
+
3
+ module SelectableAttrRails
4
+ module Validatable
5
+ module Enum
6
+ def validates_format_options
7
+ @validates_format_options
8
+ end
9
+ def validates_format(options = nil)
10
+ return @validates_format_options = nil if options == false
11
+ @validates_format_options = options || {}
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require 'selectable_attr_rails'
2
+
3
+ module SelectableAttrRails
4
+ module Validatable
5
+ autoload :Base, 'selectable_attr_rails/validatable/base'
6
+ autoload :Enum, 'selectable_attr_rails/validatable/enum'
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module SelectableAttrRails
2
+ VERSION = '0.0.3'
3
+ end
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'selectable_attr'
3
+
4
+ module SelectableAttrRails
5
+ autoload :Helpers, 'selectable_attr_rails/helpers'
6
+ autoload :DbLoadable, 'selectable_attr_rails/db_loadable'
7
+ autoload :Validatable, 'selectable_attr_rails/validatable'
8
+
9
+ class << self
10
+
11
+ def add_features_to_active_record
12
+ puts "SelectableAttrRails.add_features_to_active_record"
13
+ ActiveRecord::Base.module_eval do
14
+ include ::SelectableAttr::Base
15
+ include ::SelectableAttrRails::Validatable::Base
16
+ end
17
+ SelectableAttr::Enum.module_eval do
18
+ include ::SelectableAttrRails::DbLoadable
19
+ include ::SelectableAttrRails::Validatable::Enum
20
+ end
21
+ end
22
+
23
+ def add_features_to_action_view
24
+ ActionView::Base.module_eval do
25
+ include ::SelectableAttrRails::Helpers::SelectHelper::Base
26
+ include ::SelectableAttrRails::Helpers::CheckBoxGroupHelper::Base
27
+ include ::SelectableAttrRails::Helpers::RadioButtonGroupHelper::Base
28
+ end
29
+ ActionView::Helpers::FormBuilder.module_eval do
30
+ include ::SelectableAttrRails::Helpers::SelectHelper::FormBuilder
31
+ include ::SelectableAttrRails::Helpers::CheckBoxGroupHelper::FormBuilder
32
+ include ::SelectableAttrRails::Helpers::RadioButtonGroupHelper::FormBuilder
33
+ end
34
+ end
35
+
36
+ def add_features_to_rails
37
+ puts "SelectableAttrRails.add_features_to_rails"
38
+ add_features_to_active_record
39
+ add_features_to_action_view
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,74 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{selectable_attr_rails}
5
+ s.version = "0.3.7"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Takeshi Akima"]
9
+ s.date = %q{2009-08-17}
10
+ s.description = %q{selectable_attr_rails makes possible to use selectable_attr in rails application}
11
+ s.email = %q{akima@gmail.com}
12
+ s.extra_rdoc_files = [
13
+ "README"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "MIT-LICENSE",
18
+ "README",
19
+ "Rakefile",
20
+ "VERSION.yml",
21
+ "init.rb",
22
+ "install.rb",
23
+ "lib/selectable_attr_i18n.rb",
24
+ "lib/selectable_attr_rails.rb",
25
+ "lib/selectable_attr_rails/db_loadable.rb",
26
+ "lib/selectable_attr_rails/helpers.rb",
27
+ "lib/selectable_attr_rails/helpers/abstract_selection_helper.rb",
28
+ "lib/selectable_attr_rails/helpers/check_box_group_helper.rb",
29
+ "lib/selectable_attr_rails/helpers/radio_button_group_helper.rb",
30
+ "lib/selectable_attr_rails/helpers/select_helper.rb",
31
+ "lib/selectable_attr_rails/version.rb",
32
+ "selectable_attr_rails.gemspec",
33
+ "spec/database.yml",
34
+ "spec/fixtures/.gitignore",
35
+ "spec/introduction_spec.rb",
36
+ "spec/schema.rb",
37
+ "spec/selectable_attr_i18n_spec.rb",
38
+ "spec/spec_helper.rb",
39
+ "uninstall.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/akm/selectable_attr_rails/}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.4}
45
+ s.summary = %q{selectable_attr_rails makes possible to use selectable_attr in rails application}
46
+ s.test_files = [
47
+ "spec/introduction_spec.rb",
48
+ "spec/schema.rb",
49
+ "spec/selectable_attr_i18n_spec.rb",
50
+ "spec/spec_helper.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.0.2"])
59
+ s.add_runtime_dependency(%q<activerecord>, [">= 2.0.2"])
60
+ s.add_runtime_dependency(%q<actionpack>, [">= 2.0.2"])
61
+ s.add_runtime_dependency(%q<akm-selectable_attr>, [">= 0.3.5"])
62
+ else
63
+ s.add_dependency(%q<activesupport>, [">= 2.0.2"])
64
+ s.add_dependency(%q<activerecord>, [">= 2.0.2"])
65
+ s.add_dependency(%q<actionpack>, [">= 2.0.2"])
66
+ s.add_dependency(%q<akm-selectable_attr>, [">= 0.3.5"])
67
+ end
68
+ else
69
+ s.add_dependency(%q<activesupport>, [">= 2.0.2"])
70
+ s.add_dependency(%q<activerecord>, [">= 2.0.2"])
71
+ s.add_dependency(%q<actionpack>, [">= 2.0.2"])
72
+ s.add_dependency(%q<akm-selectable_attr>, [">= 0.3.5"])
73
+ end
74
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,6 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: selectable_attr_test.sqlite.db
4
+ sqlite3:
5
+ :adapter: sqlite3
6
+ :dbfile: selectable_attr_test.sqlite3.db
File without changes
@@ -0,0 +1,397 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+
4
+ describe SelectableAttr do
5
+
6
+ def assert_product_discount(klass)
7
+ # productsテーブルのデータから安売り用の価格は
8
+ # product_type_cd毎に決められた割合をpriceにかけて求めます。
9
+ p1 = klass.new(:name => '実践Rails', :product_type_cd => '01', :price => 3000)
10
+ p1.discount_price.should == 2400
11
+ p2 = klass.new(:name => '薔薇の名前', :product_type_cd => '02', :price => 1500)
12
+ p2.discount_price.should == 300
13
+ p3 = klass.new(:name => '未来派野郎', :product_type_cd => '03', :price => 3000)
14
+ p3.discount_price.should == 1500
15
+ end
16
+
17
+ # 定数をガンガン定義した場合
18
+ # 大文字が多くて読みにくいし、関連するデータ(ここではDISCOUNT)が増える毎に定数も増えていきます。
19
+ class LegacyProduct1 < ActiveRecord::Base
20
+ set_table_name 'products'
21
+
22
+ PRODUCT_TYPE_BOOK = '01'
23
+ PRODUCT_TYPE_DVD = '02'
24
+ PRODUCT_TYPE_CD = '03'
25
+ PRODUCT_TYPE_OTHER = '09'
26
+
27
+ PRODUCT_TYPE_OPTIONS = [
28
+ ['書籍', PRODUCT_TYPE_BOOK],
29
+ ['DVD', PRODUCT_TYPE_DVD],
30
+ ['CD', PRODUCT_TYPE_CD],
31
+ ['その他', PRODUCT_TYPE_OTHER]
32
+ ]
33
+
34
+ DISCOUNT = {
35
+ PRODUCT_TYPE_BOOK => 0.8,
36
+ PRODUCT_TYPE_DVD => 0.2,
37
+ PRODUCT_TYPE_CD => 0.5,
38
+ PRODUCT_TYPE_OTHER => 1
39
+ }
40
+
41
+ def discount_price
42
+ (DISCOUNT[product_type_cd] * price).to_i
43
+ end
44
+ end
45
+
46
+ it "test_legacy_product" do
47
+ assert_product_discount(LegacyProduct1)
48
+
49
+ # 選択肢を表示するためのデータは以下のように取得できる
50
+ LegacyProduct1::PRODUCT_TYPE_OPTIONS.should ==
51
+ [['書籍', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
52
+ end
53
+
54
+
55
+
56
+
57
+ # できるだけ定数定義をまとめた場合
58
+ # 結構すっきりするけど、同じことをいろんなモデルで書くかと思うと気が重い。
59
+ class LegacyProduct2 < ActiveRecord::Base
60
+ set_table_name 'products'
61
+
62
+ PRODUCT_TYPE_DEFS = [
63
+ {:id => '01', :name => '書籍', :discount => 0.8},
64
+ {:id => '02', :name => 'DVD', :discount => 0.2},
65
+ {:id => '03', :name => 'CD', :discount => 0.5},
66
+ {:id => '09', :name => 'その他', :discount => 1}
67
+ ]
68
+
69
+ PRODUCT_TYPE_OPTIONS = PRODUCT_TYPE_DEFS.map{|t| [t[:name], t[:id]]}
70
+ DISCOUNT = PRODUCT_TYPE_DEFS.inject({}){|dest, t|
71
+ dest[t[:id]] = t[:discount]; dest}
72
+
73
+ def discount_price
74
+ (DISCOUNT[product_type_cd] * price).to_i
75
+ end
76
+ end
77
+
78
+ it "test_legacy_product" do
79
+ assert_product_discount(LegacyProduct2)
80
+
81
+ # 選択肢を表示するためのデータは以下のように取得できる
82
+ LegacyProduct2::PRODUCT_TYPE_OPTIONS.should ==
83
+ [['書籍', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
84
+ end
85
+
86
+ # selectable_attrを使った場合
87
+ # 定義は一カ所にまとめられて、任意の属性(ここでは:discount)も一緒に書くことができてすっきり〜
88
+ class Product1 < ActiveRecord::Base
89
+ set_table_name 'products'
90
+
91
+ selectable_attr :product_type_cd do
92
+ entry '01', :book, '書籍', :discount => 0.8
93
+ entry '02', :dvd, 'DVD', :discount => 0.2
94
+ entry '03', :cd, 'CD', :discount => 0.5
95
+ entry '09', :other, 'その他', :discount => 1
96
+ validates_format :allow_nil => true, :message => 'は次のいずれかでなければなりません。 #{entries}'
97
+ end
98
+
99
+ def discount_price
100
+ (product_type_entry[:discount] * price).to_i
101
+ end
102
+ end
103
+
104
+ it "test_product1" do
105
+ assert_product_discount(Product1)
106
+ # 選択肢を表示するためのデータは以下のように取得できる
107
+ Product1.product_type_options.should ==
108
+ [['書籍', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
109
+ end
110
+
111
+
112
+ # selectable_attrが定義するインスタンスメソッドの詳細
113
+ it "test_product_type_instance_methods" do
114
+ p1 = Product1.new
115
+ p1.product_type_cd.should be_nil
116
+ p1.product_type_key.should be_nil
117
+ p1.product_type_name.should be_nil
118
+ # idを変更すると得られるキーも名称も変わります
119
+ p1.product_type_cd = '02'
120
+ p1.product_type_cd.should == '02'
121
+ p1.product_type_key.should == :dvd
122
+ p1.product_type_name.should == 'DVD'
123
+ # キーを変更すると得られるidも名称も変わります
124
+ p1.product_type_key = :book
125
+ p1.product_type_cd.should == '01'
126
+ p1.product_type_key.should == :book
127
+ p1.product_type_name.should == '書籍'
128
+ # id、キー、名称以外の任意の属性は、entryの[]メソッドで取得します。
129
+ p1.product_type_key = :cd
130
+ p1.product_type_entry[:discount].should == 0.5
131
+ end
132
+
133
+ # selectable_attrが定義するクラスメソッドの詳細
134
+ it "test_product_type_class_methods" do
135
+ # キーからid、名称を取得できます
136
+ Product1.product_type_id_by_key(:book).should == '01'
137
+ Product1.product_type_id_by_key(:dvd).should == '02'
138
+ Product1.product_type_id_by_key(:cd).should == '03'
139
+ Product1.product_type_id_by_key(:other).should == '09'
140
+ Product1.product_type_name_by_key(:book).should == '書籍'
141
+ Product1.product_type_name_by_key(:dvd).should == 'DVD'
142
+ Product1.product_type_name_by_key(:cd).should == 'CD'
143
+ Product1.product_type_name_by_key(:other).should == 'その他'
144
+ # 存在しないキーの場合はnilを返します
145
+ Product1.product_type_id_by_key(nil).should be_nil
146
+ Product1.product_type_name_by_key(nil).should be_nil
147
+ Product1.product_type_id_by_key(:unexist).should be_nil
148
+ Product1.product_type_name_by_key(:unexist).should be_nil
149
+
150
+ # idからキー、名称を取得できます
151
+ Product1.product_type_key_by_id('01').should == :book
152
+ Product1.product_type_key_by_id('02').should == :dvd
153
+ Product1.product_type_key_by_id('03').should == :cd
154
+ Product1.product_type_key_by_id('09').should == :other
155
+ Product1.product_type_name_by_id('01').should == '書籍'
156
+ Product1.product_type_name_by_id('02').should == 'DVD'
157
+ Product1.product_type_name_by_id('03').should == 'CD'
158
+ Product1.product_type_name_by_id('09').should == 'その他'
159
+ # 存在しないidの場合はnilを返します
160
+ Product1.product_type_key_by_id(nil).should be_nil
161
+ Product1.product_type_name_by_id(nil).should be_nil
162
+ Product1.product_type_key_by_id('99').should be_nil
163
+ Product1.product_type_name_by_id('99').should be_nil
164
+
165
+ # id、キー、名称の配列を取得できます
166
+ Product1.product_type_ids.should == ['01', '02', '03', '09']
167
+ Product1.product_type_keys.should == [:book, :dvd, :cd, :other]
168
+ Product1.product_type_names.should == ['書籍', 'DVD', 'CD', 'その他']
169
+ # 一部のものだけ取得することも可能です。
170
+ Product1.product_type_ids(:cd, :dvd).should == ['03', '02' ]
171
+ Product1.product_type_keys('02', '03').should == [:dvd, :cd ]
172
+ Product1.product_type_names('02', '03').should == ['DVD', 'CD']
173
+ Product1.product_type_names(:cd, :dvd).should == ['CD', 'DVD']
174
+
175
+ # select_tagなどのoption_tagsを作るための配列なんか一発っす
176
+ Product1.product_type_options.should ==
177
+ [['書籍', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
178
+ end
179
+
180
+ it "validate with entries" do
181
+ p1 = Product1.new
182
+ p1.product_type_cd.should == nil
183
+ p1.valid?.should == true
184
+ p1.errors.empty?.should == true
185
+
186
+ p1.product_type_key = :book
187
+ p1.product_type_cd.should == '01'
188
+ p1.valid?.should == true
189
+ p1.errors.empty?.should == true
190
+
191
+ p1.product_type_cd = 'XX'
192
+ p1.product_type_cd.should == 'XX'
193
+ p1.valid?.should == false
194
+ p1.errors.on(:product_type_cd).should == "は次のいずれかでなければなりません。 書籍, DVD, CD, その他"
195
+ end
196
+
197
+ # selectable_attrのエントリ名をDB上に保持するためのモデル
198
+ class ItemMaster < ActiveRecord::Base
199
+ end
200
+
201
+ # selectable_attrを使った場合その2
202
+ # アクセス時に毎回アクセス時にDBから項目名を取得します。
203
+ class ProductWithDB1 < ActiveRecord::Base
204
+ set_table_name 'products'
205
+
206
+ selectable_attr :product_type_cd do
207
+ update_by(
208
+ "select item_cd, name from item_masters where category_name = 'product_type_cd' order by item_no",
209
+ :when => :everytime)
210
+ entry '01', :book, '書籍', :discount => 0.8
211
+ entry '02', :dvd, 'DVD', :discount => 0.2
212
+ entry '03', :cd, 'CD', :discount => 0.5
213
+ entry '09', :other, 'その他', :discount => 1
214
+ end
215
+
216
+ def discount_price
217
+ (product_type_entry[:discount] * price).to_i
218
+ end
219
+ end
220
+
221
+ it "test_update_entry_name" do
222
+ # DBに全くデータがなくてもコードで記述してあるエントリは存在します。
223
+ ItemMaster.delete_all("category_name = 'product_type_cd'")
224
+ ProductWithDB1.product_type_entries.length.should == 4
225
+ ProductWithDB1.product_type_name_by_key(:book).should == '書籍'
226
+ ProductWithDB1.product_type_name_by_key(:dvd).should == 'DVD'
227
+ ProductWithDB1.product_type_name_by_key(:cd).should == 'CD'
228
+ ProductWithDB1.product_type_name_by_key(:other).should == 'その他'
229
+
230
+ assert_product_discount(ProductWithDB1)
231
+
232
+ # DBからエントリの名称を動的に変更できます
233
+ item_book = ItemMaster.create(:category_name => 'product_type_cd', :item_no => 1, :item_cd => '01', :name => '本')
234
+ ProductWithDB1.product_type_entries.length.should == 4
235
+ ProductWithDB1.product_type_name_by_key(:book).should == '本'
236
+ ProductWithDB1.product_type_name_by_key(:dvd).should == 'DVD'
237
+ ProductWithDB1.product_type_name_by_key(:cd).should == 'CD'
238
+ ProductWithDB1.product_type_name_by_key(:other).should == 'その他'
239
+ ProductWithDB1.product_type_options.should ==
240
+ [['本', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
241
+
242
+ # DBからエントリの並び順を動的に変更できます
243
+ item_book.item_no = 4;
244
+ item_book.save!
245
+ item_other = ItemMaster.create(:category_name => 'product_type_cd', :item_no => 1, :item_cd => '09', :name => 'その他')
246
+ item_dvd = ItemMaster.create(:category_name => 'product_type_cd', :item_no => 2, :item_cd => '02') # nameは指定しなかったらデフォルトが使われます。
247
+ item_cd = ItemMaster.create(:category_name => 'product_type_cd', :item_no => 3, :item_cd => '03') # nameは指定しなかったらデフォルトが使われます。
248
+ ProductWithDB1.product_type_options.should ==
249
+ [['その他', '09'], ['DVD', '02'], ['CD', '03'], ['本', '01']]
250
+
251
+ # DBからエントリを動的に追加することも可能です。
252
+ item_toys = ItemMaster.create(:category_name => 'product_type_cd', :item_no => 5, :item_cd => '04', :name => 'おもちゃ')
253
+ ProductWithDB1.product_type_options.should ==
254
+ [['その他', '09'], ['DVD', '02'], ['CD', '03'], ['本', '01'], ['おもちゃ', '04']]
255
+ ProductWithDB1.product_type_key_by_id('04').should == :entry_04
256
+
257
+ # DBからレコードを削除してもコードで定義したentryは削除されません。
258
+ # 順番はDBからの取得順で並び替えられたものの後になります
259
+ item_dvd.destroy
260
+ ProductWithDB1.product_type_options.should ==
261
+ [['その他', '09'], ['CD', '03'], ['本', '01'], ['おもちゃ', '04'], ['DVD', '02']]
262
+
263
+ # DB上で追加したレコードを削除すると、エントリも削除されます
264
+ item_toys.destroy
265
+ ProductWithDB1.product_type_options.should ==
266
+ [['その他', '09'], ['CD', '03'], ['本', '01'], ['DVD', '02']]
267
+
268
+ # 名称を指定していたDBのレコードを削除したら元に戻ります。
269
+ item_book.destroy
270
+ ProductWithDB1.product_type_options.should ==
271
+ [['その他', '09'], ['CD', '03'], ['書籍', '01'], ['DVD', '02']]
272
+
273
+ # エントリに該当するレコードを全部削除したら、元に戻ります。
274
+ ItemMaster.delete_all("category_name = 'product_type_cd'")
275
+ ProductWithDB1.product_type_options.should ==
276
+ [['書籍', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
277
+
278
+ assert_product_discount(ProductWithDB1)
279
+ end
280
+
281
+
282
+
283
+
284
+ # Q: product_type_cd の'_cd'はどこにいっちゃったの?
285
+ # A: デフォルトでは、/(_cd$|_code$|_cds$|_codes$)/ を削除したものをbase_nameとして
286
+ # 扱い、それに_keyなどを付加してメソッド名を定義します。もしこのルールを変更したい場合、
287
+ # selectable_attrを使う前に selectable_attr_name_pattern で新たなルールを指定してください。
288
+ class Product2 < ActiveRecord::Base
289
+ set_table_name 'products'
290
+ self.selectable_attr_name_pattern = /^product_|_cd$/
291
+
292
+ selectable_attr :product_type_cd do
293
+ entry '01', :book, '書籍', :discount => 0.8
294
+ entry '02', :dvd, 'DVD', :discount => 0.2
295
+ entry '03', :cd, 'CD', :discount => 0.5
296
+ entry '09', :other, 'その他', :discount => 1
297
+ end
298
+
299
+ def discount_price
300
+ (type_entry[:discount] * price).to_i
301
+ end
302
+ end
303
+
304
+ it "test_product2" do
305
+ assert_product_discount(Product2)
306
+ # 選択肢を表示するためのデータは以下のように取得できる
307
+ Product2.type_options.should ==
308
+ [['書籍', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
309
+
310
+ p2 = Product2.new
311
+ p2.product_type_cd.should be_nil
312
+ p2.type_key.should be_nil
313
+ p2.type_name.should be_nil
314
+ # idを変更すると得られるキーも名称も変わります
315
+ p2.product_type_cd = '02'
316
+ p2.product_type_cd.should == '02'
317
+ p2.type_key.should == :dvd
318
+ p2.type_name.should == 'DVD'
319
+ # キーを変更すると得られるidも名称も変わります
320
+ p2.type_key = :book
321
+ p2.product_type_cd.should == '01'
322
+ p2.type_key.should == :book
323
+ p2.type_name.should == '書籍'
324
+ # id、キー、名称以外の任意の属性は、entryの[]メソッドで取得します。
325
+ p2.type_key = :cd
326
+ p2.type_entry[:discount].should == 0.5
327
+
328
+ Product2.type_id_by_key(:book).should == '01'
329
+ Product2.type_id_by_key(:dvd).should == '02'
330
+ Product2.type_name_by_key(:cd).should == 'CD'
331
+ Product2.type_name_by_key(:other).should == 'その他'
332
+ Product2.type_key_by_id('09').should == :other
333
+ Product2.type_name_by_id('01').should == '書籍'
334
+ Product2.type_keys.should == [:book, :dvd, :cd, :other]
335
+ Product2.type_names.should == ['書籍', 'DVD', 'CD', 'その他']
336
+ Product2.type_keys('02', '03').should == [:dvd, :cd]
337
+ Product2.type_names(:cd, :dvd).should == ['CD', 'DVD']
338
+ end
339
+
340
+
341
+
342
+
343
+ # Q: selectable_attrの呼び出し毎にbase_bname(って言うの?)を指定したいんだけど。
344
+ # A: base_nameオプションを指定してください。
345
+ class Product3 < ActiveRecord::Base
346
+ set_table_name 'products'
347
+
348
+ selectable_attr :product_type_cd, :base_name => 'type' do
349
+ entry '01', :book, '書籍', :discount => 0.8
350
+ entry '02', :dvd, 'DVD', :discount => 0.2
351
+ entry '03', :cd, 'CD', :discount => 0.5
352
+ entry '09', :other, 'その他', :discount => 1
353
+ end
354
+
355
+ def discount_price
356
+ (type_entry[:discount] * price).to_i
357
+ end
358
+ end
359
+
360
+ it "test_product3" do
361
+ assert_product_discount(Product3)
362
+ # 選択肢を表示するためのデータは以下のように取得できる
363
+ Product3.type_options.should ==
364
+ [['書籍', '01'], ['DVD', '02'], ['CD', '03'], ['その他', '09']]
365
+
366
+ p3 = Product3.new
367
+ p3.product_type_cd.should be_nil
368
+ p3.type_key.should be_nil
369
+ p3.type_name.should be_nil
370
+ # idを変更すると得られるキーも名称も変わります
371
+ p3.product_type_cd = '02'
372
+ p3.product_type_cd.should == '02'
373
+ p3.type_key.should == :dvd
374
+ p3.type_name.should == 'DVD'
375
+ # キーを変更すると得られるidも名称も変わります
376
+ p3.type_key = :book
377
+ p3.product_type_cd.should == '01'
378
+ p3.type_key.should == :book
379
+ p3.type_name.should == '書籍'
380
+ # id、キー、名称以外の任意の属性は、entryの[]メソッドで取得します。
381
+ p3.type_key = :cd
382
+ p3.type_entry[:discount].should == 0.5
383
+
384
+ Product3.type_id_by_key(:book).should == '01'
385
+ Product3.type_id_by_key(:dvd).should == '02'
386
+ Product3.type_name_by_key(:cd).should == 'CD'
387
+ Product3.type_name_by_key(:other).should == 'その他'
388
+ Product3.type_key_by_id('09').should == :other
389
+ Product3.type_name_by_id('01').should == '書籍'
390
+ Product3.type_keys.should == [:book, :dvd, :cd, :other]
391
+ Product3.type_names.should == ['書籍', 'DVD', 'CD', 'その他']
392
+ Product3.type_keys('02', '03').should == [:dvd, :cd]
393
+ Product3.type_names(:cd, :dvd).should == ['CD', 'DVD']
394
+ end
395
+
396
+ end
397
+