Class: AuxCode

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
lib/aux_codes.rb,
lib/aux_codes/aux_code_class.rb

Overview

extend AuxCode to return a full-blown ActiveRecord class

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.categoriesObject



108
109
110
# File 'lib/aux_codes.rb', line 108

def categories
  AuxCode.find_all_by_aux_code_id(0)
end

.category(category_object_or_id_or_name) ⇒ Object Also known as: []



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/aux_codes.rb', line 116

def category category_object_or_id_or_name
  obj = category_object_or_id_or_name
  return obj if obj.is_a?AuxCode
  return AuxCode.find(obj) if obj.is_a?Fixnum
  if obj.is_a?(String) || obj.is_a?(Symbol)
    obj = obj.to_s
    found = AuxCode.find_by_name_and_aux_code_id(obj, 0)
    if found.nil?
      # try replacing underscores with spaces and doing a 'LIKE' search
      found = AuxCode.find :first, :conditions => ["name LIKE ? AND aux_code_id = ?", obj.gsub('_', ' '), 0]
    end
    return found
  end
  raise "I don't know how to find an AuxCode of type #{ obj.class }"
end

.category_code_names(category_object_or_id_or_name) ⇒ Object



138
139
140
# File 'lib/aux_codes.rb', line 138

def category_code_names category_object_or_id_or_name
  category( category_object_or_id_or_name ).code_names
end

.category_codes(category_object_or_id_or_name) ⇒ Object Also known as: category_values



133
134
135
# File 'lib/aux_codes.rb', line 133

def category_codes category_object_or_id_or_name
  category( category_object_or_id_or_name ).codes
end

.category_namesObject



112
113
114
# File 'lib/aux_codes.rb', line 112

def category_names
  AuxCode.categories.map &:name
end

.create_classes!Object



142
143
144
145
146
# File 'lib/aux_codes.rb', line 142

def create_classes!
  AuxCode.categories.each do |category|
    Kernel::const_set category.class_name, category.aux_code_class
  end
end

.initObject

initialize AuxCodes … looks for config/aux_codes.yml and creates classes



182
183
184
185
186
187
188
189
190
# File 'lib/aux_codes.rb', line 182

def init # should eventually take configuration options (hash || block)
  if ActiveRecord::Base.connection.tables.include? 'aux_codes'
    aux_codes_yml = File.join 'config', 'aux_codes.yml'
    if File.file? aux_codes_yml
      load_file aux_codes_yml
      create_classes!
    end
  end
end

.load(hash) ⇒ Object

loads AuxCodes (creates them) from a Hash, keyed on the name of the aux code categories to create

hash: a Hash or an Array [ [key,value], [key,value] ] or anything with an enumerator

that'll work with `hash.each {|key,value| ... }`


198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/aux_codes.rb', line 198

def load hash
  return unless hash.is_a?Hash
  hash.each do |category_name, codes|
    category = AuxCode.find_or_create_by_name( category_name.to_s ).aux_code_class
    codes.each do |name, values|

      # only a name given
      if values.nil? or values.empty?
        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'
          category.create :name => name.to_s unless category.code_names.include?(name.to_s)

        elsif name.is_a? Hash # we have a Hash, likely with the create options, eg. { :name => 'hi', :foo =>'bar' }
          category.create name

        else
          raise "not sure how to create code in category #{ category.name } with: #{ name.inspect }"
        end

        # we have a name and values
      else
        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
          code = category[ name.to_s ]
          if code
            values.each do |attribute, new_value|
              code.send "#{attribute}=", new_value # update values
            end
          else
            code = category.create values.merge({ :name => name.to_s })
          end

        else
          raise "not sure how to create code in category #{ category.name } with: #{ name.inspect }, #{ values.inspect }"

        end
      end
    end
  end
end

.load_file(serialized_yaml_file_path) ⇒ Object



176
177
178
# File 'lib/aux_codes.rb', line 176

def load_file serialized_yaml_file_path
  load_yaml File.read(serialized_yaml_file_path)
end

.load_yaml(yaml_string) ⇒ Object



171
172
173
174
# File 'lib/aux_codes.rb', line 171

def load_yaml yaml_string
  require 'yaml'
  self.load YAML::load(yaml_string)
end

.method_missing_with_indifferent_hash_style_values(name, *args, &block) ⇒ Object

this allows us to say things like:

AuxCode.create :name => 'foo'

AuxCode.foo # should return the foo category aux code


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/aux_codes.rb', line 154

def method_missing_with_indifferent_hash_style_values name, *args, &block
  unless self.respond_to?:aux_code_id # in which case, this is a *derived* class, not AuxCode
    begin
      method_missing_without_indifferent_hash_style_values name, *args, &block
    rescue NoMethodError => ex
      begin
        self[name]
      rescue
        raise ex
      end
    end
  else
    method_missing_without_indifferent_hash_style_values name, *args, &block
  end
end

Instance Method Details

#[](attribute_or_code_name) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/aux_codes.rb', line 45

def [] attribute_or_code_name
  if attributes.include?attribute_or_code_name
    attributes[attribute_or_code_name]
  else
    found = codes.select {|c| c.name.to_s =~ /#{attribute_or_code_name}/ }
      if found.empty? # try case insensitive (sans underscores)
        found = codes.select {|c| c.name.downcase.gsub('_',' ').to_s =~ 
          /#{attribute_or_code_name.to_s.downcase.gsub('_',' ')}/ }
      end
    found.first if found
  end
end

#aux_code_class(&block) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/aux_codes/aux_code_class.rb', line 6

def aux_code_class &block
  klass = Class.new(AuxCode) do

    class << self

      attr_accessor :aux_code_id, :aux_code

      def aux_code
        # @aux_code ||= AuxCode.find aux_code_id
        AuxCode.find aux_code_id
      end

      def [] code
        aux_code[code]
      end

      #
      # this handles typical ActiveRecord::Base method_missing features, eg: aux_code.find_by_name 'foo'
      #
      # we wrap these methods in the scope of this aux code (category)
      #
      def method_missing_with_aux_code_scope name, *args, &block
        if name.to_s[/^find/]
          if name.to_s[/or_create_by_/]
            name = "#{name}_and_aux_code_id".to_sym
            args << self.aux_code_id
            # method_missing_without_aux_code_scope name, *args
            AuxCode.send name, *args, &block
          else
            with_scope(:find => { :conditions => ['aux_code_id = ?', self.aux_code_id] }) do
              method_missing_without_aux_code_scope name, *args, &block
            end
          end
        else
          method_missing_without_aux_code_scope name, *args, &block
        end
      rescue NoMethodError => ex
        begin
          aux_code.send name, *args, &block # try on the AuxCode instance for this class ...
        rescue
          raise ex
        end
      end
      alias_method_chain :method_missing, :aux_code_scope

      def count_with_aux_code_scope options = {}
        with_scope(:find => { :conditions => ['aux_code_id = ?', self.aux_code_id] }) do
          count_without_aux_code_scope options
        end
      end
      alias_method_chain :count, :aux_code_scope

      def find_with_aux_code_scope first_or_all, options = {}
        with_scope(:find => { :conditions => ['aux_code_id = ?', self.aux_code_id] }) do
          find_without_aux_code_scope first_or_all, options
        end
      end
      alias_method_chain :find, :aux_code_scope
      
      def create_with_aux_code_scope options = {}
        create_without_aux_code_scope options.merge({ :aux_code_id => self.aux_code_id })
      end
      alias_method_chain :create, :aux_code_scope
      
      def create_with_aux_code_scope! options = {}
        create_without_aux_code_scope! options.merge({ :aux_code_id => self.aux_code_id })
      end
      alias_method_chain :create!, :aux_code_scope
      
      def new_with_aux_code_scope options = {}
        begin
          new_without_aux_code_scope options.merge({ :aux_code_id => self.aux_code_id })
        rescue ActiveRecord::UnknownAttributeError => ex

          # we were likely passed some unknown meta attributes ... define them ...
          meta_attribute_name = /unknown attribute: (.*)/.match(ex.message).captures.first
          meta_attributes << meta_attribute_name
          self.reload_meta_attributes!
          new_with_aux_code_scope options # re-call ... WARNING ... might end up in infinite loop!

        end
      end
      alias_method_chain :new, :aux_code_scope

    end
  end

  klass.aux_code_id = self.id # the class needs to know its own aux_code_id

  # 
  # add custom attributes
  #
  klass.class.class_eval do

    # an array of valid meta attribute names
    attr_accessor :meta_attributes

    def attr_meta *attribute_names
      @meta_attributes ||= []
      @meta_attributes += attribute_names.map {|attribute_name| attribute_name.to_s }
      @meta_attributes
    end
  end

  # class customizations (if block passed in)
  klass.class_eval(&block) if block
  
  # for each of the meta_attributes defined, create getter and setter methods
  #
  # CAUTION: the way we're currently doing this, this'll only work if attr_meta 
  #          is set when you initially get the aux_code_class ... adding 
  #          meta attributes later won't currently work!
  #
  klass.class_eval {

    def self.reload_meta_attributes!
      self.meta_attributes ||= []

      self.meta_attributes.each do |meta_attribute|
        
        unless self.respond_to? meta_attribute
          define_method(meta_attribute) do
            get_meta_attribute(meta_attribute)
          end
        end

        unless self.respond_to? "#{meta_attribute}="
          define_method("#{meta_attribute}=") do |value|
            set_meta_attribute(meta_attribute, value)
          end
        end

      end
    end

    reload_meta_attributes!

  }

  # wow, need to clean this up ...

  klass
end

#class_nameObject



37
38
39
# File 'lib/aux_codes.rb', line 37

def class_name
  name.gsub(/[^[:alpha:]]/,'_').titleize.gsub(' ','').singularize
end

#code_namesObject



29
30
31
# File 'lib/aux_codes.rb', line 29

def code_names
  codes.map &:name
end

#deserialized_meta_hashObject



58
59
60
61
62
# File 'lib/aux_codes.rb', line 58

def deserialized_meta_hash
  require 'yaml'
  self.meta ||= ""
  YAML::load(self.meta) || { }
end

#get_meta_attribute(meta_attribute) ⇒ Object



64
65
66
# File 'lib/aux_codes.rb', line 64

def get_meta_attribute meta_attribute
  deserialized_meta_hash[meta_attribute.to_s]
end

#is_a_category?Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/aux_codes.rb', line 33

def is_a_category?
  aux_code_id == 0
end

#method_missing_with_indifferent_hash_style_values(name, *args, &block) ⇒ Object

this allows us to say things like:

foo = AuxCode.create :name => 'foo'
foo.codes.create :name => 'bar'

foo.bar # should return the bar aux code under the foo category

if bar doesn't exist, we throw a normal NoMethodError

this should check meta_attributes on the object too


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/aux_codes.rb', line 86

def method_missing_with_indifferent_hash_style_values name, *args, &block
  method_missing_without_indifferent_hash_style_values name, *args, &block
rescue NoMethodError => ex
  begin
    if name.to_s[/=$/]
      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
      save
    else
      code = self[name]
      code = self.get_meta_attribute(name) unless code
      raise ex unless code
      return code
    end
  rescue
    raise ex
  end
end

#set_meta_attribute(meta_attribute, value) ⇒ Object



68
69
70
71
72
73
# File 'lib/aux_codes.rb', line 68

def set_meta_attribute meta_attribute, value
  require 'yaml'
  meta_hash = deserialized_meta_hash
  meta_hash[meta_attribute.to_s] = value
  self.meta = meta_hash.to_yaml
end

#to_sObject



41
42
43
# File 'lib/aux_codes.rb', line 41

def to_s
  name
end