selectable_attr 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,304 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SelectableAttr
3
+ module Base
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ ENUM_ARRAY_METHODS = {
9
+ :none => {
10
+ :to_hash_array => Proc.new do |enum, attr_value|
11
+ value = (attr_value || []).map(&:to_s)
12
+ enum.to_hash_array do |hash|
13
+ hash[:select] = value.include?(hash[:id].to_s)
14
+ end
15
+ end,
16
+
17
+ :to_attr_value => Proc.new do |enum, hash_array|
18
+ hash_array.select{|hash| hash[:select]}.map{|hash| hash[:id]}
19
+ end
20
+ },
21
+
22
+ :comma_string => {
23
+ :to_hash_array => Proc.new do |enum, attr_value|
24
+ values = attr_value.is_a?(Array) ? attr_value.map{|v|v.to_s} :
25
+ (attr_value || '').split(',')
26
+ enum.to_hash_array do |hash|
27
+ hash[:select] = values.include?(hash[:id].to_s)
28
+ end
29
+ end,
30
+
31
+ :to_attr_value => Proc.new do |enum, hash_array|
32
+ hash_array.select{|hash| hash[:select]}.map{|hash| hash[:id]}.join(',')
33
+ end
34
+ },
35
+
36
+
37
+ :binary_string => {
38
+ :to_hash_array => Proc.new do |enum, attr_value|
39
+ value = attr_value || ''
40
+ idx = 0
41
+ enum.to_hash_array do |hash|
42
+ hash[:select] = (value[idx, 1] == '1')
43
+ idx += 1
44
+ end
45
+ end,
46
+
47
+ :to_attr_value => Proc.new do |enum, hash_array|
48
+ result = ''
49
+ hash_map = hash_array.inject({}){|dest, hash| dest[hash[:id]] = hash; dest}
50
+ enum.each do |entry|
51
+ hash = hash_map[entry.id]
52
+ result << (hash[:select] ? '1' : '0')
53
+ end
54
+ result
55
+ end
56
+ }
57
+ }
58
+
59
+ module ClassMethods
60
+ def single_selectable_attrs
61
+ @single_selectable_attrs_hash ||= {};
62
+ @single_selectable_attrs_hash[self] ||= []
63
+ end
64
+
65
+ def multi_selectable_attrs
66
+ @multi_selectable_attrs_hash ||= {};
67
+ @multi_selectable_attrs_hash[self] ||= []
68
+ end
69
+
70
+ def selectable_attr_type_for(attr)
71
+ single_selectable_attrs.include?(attr.to_s) ? :single :
72
+ multi_selectable_attrs.include?(attr.to_s) ? :multi : nil
73
+ end
74
+
75
+ def enum(*args, &block)
76
+ process_definition(block, *args) do |enum, context|
77
+ self.single_selectable_attrs << context[:attr].to_s
78
+ define_enum_class_methods(context)
79
+ define_enum_instance_methods(context)
80
+ end
81
+ end
82
+ alias_method :single_selectable_attr, :enum
83
+ alias_method :selectable_attr, :enum
84
+
85
+
86
+ def enum_array(*args, &block)
87
+ base_options = args.last.is_a?(Hash) ? args.pop : {}
88
+ args << base_options # .update({:attr_accessor => false})
89
+ process_definition(block, *args) do |enum, context|
90
+ self.multi_selectable_attrs << context[:attr].to_s
91
+ define_enum_class_methods(context)
92
+ define_enum_array_instance_methods(context)
93
+ end
94
+ end
95
+ alias_method :multi_selectable_attr, :enum_array
96
+
97
+ def process_definition(block, *args)
98
+ base_options = args.last.is_a?(Hash) ? args.pop : {}
99
+ enum = base_options[:enum] || create_enum(&block)
100
+ args.each do |attr|
101
+ context = {
102
+ :enum => enum,
103
+ :attr_accessor => !has_attr(attr),
104
+ :attr => attr,
105
+ :base_name => enum_base_name(attr)
106
+ }.update(base_options)
107
+ define_enum(context)
108
+ define_accessor(context)
109
+ yield(enum, context)
110
+ unless enum.i18n_scope
111
+ paths = [:selectable_attrs] + self.name.to_s.split('::').map{|s| s.to_sym}
112
+ paths << attr.to_sym
113
+ enum.i18n_scope(*paths)
114
+ end
115
+ end
116
+ enum
117
+ end
118
+
119
+ def has_attr(attr)
120
+ return true if self.method_defined?(attr)
121
+ return false unless self.respond_to?(:columns)
122
+ if self.respond_to?(:connection) and self.respond_to?(:connected?)
123
+ begin
124
+ self.connection unless self.connected?
125
+ rescue Exception
126
+ return nil if !self.connected?
127
+ end
128
+ end
129
+ (respond_to?(:table_exists?) && self.table_exists?) ?
130
+ (self.columns || []).any?{|col|col.name.to_s == attr.to_s} : false
131
+ end
132
+
133
+ def attr_enumeable_base(*args, &block)
134
+ @base_name_processor = block
135
+ end
136
+
137
+ def enum_base_name(attr)
138
+ if @base_name_processor
139
+ @base_name_processor.call(attr).to_s
140
+ else
141
+ attr.to_s.gsub(selectable_attr_name_pattern, '')
142
+ end
143
+ end
144
+
145
+ DEFAULT_SELECTABLE_ATTR_NAME_PATTERN = /(_cd$|_code$|_cds$|_codes$)/
146
+
147
+ def selectable_attr_name_pattern
148
+ @selectable_attr_name_pattern ||= DEFAULT_SELECTABLE_ATTR_NAME_PATTERN
149
+ end
150
+ alias_method :enum_name_pattern, :selectable_attr_name_pattern
151
+
152
+ def selectable_attr_name_pattern=(value)
153
+ @selectable_attr_name_pattern = value
154
+ end
155
+ alias_method :enum_name_pattern=, :selectable_attr_name_pattern=
156
+
157
+ def create_enum(&block)
158
+ result = Enum.new
159
+ result.instance_eval(&block)
160
+ result
161
+ end
162
+
163
+ def define_enum(context)
164
+ base_name = context[:base_name]
165
+ const_name = "#{base_name.upcase}_ENUM"
166
+ const_set(const_name, context[:enum]) unless const_defined?(const_name)
167
+ end
168
+
169
+ def enum_for(attr)
170
+ base_name = enum_base_name(attr)
171
+ name = "#{base_name.upcase}_ENUM"
172
+ const_defined?(name) ? const_get(name) : nil
173
+ end
174
+
175
+ def define_accessor(context)
176
+ attr = context[:attr]
177
+ return unless (instance_methods & [attr, "#{attr}="]).empty?
178
+ if context[:attr_accessor]
179
+ if context[:default]
180
+ if respond_to?(:attr_accessor_with_default)
181
+ attr_accessor_with_default(attr, context[:default])
182
+ else
183
+ instance_var_name = "@#{attr}"
184
+ attr_writer(attr)
185
+ define_method(attr) do
186
+ value = instance_variable_get(instance_var_name)
187
+ instance_variable_set(instance_var_name, value = context[:default]) unless value
188
+ value
189
+ end
190
+ end
191
+ else
192
+ attr_accessor(attr)
193
+ end
194
+ else
195
+ if context[:default]
196
+ $stderr.puts "WARNING! :default option ignored for #{attr}"
197
+ end
198
+ end
199
+ end
200
+
201
+ def define_enum_class_methods(context)
202
+ base_name = context[:base_name]
203
+ enum = context[:enum]
204
+ mod = Module.new
205
+ mod.module_eval do
206
+ define_method("#{base_name}_enum"){enum}
207
+ define_method("#{base_name}_hash_array"){enum.to_hash_array}
208
+ define_method("#{base_name}_entries"){enum.entries}
209
+ define_method("#{base_name}_options"){|*ids_or_keys|enum.options(*ids_or_keys)}
210
+ define_method("#{base_name}_ids"){|*ids_or_keys| enum.ids(*ids_or_keys)}
211
+ define_method("#{base_name}_keys"){|*ids_or_keys|enum.keys(*ids_or_keys)}
212
+ define_method("#{base_name}_names"){|*ids_or_keys|enum.names(*ids_or_keys)}
213
+ define_method("#{base_name}_key_by_id"){|id|enum.key_by_id(id)}
214
+ define_method("#{base_name}_id_by_key"){|key|enum.id_by_key(key)}
215
+ define_method("#{base_name}_name_by_id"){|id|enum.name_by_id(id)}
216
+ define_method("#{base_name}_name_by_key"){|key|enum.name_by_key(key)}
217
+ define_method("#{base_name}_entry_by_id"){|id|enum.entry_by_id(id)}
218
+ define_method("#{base_name}_entry_by_key"){|key|enum.entry_by_key(key)}
219
+ end
220
+ if convertors = ENUM_ARRAY_METHODS[context[:convert_with] || :none]
221
+ mod.module_eval do
222
+ define_method("#{base_name}_to_hash_array", convertors[:to_hash_array])
223
+ define_method("hash_array_to_#{base_name}", convertors[:to_attr_value])
224
+ end
225
+ end
226
+ self.extend(mod)
227
+ end
228
+
229
+ def define_enum_instance_methods(context)
230
+ attr = context[:attr]
231
+ base_name = context[:base_name]
232
+ instance_methods = <<-EOS
233
+ def #{base_name}_key
234
+ self.class.#{base_name}_key_by_id(#{attr})
235
+ end
236
+ def #{base_name}_key=(key)
237
+ self.#{attr} = self.class.#{base_name}_id_by_key(key)
238
+ end
239
+ def #{base_name}_name
240
+ self.class.#{base_name}_name_by_id(#{attr})
241
+ end
242
+ def #{base_name}_entry
243
+ self.class.#{base_name}_entry_by_id(#{attr})
244
+ end
245
+ def #{base_name}_entry
246
+ self.class.#{base_name}_entry_by_id(#{attr})
247
+ end
248
+ EOS
249
+ self.module_eval(instance_methods)
250
+ end
251
+
252
+ def define_enum_array_instance_methods(context)
253
+ attr = context[:attr]
254
+ base_name = context[:base_name]
255
+ # ActiveRecord::Baseから継承している場合は、基本カラムに対応するメソッドはない
256
+ self.module_eval(<<-"EOS")
257
+ def #{base_name}_ids
258
+ #{base_name}_hash_array_selected.map{|hash|hash[:id]}
259
+ end
260
+ def #{base_name}_ids=(ids)
261
+ ids = ids.split(',') if ids.is_a?(String)
262
+ ids = ids ? ids.map(&:to_s) : []
263
+ update_#{base_name}_hash_array{|hash|ids.include?(hash[:id].to_s)}
264
+ end
265
+ def #{base_name}_hash_array
266
+ self.class.#{base_name}_to_hash_array(self.class.#{base_name}_enum, #{attr})
267
+ end
268
+ def #{base_name}_hash_array=(hash_array)
269
+ self.#{attr} = self.class.hash_array_to_#{base_name}(self.class.#{base_name}_enum, hash_array)
270
+ end
271
+ def #{base_name}_hash_array_selected
272
+ #{base_name}_hash_array.select{|hash|!!hash[:select]}
273
+ end
274
+ def update_#{base_name}_hash_array(&block)
275
+ hash_array = #{base_name}_hash_array.map do |hash|
276
+ hash.merge(:select => yield(hash))
277
+ end
278
+ self.#{base_name}_hash_array = hash_array
279
+ end
280
+ def #{base_name}_keys
281
+ #{base_name}_hash_array_selected.map{|hash|hash[:key]}
282
+ end
283
+ def #{base_name}_keys=(keys)
284
+ update_#{base_name}_hash_array{|hash|keys.include?(hash[:key])}
285
+ end
286
+ def #{base_name}_selection
287
+ #{base_name}_hash_array.map{|hash|!!hash[:select]}
288
+ end
289
+ def #{base_name}_selection=(selection)
290
+ idx = -1
291
+ update_#{base_name}_hash_array{|hash| idx += 1; !!selection[idx]}
292
+ end
293
+ def #{base_name}_names
294
+ #{base_name}_hash_array_selected.map{|hash|hash[:name]}
295
+ end
296
+ def #{base_name}_entries
297
+ ids = #{base_name}_ids
298
+ self.class.#{base_name}_enum.select{|entry|ids.include?(entry.id)}
299
+ end
300
+ EOS
301
+ end
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,168 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SelectableAttr
3
+
4
+ class Enum
5
+ include Enumerable
6
+
7
+ class << self
8
+ def instances
9
+ @@instances ||= []
10
+ end
11
+ end
12
+
13
+ def initialize(&block)
14
+ @entries = []
15
+ instance_eval(&block) if block_given?
16
+ SelectableAttr::Enum.instances << self
17
+ end
18
+
19
+ def entries
20
+ @entries
21
+ end
22
+
23
+ def each(&block)
24
+ entries.each(&block)
25
+ end
26
+
27
+ def define(id, key, name, options = nil, &block)
28
+ entry = Entry.new(self, id, key, name, options, &block)
29
+ entry.instance_variable_set(:@defined_in_code, true)
30
+ @entries << entry
31
+ end
32
+ alias_method :entry, :define
33
+
34
+ def i18n_scope(*path)
35
+ @i18n_scope = path unless path.empty?
36
+ @i18n_scope
37
+ end
38
+
39
+ def match_entry(entry, value, *attrs)
40
+ attrs.any?{|attr| entry[attr].to_s == value.to_s}
41
+ end
42
+
43
+ def entry_by(value, *attrs)
44
+ entries.detect{|entry| match_entry(entry, value, *attrs)} || Entry::NULL
45
+ end
46
+
47
+ def entry_by_id(id)
48
+ entry_by(id, :id)
49
+ end
50
+
51
+ def entry_by_key(key)
52
+ entry_by(key, :key)
53
+ end
54
+
55
+ def entry_by_id_or_key(id_or_key)
56
+ entry_by(id_or_key, :id, :key)
57
+ end
58
+
59
+ def entry_by_hash(attrs)
60
+ entries.detect{|entry| attrs.all?{|(attr, value)| entry[attr].to_s == value.to_s }} || Entry::NULL
61
+ end
62
+
63
+ def [](arg)
64
+ arg.is_a?(Hash) ? entry_by_hash(arg) : entry_by_id_or_key(arg)
65
+ end
66
+
67
+ def values(*args)
68
+ args = args.empty? ? [:name, :id] : args
69
+ result = entries.collect{|entry| args.collect{|arg| entry.send(arg) }}
70
+ (args.length == 1) ? result.flatten : result
71
+ end
72
+
73
+ def map_attrs(attrs, *ids_or_keys)
74
+ if attrs.is_a?(Array)
75
+ ids_or_keys.empty? ?
76
+ entries.map{|entry| attrs.map{|attr|entry.send(attr)}} :
77
+ ids_or_keys.map do |id_or_key|
78
+ entry = entry_by_id_or_key(id_or_key)
79
+ attrs.map{|attr|entry.send(attr)}
80
+ end
81
+ else
82
+ attr = attrs
83
+ ids_or_keys.empty? ?
84
+ entries.map(&attr.to_sym) :
85
+ ids_or_keys.map{|id_or_key|entry_by_id_or_key(id_or_key).send(attr)}
86
+ end
87
+ end
88
+
89
+ def ids(*ids_or_keys); map_attrs(:id, *ids_or_keys); end
90
+ def keys(*ids_or_keys); map_attrs(:key, *ids_or_keys); end
91
+ def names(*ids_or_keys); map_attrs(:name, *ids_or_keys); end
92
+ def options(*ids_or_keys); map_attrs([:name, :id], *ids_or_keys); end
93
+
94
+ def key_by_id(id); entry_by_id(id).key; end
95
+ def id_by_key(key); entry_by_key(key).id; end
96
+ def name_by_id(id); entry_by_id(id).name; end
97
+ def name_by_key(key); entry_by_key(key).name; end
98
+
99
+ def find(options = nil, &block)
100
+ entries.detect{|entry|
101
+ block_given? ? yield(entry) : entry.match?(options)
102
+ } || Entry::NULL
103
+ end
104
+
105
+ def to_hash_array
106
+ entries.map do |entry|
107
+ result = entry.to_hash
108
+ yield(result) if defined? yield
109
+ result
110
+ end
111
+ end
112
+
113
+ def length
114
+ entries.length
115
+ end
116
+ alias_method :size, :length
117
+
118
+ class Entry
119
+ BASE_ATTRS = [:id, :key, :name]
120
+ attr_reader :id, :key
121
+ attr_reader :defined_in_code
122
+ def initialize(enum, id, key, name, options = nil, &block)
123
+ @enum = enum
124
+ @id = id
125
+ @key = key
126
+ @name = name
127
+ @options = options
128
+ self.instance_eval(&block) if block
129
+ end
130
+
131
+ attr_reader :name
132
+
133
+ def [](option_key)
134
+ BASE_ATTRS.include?(option_key) ? send(option_key) :
135
+ @options ? @options[option_key] : nil
136
+ end
137
+
138
+ def match?(options)
139
+ @options === options
140
+ end
141
+
142
+ def null?
143
+ false
144
+ end
145
+
146
+ def null_object?
147
+ self.null?
148
+ end
149
+
150
+ def to_hash
151
+ (@options || {}).merge(:id => @id, :key => @key, :name => name)
152
+ end
153
+
154
+ def inspect
155
+ # object_idを2倍にしているのは通常のinspectと合わせるためです。
156
+ '#<%s:%x @id=%s, @key=%s, @name=%s, @options=%s' % [
157
+ self.class.name, object_id * 2, id.inspect, key.inspect, name.inspect, @options.inspect]
158
+ end
159
+
160
+ NULL = new(nil, nil, nil, nil) do
161
+ def null?; true; end
162
+ def name; nil; end
163
+ end
164
+ end
165
+
166
+ end
167
+
168
+ end
@@ -0,0 +1,3 @@
1
+ module SelectableAttr
2
+ VERSION = '0.0.3'
3
+ end
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+ module SelectableAttr
3
+ autoload :VERSION, 'selectable_attr/version'
4
+ autoload :Enum, 'selectable_attr/enum'
5
+ autoload :Base, 'selectable_attr/base'
6
+ end
@@ -0,0 +1,54 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{selectable_attr}
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 generates extra methods dynamically for attribute which has options}
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.rb",
24
+ "lib/selectable_attr/base.rb",
25
+ "lib/selectable_attr/enum.rb",
26
+ "lib/selectable_attr/version.rb",
27
+ "selectable_attr.gemspec",
28
+ "spec/selectable_attr_base_alias_spec.rb",
29
+ "spec/selectable_attr_enum_spec.rb",
30
+ "spec/spec_helper.rb",
31
+ "tasks/selectable_attr_tasks.rake",
32
+ "uninstall.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/akm/selectable_attr/}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.4}
38
+ s.summary = %q{selectable_attr generates extra methods dynamically}
39
+ s.test_files = [
40
+ "spec/selectable_attr_base_alias_spec.rb",
41
+ "spec/selectable_attr_enum_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ else
51
+ end
52
+ else
53
+ end
54
+ end