dm-aux_codes 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+