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