selectable_attr 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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