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