aux_codes 1.0.6
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.
- data/lib/aux_codes.rb +247 -0
- data/lib/aux_codes/aux_code_class.rb +150 -0
- data/lib/aux_codes/migration.rb +31 -0
- metadata +57 -0
data/lib/aux_codes.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
%w( rubygems activerecord aux_codes/migration ).each {|lib| require lib }
|
4
|
+
|
5
|
+
# top-level class for AuxCodes
|
6
|
+
#
|
7
|
+
# used for namespacing and global configuration (once added)
|
8
|
+
#
|
9
|
+
class AuxCodes
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# the basic AuxCode ActiveRecord class
|
14
|
+
#
|
15
|
+
class AuxCode < ActiveRecord::Base
|
16
|
+
|
17
|
+
validates_presence_of :name
|
18
|
+
validates_uniqueness_of :name, :scope => :aux_code_id
|
19
|
+
|
20
|
+
belongs_to :aux_code
|
21
|
+
alias code aux_code
|
22
|
+
alias category aux_code
|
23
|
+
|
24
|
+
has_many :aux_codes
|
25
|
+
alias codes aux_codes
|
26
|
+
|
27
|
+
before_create :set_default_values
|
28
|
+
|
29
|
+
def code_names
|
30
|
+
codes.map &:name
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_a_category?
|
34
|
+
aux_code_id == 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def class_name
|
38
|
+
name.gsub(/[^[:alpha:]]/,'_').titleize.gsub(' ','').singularize
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
name
|
43
|
+
end
|
44
|
+
|
45
|
+
def [] attribute_or_code_name
|
46
|
+
if attributes.include?attribute_or_code_name
|
47
|
+
attributes[attribute_or_code_name]
|
48
|
+
else
|
49
|
+
found = codes.select {|c| c.name.to_s =~ /#{attribute_or_code_name}/ }
|
50
|
+
if found.empty? # try case insensitive (sans underscores)
|
51
|
+
found = codes.select {|c| c.name.downcase.gsub('_',' ').to_s =~
|
52
|
+
/#{attribute_or_code_name.to_s.downcase.gsub('_',' ')}/ }
|
53
|
+
end
|
54
|
+
found.first if found
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def deserialized_meta_hash
|
59
|
+
require 'yaml'
|
60
|
+
self.meta ||= ""
|
61
|
+
YAML::load(self.meta) || { }
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_meta_attribute meta_attribute
|
65
|
+
deserialized_meta_hash[meta_attribute.to_s]
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_meta_attribute meta_attribute, value
|
69
|
+
require 'yaml'
|
70
|
+
meta_hash = deserialized_meta_hash
|
71
|
+
meta_hash[meta_attribute.to_s] = value
|
72
|
+
self.meta = meta_hash.to_yaml
|
73
|
+
end
|
74
|
+
|
75
|
+
# this allows us to say things like:
|
76
|
+
#
|
77
|
+
# foo = AuxCode.create :name => 'foo'
|
78
|
+
# foo.codes.create :name => 'bar'
|
79
|
+
#
|
80
|
+
# foo.bar # should return the bar aux code under the foo category
|
81
|
+
#
|
82
|
+
# if bar doesn't exist, we throw a normal NoMethodError
|
83
|
+
#
|
84
|
+
# this should check meta_attributes on the object too
|
85
|
+
#
|
86
|
+
def method_missing_with_indifferent_hash_style_values name, *args, &block
|
87
|
+
method_missing_without_indifferent_hash_style_values name, *args, &block
|
88
|
+
rescue NoMethodError => ex
|
89
|
+
begin
|
90
|
+
if name.to_s[/=$/]
|
91
|
+
self.set_meta_attribute(name.to_s.sub(/=$/,''), args.first) # we said `code.foo= X` so we should set the foo meta attribute to X
|
92
|
+
save
|
93
|
+
else
|
94
|
+
code = self[name]
|
95
|
+
code = self.get_meta_attribute(name) unless code
|
96
|
+
raise ex unless code
|
97
|
+
return code
|
98
|
+
end
|
99
|
+
rescue
|
100
|
+
raise ex
|
101
|
+
end
|
102
|
+
end
|
103
|
+
alias_method_chain :method_missing, :indifferent_hash_style_values
|
104
|
+
|
105
|
+
# class methods
|
106
|
+
class << self
|
107
|
+
|
108
|
+
def categories
|
109
|
+
AuxCode.find_all_by_aux_code_id(0)
|
110
|
+
end
|
111
|
+
|
112
|
+
def category_names
|
113
|
+
AuxCode.categories.map &:name
|
114
|
+
end
|
115
|
+
|
116
|
+
def category category_object_or_id_or_name
|
117
|
+
obj = category_object_or_id_or_name
|
118
|
+
return obj if obj.is_a?AuxCode
|
119
|
+
return AuxCode.find(obj) if obj.is_a?Fixnum
|
120
|
+
if obj.is_a?(String) || obj.is_a?(Symbol)
|
121
|
+
obj = obj.to_s
|
122
|
+
found = AuxCode.find_by_name_and_aux_code_id(obj, 0)
|
123
|
+
if found.nil?
|
124
|
+
# try replacing underscores with spaces and doing a 'LIKE' search
|
125
|
+
found = AuxCode.find :first, :conditions => ["name LIKE ? AND aux_code_id = ?", obj.gsub('_', ' '), 0]
|
126
|
+
end
|
127
|
+
return found
|
128
|
+
end
|
129
|
+
raise "I don't know how to find an AuxCode of type #{ obj.class }"
|
130
|
+
end
|
131
|
+
alias [] category
|
132
|
+
|
133
|
+
def category_codes category_object_or_id_or_name
|
134
|
+
category( category_object_or_id_or_name ).codes
|
135
|
+
end
|
136
|
+
alias category_values category_codes
|
137
|
+
|
138
|
+
def category_code_names category_object_or_id_or_name
|
139
|
+
category( category_object_or_id_or_name ).code_names
|
140
|
+
end
|
141
|
+
|
142
|
+
def create_classes!
|
143
|
+
AuxCode.categories.each do |category|
|
144
|
+
Kernel::const_set category.class_name, category.aux_code_class
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# this allows us to say things like:
|
149
|
+
#
|
150
|
+
# AuxCode.create :name => 'foo'
|
151
|
+
#
|
152
|
+
# AuxCode.foo # should return the foo category aux code
|
153
|
+
#
|
154
|
+
def method_missing_with_indifferent_hash_style_values name, *args, &block
|
155
|
+
unless self.respond_to?:aux_code_id # in which case, this is a *derived* class, not AuxCode
|
156
|
+
begin
|
157
|
+
method_missing_without_indifferent_hash_style_values name, *args, &block
|
158
|
+
rescue NoMethodError => ex
|
159
|
+
begin
|
160
|
+
self[name]
|
161
|
+
rescue
|
162
|
+
raise ex
|
163
|
+
end
|
164
|
+
end
|
165
|
+
else
|
166
|
+
method_missing_without_indifferent_hash_style_values name, *args, &block
|
167
|
+
end
|
168
|
+
end
|
169
|
+
alias_method_chain :method_missing, :indifferent_hash_style_values
|
170
|
+
|
171
|
+
def load_yaml yaml_string
|
172
|
+
require 'yaml'
|
173
|
+
self.load YAML::load(yaml_string)
|
174
|
+
end
|
175
|
+
|
176
|
+
def load_file serialized_yaml_file_path
|
177
|
+
load_yaml File.read(serialized_yaml_file_path)
|
178
|
+
end
|
179
|
+
|
180
|
+
# initialize AuxCodes ... looks for config/aux_codes.yml
|
181
|
+
# and creates classes
|
182
|
+
def init # should eventually take configuration options (hash || block)
|
183
|
+
if ActiveRecord::Base.connection.tables.include? 'aux_codes'
|
184
|
+
aux_codes_yml = File.join 'config', 'aux_codes.yml'
|
185
|
+
if File.file? aux_codes_yml
|
186
|
+
load_file aux_codes_yml
|
187
|
+
create_classes!
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# loads AuxCodes (creates them) from a Hash, keyed on the name of the aux code categories to create
|
194
|
+
#
|
195
|
+
# hash: a Hash or an Array [ [key,value], [key,value] ] or anything with an enumerator
|
196
|
+
# that'll work with `hash.each {|key,value| ... }`
|
197
|
+
#
|
198
|
+
def load hash
|
199
|
+
return unless hash.is_a?Hash
|
200
|
+
hash.each do |category_name, codes|
|
201
|
+
category = AuxCode.find_or_create_by_name( category_name.to_s ).aux_code_class
|
202
|
+
codes.each do |name, values|
|
203
|
+
|
204
|
+
# only a name given
|
205
|
+
if values.nil? or values.empty?
|
206
|
+
if name.is_a? String or name.is_a? Symbol # we have a String || Symbol, it's likely the code's name, eg. :foo or 'bar'
|
207
|
+
category.create :name => name.to_s unless category.code_names.include?(name.to_s)
|
208
|
+
|
209
|
+
elsif name.is_a? Hash # we have a Hash, likely with the create options, eg. { :name => 'hi', :foo =>'bar' }
|
210
|
+
category.create name
|
211
|
+
|
212
|
+
else
|
213
|
+
raise "not sure how to create code in category #{ category.name } with: #{ name.inspect }"
|
214
|
+
end
|
215
|
+
|
216
|
+
# we have a name and values
|
217
|
+
else
|
218
|
+
if values.is_a? Hash and (name.is_a? String or name.is_a? Symbol) # we have a Hash, likely with the create options ... we'll merge the name in as :name and create
|
219
|
+
code = category[ name.to_s ]
|
220
|
+
if code
|
221
|
+
values.each do |attribute, new_value|
|
222
|
+
code.send "#{attribute}=", new_value # update values
|
223
|
+
end
|
224
|
+
else
|
225
|
+
code = category.create values.merge({ :name => name.to_s })
|
226
|
+
end
|
227
|
+
|
228
|
+
else
|
229
|
+
raise "not sure how to create code in category #{ category.name } with: #{ name.inspect }, #{ values.inspect }"
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
protected
|
240
|
+
|
241
|
+
def set_default_values
|
242
|
+
self.aux_code_id = 0 unless aux_code_id
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
%w( aux_codes/aux_code_class ).each {|lib| require lib } # define the class returned by #aux_code_class
|
@@ -0,0 +1,150 @@
|
|
1
|
+
#
|
2
|
+
# extend AuxCode to return a full-blown ActiveRecord class
|
3
|
+
#
|
4
|
+
class AuxCode
|
5
|
+
|
6
|
+
def aux_code_class &block
|
7
|
+
klass = Class.new(AuxCode) do
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
attr_accessor :aux_code_id, :aux_code
|
12
|
+
|
13
|
+
def aux_code
|
14
|
+
# @aux_code ||= AuxCode.find aux_code_id
|
15
|
+
AuxCode.find aux_code_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def [] code
|
19
|
+
aux_code[code]
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# this handles typical ActiveRecord::Base method_missing features, eg: aux_code.find_by_name 'foo'
|
24
|
+
#
|
25
|
+
# we wrap these methods in the scope of this aux code (category)
|
26
|
+
#
|
27
|
+
def method_missing_with_aux_code_scope name, *args, &block
|
28
|
+
if name.to_s[/^find/]
|
29
|
+
if name.to_s[/or_create_by_/]
|
30
|
+
name = "#{name}_and_aux_code_id".to_sym
|
31
|
+
args << self.aux_code_id
|
32
|
+
# method_missing_without_aux_code_scope name, *args
|
33
|
+
AuxCode.send name, *args, &block
|
34
|
+
else
|
35
|
+
with_scope(:find => { :conditions => ['aux_code_id = ?', self.aux_code_id] }) do
|
36
|
+
method_missing_without_aux_code_scope name, *args, &block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
else
|
40
|
+
method_missing_without_aux_code_scope name, *args, &block
|
41
|
+
end
|
42
|
+
rescue NoMethodError => ex
|
43
|
+
begin
|
44
|
+
aux_code.send name, *args, &block # try on the AuxCode instance for this class ...
|
45
|
+
rescue
|
46
|
+
raise ex
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias_method_chain :method_missing, :aux_code_scope
|
50
|
+
|
51
|
+
def count_with_aux_code_scope options = {}
|
52
|
+
with_scope(:find => { :conditions => ['aux_code_id = ?', self.aux_code_id] }) do
|
53
|
+
count_without_aux_code_scope options
|
54
|
+
end
|
55
|
+
end
|
56
|
+
alias_method_chain :count, :aux_code_scope
|
57
|
+
|
58
|
+
def find_with_aux_code_scope first_or_all, options = {}
|
59
|
+
with_scope(:find => { :conditions => ['aux_code_id = ?', self.aux_code_id] }) do
|
60
|
+
find_without_aux_code_scope first_or_all, options
|
61
|
+
end
|
62
|
+
end
|
63
|
+
alias_method_chain :find, :aux_code_scope
|
64
|
+
|
65
|
+
def create_with_aux_code_scope options = {}
|
66
|
+
create_without_aux_code_scope options.merge({ :aux_code_id => self.aux_code_id })
|
67
|
+
end
|
68
|
+
alias_method_chain :create, :aux_code_scope
|
69
|
+
|
70
|
+
def create_with_aux_code_scope! options = {}
|
71
|
+
create_without_aux_code_scope! options.merge({ :aux_code_id => self.aux_code_id })
|
72
|
+
end
|
73
|
+
alias_method_chain :create!, :aux_code_scope
|
74
|
+
|
75
|
+
def new_with_aux_code_scope options = {}
|
76
|
+
begin
|
77
|
+
new_without_aux_code_scope options.merge({ :aux_code_id => self.aux_code_id })
|
78
|
+
rescue ActiveRecord::UnknownAttributeError => ex
|
79
|
+
|
80
|
+
# we were likely passed some unknown meta attributes ... define them ...
|
81
|
+
meta_attribute_name = /unknown attribute: (.*)/.match(ex.message).captures.first
|
82
|
+
meta_attributes << meta_attribute_name
|
83
|
+
self.reload_meta_attributes!
|
84
|
+
new_with_aux_code_scope options # re-call ... WARNING ... might end up in infinite loop!
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
alias_method_chain :new, :aux_code_scope
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
klass.aux_code_id = self.id # the class needs to know its own aux_code_id
|
94
|
+
|
95
|
+
#
|
96
|
+
# add custom attributes
|
97
|
+
#
|
98
|
+
klass.class.class_eval do
|
99
|
+
|
100
|
+
# an array of valid meta attribute names
|
101
|
+
attr_accessor :meta_attributes
|
102
|
+
|
103
|
+
def attr_meta *attribute_names
|
104
|
+
@meta_attributes ||= []
|
105
|
+
@meta_attributes += attribute_names.map {|attribute_name| attribute_name.to_s }
|
106
|
+
@meta_attributes
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# class customizations (if block passed in)
|
111
|
+
klass.class_eval(&block) if block
|
112
|
+
|
113
|
+
# for each of the meta_attributes defined, create getter and setter methods
|
114
|
+
#
|
115
|
+
# CAUTION: the way we're currently doing this, this'll only work if attr_meta
|
116
|
+
# is set when you initially get the aux_code_class ... adding
|
117
|
+
# meta attributes later won't currently work!
|
118
|
+
#
|
119
|
+
klass.class_eval {
|
120
|
+
|
121
|
+
def self.reload_meta_attributes!
|
122
|
+
self.meta_attributes ||= []
|
123
|
+
|
124
|
+
self.meta_attributes.each do |meta_attribute|
|
125
|
+
|
126
|
+
unless self.respond_to? meta_attribute
|
127
|
+
define_method(meta_attribute) do
|
128
|
+
get_meta_attribute(meta_attribute)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
unless self.respond_to? "#{meta_attribute}="
|
133
|
+
define_method("#{meta_attribute}=") do |value|
|
134
|
+
set_meta_attribute(meta_attribute, value)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
reload_meta_attributes!
|
142
|
+
|
143
|
+
}
|
144
|
+
|
145
|
+
# wow, need to clean this up ...
|
146
|
+
|
147
|
+
klass
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# ActiveRecord migration for creating the aux_codes table
|
3
|
+
#
|
4
|
+
class AuxCodes
|
5
|
+
class CreateAuxCodes < ActiveRecord::Migration
|
6
|
+
|
7
|
+
def self.up
|
8
|
+
create_table :aux_codes, :comment => 'Auxilary Codes' do |t|
|
9
|
+
|
10
|
+
t.integer :aux_code_id, :comment => 'ID of parent aux code (Category)', :null => false
|
11
|
+
t.string :name, :comment => 'Name of Category code (or child code)', :null => false
|
12
|
+
|
13
|
+
# disabled for now, as they're not needed - no specs using this functionality
|
14
|
+
#
|
15
|
+
# %w( integer decimal string text boolean datetime ).each do |field_type|
|
16
|
+
# t.column field_type.to_sym, "#{field_type}_field"
|
17
|
+
# end
|
18
|
+
|
19
|
+
# this should be added conditionally, based on whether or not meta attributes are desired
|
20
|
+
t.text :meta, :comment => 'Serialized meta_attributes'
|
21
|
+
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.down
|
27
|
+
drop_table :aux_codes
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aux_codes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- remi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-20 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ActiveRecord plugin for easily managing lots of enumeration-type data
|
17
|
+
email: remi@remitaylor.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/aux_codes.rb
|
26
|
+
- lib/aux_codes/aux_code_class.rb
|
27
|
+
- lib/aux_codes/migration.rb
|
28
|
+
has_rdoc: true
|
29
|
+
homepage: http://github.com/remi/aux_codes
|
30
|
+
licenses: []
|
31
|
+
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "0"
|
42
|
+
version:
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.3.5
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: ActiveRecord plugin for easily managing lots of enumeration-type data
|
56
|
+
test_files: []
|
57
|
+
|