dm-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/aux_code_class.rb +163 -0
- data/lib/aux_codes.rb +272 -0
- metadata +56 -0
@@ -0,0 +1,163 @@
|
|
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
|
8
|
+
klass.send :include, Stuff
|
9
|
+
klass.class_eval do
|
10
|
+
|
11
|
+
storage_names[:default] = 'aux_codes'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def name
|
15
|
+
'AuxCode'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.codes
|
20
|
+
all
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.code_names
|
24
|
+
codes.map &:name
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
attr_accessor :aux_code_id, :aux_code
|
30
|
+
|
31
|
+
def aux_code
|
32
|
+
# @aux_code ||= AuxCode.find aux_code_id
|
33
|
+
AuxCode.get aux_code_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def [] code
|
37
|
+
aux_code[code]
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# this handles typical ActiveRecord::Base method_missing features, eg: aux_code.find_by_name 'foo'
|
42
|
+
#
|
43
|
+
# we wrap these methods in the scope of this aux code (category)
|
44
|
+
#
|
45
|
+
def method_missing_with_aux_code_scope name, *args, &block
|
46
|
+
method_missing_without_aux_code_scope name, *args, &block
|
47
|
+
rescue NoMethodError => ex
|
48
|
+
begin
|
49
|
+
aux_code.send name, *args, &block # try on the AuxCode instance for this class ...
|
50
|
+
rescue
|
51
|
+
raise ex
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method_chain :method_missing, :aux_code_scope
|
56
|
+
|
57
|
+
def count_with_aux_code_scope options = {}
|
58
|
+
with_scope :aux_code_id => self.aux_code_id do
|
59
|
+
count_without_aux_code_scope options
|
60
|
+
end
|
61
|
+
end
|
62
|
+
alias_method_chain :count, :aux_code_scope
|
63
|
+
|
64
|
+
def first_with_aux_code_scope options = {}
|
65
|
+
with_scope :aux_code_id => self.aux_code_id do
|
66
|
+
first_without_aux_code_scope options
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias_method_chain :first, :aux_code_scope
|
70
|
+
|
71
|
+
def all_with_aux_code_scope options = {}
|
72
|
+
with_scope :aux_code_id => self.aux_code_id do
|
73
|
+
all_without_aux_code_scope options
|
74
|
+
end
|
75
|
+
end
|
76
|
+
alias_method_chain :all, :aux_code_scope
|
77
|
+
|
78
|
+
def create_with_aux_code_scope options = {}
|
79
|
+
create_without_aux_code_scope options.merge({ :aux_code_id => self.aux_code_id })
|
80
|
+
end
|
81
|
+
alias_method_chain :create, :aux_code_scope
|
82
|
+
|
83
|
+
def create_with_aux_code_scope! options = {}
|
84
|
+
create_without_aux_code_scope! options.merge({ :aux_code_id => self.aux_code_id })
|
85
|
+
end
|
86
|
+
alias_method_chain :create!, :aux_code_scope
|
87
|
+
|
88
|
+
def new_with_aux_code_scope options = {}
|
89
|
+
begin
|
90
|
+
new_without_aux_code_scope options.merge({ :aux_code_id => self.aux_code_id })
|
91
|
+
rescue Exception => ex
|
92
|
+
|
93
|
+
# we were likely passed some unknown meta attributes ... define them ...
|
94
|
+
meta_attribute_name = /The attribute '(.*)' is not accessible in/.match(ex.message).captures.first
|
95
|
+
meta_attributes << meta_attribute_name
|
96
|
+
self.reload_meta_attributes!
|
97
|
+
new_with_aux_code_scope options # re-call ... WARNING ... might end up in infinite loop!
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
alias_method_chain :new, :aux_code_scope
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
klass.aux_code_id = self.id # the class needs to know its own aux_code_id
|
107
|
+
|
108
|
+
#
|
109
|
+
# add custom attributes
|
110
|
+
#
|
111
|
+
klass.class.class_eval do
|
112
|
+
|
113
|
+
# an array of valid meta attribute names
|
114
|
+
attr_accessor :meta_attributes
|
115
|
+
|
116
|
+
def attr_meta *attribute_names
|
117
|
+
@meta_attributes ||= []
|
118
|
+
@meta_attributes += attribute_names.map {|attribute_name| attribute_name.to_s }
|
119
|
+
@meta_attributes
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# class customizations (if block passed in)
|
124
|
+
klass.class_eval(&block) if block
|
125
|
+
|
126
|
+
# for each of the meta_attributes defined, create getter and setter methods
|
127
|
+
#
|
128
|
+
# CAUTION: the way we're currently doing this, this'll only work if attr_meta
|
129
|
+
# is set when you initially get the aux_code_class ... adding
|
130
|
+
# meta attributes later won't currently work!
|
131
|
+
#
|
132
|
+
klass.class_eval {
|
133
|
+
|
134
|
+
def self.reload_meta_attributes!
|
135
|
+
self.meta_attributes ||= []
|
136
|
+
|
137
|
+
self.meta_attributes.each do |meta_attribute|
|
138
|
+
|
139
|
+
unless self.respond_to? meta_attribute
|
140
|
+
define_method(meta_attribute) do
|
141
|
+
get_meta_attribute(meta_attribute)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
unless self.respond_to? "#{meta_attribute}="
|
146
|
+
define_method("#{meta_attribute}=") do |value|
|
147
|
+
set_meta_attribute(meta_attribute, value)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
reload_meta_attributes!
|
155
|
+
|
156
|
+
}
|
157
|
+
|
158
|
+
# wow, need to clean this up ...
|
159
|
+
|
160
|
+
klass
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
data/lib/aux_codes.rb
ADDED
@@ -0,0 +1,272 @@
|
|
1
|
+
%w( rubygems dm-core dm-validations dm-aggregates dm-timestamps ).each {|lib| require lib }
|
2
|
+
|
3
|
+
require 'activesupport' # just for alias_method_chain
|
4
|
+
|
5
|
+
#
|
6
|
+
# the basic AuxCode class (DataMapper::Resource)
|
7
|
+
#
|
8
|
+
class AuxCode
|
9
|
+
end
|
10
|
+
|
11
|
+
module Stuff
|
12
|
+
def self.included base
|
13
|
+
base.class_eval do
|
14
|
+
|
15
|
+
include DataMapper::Resource
|
16
|
+
|
17
|
+
property :id, DataMapper::Types::Serial
|
18
|
+
property :aux_code_id, Integer, :default => 0
|
19
|
+
property :name, String, :required => true, :unique => :aux_code_id
|
20
|
+
property :meta, DataMapper::Types::Text
|
21
|
+
|
22
|
+
# timestamps :at
|
23
|
+
|
24
|
+
belongs_to :aux_code
|
25
|
+
has n, :aux_codes
|
26
|
+
|
27
|
+
def self.name
|
28
|
+
'AuxCode'
|
29
|
+
end
|
30
|
+
|
31
|
+
alias code aux_code
|
32
|
+
alias category aux_code
|
33
|
+
alias codes aux_codes
|
34
|
+
|
35
|
+
# Helper methods to make DataMapper happy.
|
36
|
+
#
|
37
|
+
# In theory, we shouldn't use these in DataMapper but ... it's easy to port them
|
38
|
+
|
39
|
+
def self.find_all_by_aux_code_id aux_code_id
|
40
|
+
all :aux_code_id => aux_code_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.find_by_name_and_aux_code_id name, aux_code_id
|
44
|
+
first :name => name, :aux_code_id => aux_code_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.find_or_create_by_name name
|
48
|
+
first(:name => name) || create(:name => name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.find_by_name name
|
52
|
+
first :name => name
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.find_all_by_name name
|
56
|
+
all :name => name
|
57
|
+
end
|
58
|
+
|
59
|
+
def code_names
|
60
|
+
codes.map &:name
|
61
|
+
end
|
62
|
+
|
63
|
+
def is_a_category?
|
64
|
+
aux_code_id == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def class_name
|
68
|
+
name.gsub(/[^[:alpha:]]/,'_').titleize.gsub(' ','').singularize
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
name
|
73
|
+
end
|
74
|
+
|
75
|
+
def [] attribute_or_code_name
|
76
|
+
if attributes.include?attribute_or_code_name
|
77
|
+
attributes[attribute_or_code_name]
|
78
|
+
else
|
79
|
+
found = codes.select {|c| c.name.to_s =~ /#{attribute_or_code_name}/ }
|
80
|
+
if found.empty? # try case insensitive (sans underscores)
|
81
|
+
found = codes.select {|c| c.name.downcase.gsub('_',' ').to_s =~
|
82
|
+
/#{attribute_or_code_name.to_s.downcase.gsub('_',' ')}/ }
|
83
|
+
end
|
84
|
+
found.first if found
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def deserialized_meta_hash
|
89
|
+
require 'yaml'
|
90
|
+
self.meta ||= ""
|
91
|
+
YAML::load(self.meta) || { }
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_meta_attribute meta_attribute
|
95
|
+
deserialized_meta_hash[meta_attribute.to_s]
|
96
|
+
end
|
97
|
+
|
98
|
+
def set_meta_attribute meta_attribute, value
|
99
|
+
require 'yaml'
|
100
|
+
meta_hash = deserialized_meta_hash
|
101
|
+
meta_hash[meta_attribute.to_s] = value
|
102
|
+
self.meta = meta_hash.to_yaml
|
103
|
+
end
|
104
|
+
|
105
|
+
# this allows us to say things like:
|
106
|
+
#
|
107
|
+
# foo = AuxCode.create :name => 'foo'
|
108
|
+
# foo.codes.create :name => 'bar'
|
109
|
+
#
|
110
|
+
# foo.bar # should return the bar aux code under the foo category
|
111
|
+
#
|
112
|
+
# if bar doesn't exist, we throw a normal NoMethodError
|
113
|
+
#
|
114
|
+
# this should check meta_attributes on the object too
|
115
|
+
#
|
116
|
+
def method_missing_with_indifferent_hash_style_values name, *args, &block
|
117
|
+
method_missing_without_indifferent_hash_style_values name, *args, &block
|
118
|
+
rescue NoMethodError => ex
|
119
|
+
begin
|
120
|
+
if name.to_s[/=$/]
|
121
|
+
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
|
122
|
+
save
|
123
|
+
else
|
124
|
+
code = self[name]
|
125
|
+
code = self.get_meta_attribute(name) unless code
|
126
|
+
raise ex unless code
|
127
|
+
return code
|
128
|
+
end
|
129
|
+
rescue
|
130
|
+
raise ex
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
alias_method_chain :method_missing, :indifferent_hash_style_values
|
135
|
+
|
136
|
+
# class methods
|
137
|
+
class << self
|
138
|
+
|
139
|
+
def categories
|
140
|
+
AuxCode.find_all_by_aux_code_id(0)
|
141
|
+
end
|
142
|
+
|
143
|
+
def category_names
|
144
|
+
AuxCode.categories.map &:name
|
145
|
+
end
|
146
|
+
|
147
|
+
def category category_object_or_id_or_name
|
148
|
+
obj = category_object_or_id_or_name
|
149
|
+
return obj if obj.is_a?(AuxCode)
|
150
|
+
return AuxCode.get(obj) if obj.is_a?(Fixnum)
|
151
|
+
if obj.is_a?(String) || obj.is_a?(Symbol)
|
152
|
+
obj = obj.to_s
|
153
|
+
found = AuxCode.find_by_name_and_aux_code_id(obj, 0)
|
154
|
+
if found.nil?
|
155
|
+
# try replacing underscores with spaces and doing a 'LIKE' search
|
156
|
+
found = AuxCode.first :name.like => obj.gsub('_', ' '), :aux_code_id => 0
|
157
|
+
end
|
158
|
+
return found
|
159
|
+
end
|
160
|
+
raise "I don't know how to find an AuxCode of type #{ obj.class }"
|
161
|
+
end
|
162
|
+
alias [] category
|
163
|
+
|
164
|
+
def category_codes category_object_or_id_or_name
|
165
|
+
category( category_object_or_id_or_name ).codes
|
166
|
+
end
|
167
|
+
alias category_values category_codes
|
168
|
+
|
169
|
+
def category_code_names category_object_or_id_or_name
|
170
|
+
category( category_object_or_id_or_name ).code_names
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_classes!
|
174
|
+
AuxCode.categories.each do |category|
|
175
|
+
Object.const_set category.class_name, category.aux_code_class
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# this allows us to say things like:
|
180
|
+
#
|
181
|
+
# AuxCode.create :name => 'foo'
|
182
|
+
#
|
183
|
+
# AuxCode.foo # should return the foo category aux code
|
184
|
+
#
|
185
|
+
def method_missing_with_indifferent_hash_style_values name, *args, &block
|
186
|
+
begin
|
187
|
+
method_missing_without_indifferent_hash_style_values name, *args, &block
|
188
|
+
rescue NoMethodError => ex
|
189
|
+
begin
|
190
|
+
self[name]
|
191
|
+
rescue
|
192
|
+
raise ex
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
alias_method_chain :method_missing, :indifferent_hash_style_values
|
198
|
+
|
199
|
+
def load_yaml yaml_string
|
200
|
+
require 'yaml'
|
201
|
+
self.load_from YAML::load(yaml_string)
|
202
|
+
end
|
203
|
+
|
204
|
+
def load_file serialized_yaml_file_path
|
205
|
+
load_yaml File.read(serialized_yaml_file_path)
|
206
|
+
end
|
207
|
+
|
208
|
+
# initialize AuxCodes ... looks for config/aux_codes.yml
|
209
|
+
# and creates classes
|
210
|
+
def init # should eventually take configuration options (hash || block)
|
211
|
+
aux_codes_yml = File.join 'config', 'aux_codes.yml'
|
212
|
+
if File.file? aux_codes_yml
|
213
|
+
load_file aux_codes_yml
|
214
|
+
create_classes!
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# loads AuxCodes (creates them) from a Hash, keyed on the name of the aux code categories to create
|
220
|
+
#
|
221
|
+
# hash: a Hash or an Array [ [key,value], [key,value] ] or anything with an enumerator
|
222
|
+
# that'll work with `hash.each {|key,value| ... }`
|
223
|
+
#
|
224
|
+
def load_from hash
|
225
|
+
return unless hash.is_a?(Hash)
|
226
|
+
hash.each do |category_name, codes|
|
227
|
+
category = AuxCode.find_or_create_by_name( category_name.to_s ).aux_code_class
|
228
|
+
codes.each do |name, values|
|
229
|
+
|
230
|
+
# only a name given
|
231
|
+
if values.nil? or values.empty?
|
232
|
+
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'
|
233
|
+
category.create :name => name.to_s unless category.code_names.include?(name.to_s)
|
234
|
+
|
235
|
+
elsif name.is_a? Hash # we have a Hash, likely with the create options, eg. { :name => 'hi', :foo =>'bar' }
|
236
|
+
category.create name
|
237
|
+
|
238
|
+
else
|
239
|
+
raise "not sure how to create code in category #{ category.name } with: #{ name.inspect }"
|
240
|
+
end
|
241
|
+
|
242
|
+
# we have a name and values
|
243
|
+
else
|
244
|
+
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
|
245
|
+
code = category[ name.to_s ]
|
246
|
+
if code
|
247
|
+
values.each do |attribute, new_value|
|
248
|
+
code.send "#{attribute}=", new_value # update values
|
249
|
+
end
|
250
|
+
else
|
251
|
+
code = category.create values.merge({ :name => name.to_s })
|
252
|
+
end
|
253
|
+
|
254
|
+
else
|
255
|
+
raise "not sure how to create code in category #{ category.name } with: #{ name.inspect }, #{ values.inspect }"
|
256
|
+
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
AuxCode.send :include, Stuff
|
271
|
+
|
272
|
+
require File.dirname(__FILE__) + '/aux_codes/aux_code_class' # define the class returned by #aux_code_class
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-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: DataMapper 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
|
+
has_rdoc: true
|
28
|
+
homepage: http://github.com/remi/aux_codes
|
29
|
+
licenses: []
|
30
|
+
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "0"
|
41
|
+
version:
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
requirements: []
|
49
|
+
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.3.5
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: DataMapper plugin for easily managing lots of enumeration-type data
|
55
|
+
test_files: []
|
56
|
+
|