Class: ConstantRecord::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/constantrecord.rb

Overview

ConstantRecord::Base is a tiny ActiveRecord substitute for small, never changing database tables.

Usage:

class Currency < ConstantRecord::Base
  data 'EUR', 'USD', 'CAD', 'GBP', 'CHF'
end

or

class MoreDetailedCurrency < ConstantRecord::Base
  columns :name, :description
  data ['EUR', 'Euro'],
       ['USD', 'US Dollar'],
       ['CAD', 'Canadian Dollar'],
       ['GBP', 'British Pound sterling'],
       ['CHF', 'Swiss franc']
end

To show all records in a HTML select field, use:

<%= f.select :currency_id, Currency.options_for_select %>

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id, *values) ⇒ Base

Constructor. Call with the id plus a list of the values.

MyConstantRecord.new(1, 'value of column #1', 2, 3.333)


63
64
65
66
67
68
69
70
71
72
# File 'lib/constantrecord.rb', line 63

def initialize(id, *values)
  @id = id

  return if values.empty?

  #  set the instance variables
  get_columns.each do |key, value|
    instance_variable_set("@#{key}", values[value])
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object

Keep this to spot problems in integration with ActiveRecord



203
204
205
206
# File 'lib/constantrecord.rb', line 203

def method_missing(symbol, *args)  #:nodoc:
  self.class.log(:debug, "#{self.class}#method_missing(:#{symbol})")
  super symbol, *args
end

Instance Attribute Details

#idObject

:nodoc:



36
37
38
# File 'lib/constantrecord.rb', line 36

def id
  @id
end

#nameObject

:nodoc:



36
37
38
# File 'lib/constantrecord.rb', line 36

def name
  @name
end

Class Method Details

.[](name) ⇒ Object

shortcut to retrieve value for name - eases dynamic lookup



136
137
138
139
# File 'lib/constantrecord.rb', line 136

def self.[](name)
  ret = @data.detect{|datum| datum.first == name}
  ret ? ret.last : raise(ConstantNotFound, "No such #{self.name} constant: #{name}")
end

.all(*args) ⇒ Object

shortcut to #find(:all)



121
122
123
# File 'lib/constantrecord.rb', line 121

def self.all(*args)
  find_all
end

.columns(*args) ⇒ Object

Set the column names of the constant table. Default is one column called name



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/constantrecord.rb', line 40

def self.columns(*args)
  # remove the default column name
  undef_method :name
  class << self
    undef_method :names
  end

  col_ar = build_column_methods(*args)

  @columns = Hash[*col_ar]

  build_array_over_column_methods
end

.count(*args) ⇒ Object

Implement count. Warning: :conditions are not supported!

Raises:

  • (TypeError)


142
143
144
145
146
147
148
149
150
# File 'lib/constantrecord.rb', line 142

def self.count(*args)
  selector = args[0] || :all
  raise TypeError.new("#{self}.count failed!\nArguments: #{args.inspect}") unless selector.kind_of?(Symbol)

  #  ignore conditions on :all
  return @data.size if selector == :all

  raise ArgumentError.new("#{self}.count failed!\nArguments: #{args.inspect}")
end

.data(*args) ⇒ Object

Set the data. Arguments must be an Array.



55
56
57
# File 'lib/constantrecord.rb', line 55

def self.data(*args)
  @data = args.collect{|arg| arg.kind_of?(Array) ? arg : [arg]}
end

.find(*args) ⇒ Object

Implement find. Warning: :conditions are only supported with :first!

Raises:

  • (TypeError)


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
# File 'lib/constantrecord.rb', line 75

def self.find(*args)
  selector = args[0]

  #  find might get called on constant_record_id.nil? == true
  return nil if selector.nil?

  raise TypeError.new("#{self}.find failed!\nArguments: #{args.inspect}") unless selector.kind_of?(Symbol) || selector.kind_of?(Fixnum)

  case selector
  when :all
    #  ignore conditions on :all
    return find_all if selector == :all
  when :first
    #  no conditions given, return the first record
    return self.new(1, *@data[0]) if args.size == 1

    conditions = args[1][:conditions]

    raise TypeError.new("#{self}.find failed!\nArguments: #{args.inspect}") unless conditions.kind_of?(Hash) && conditions.size == 1

    compare_col_nr = get_columns[conditions.keys[0]]
    raise "Unknown column :#{conditions.keys[0]}" unless compare_col_nr

    @data.each_with_index do |datum, i|
      #  some special handling to integers
      cond_compare = if datum[compare_col_nr].kind_of?(Integer)
        conditions.values[0].to_i
      else
        #  leave anything else as it is
        conditions.values[0]
      end
      return self.new(i + 1, *datum) if datum[compare_col_nr] == cond_compare
    end

    return nil
  when :last
    return self.new(@data.size, *@data[-1])
  else
    #  ignore conditions if id is given as the first argument
    return find_by_id(selector) if selector.kind_of?(Fixnum)
  end

  raise ArgumentError.new("#{self}.find failed!\nArguments: #{args.inspect}")
end

.first(*args) ⇒ Object

shortcut to #find(:first)



126
127
128
# File 'lib/constantrecord.rb', line 126

def self.first(*args)
  find(:first, *args)
end

.idsObject

Returns an array of all ids



320
321
322
323
324
# File 'lib/constantrecord.rb', line 320

def self.ids
  ret = []
  @data.length.times{|n| ret << n+1}
  ret
end

.last(*args) ⇒ Object

shortcut to #find(:last)



131
132
133
# File 'lib/constantrecord.rb', line 131

def self.last(*args)
  find(:last, *args)
end

.loggerObject

Get the logger



327
328
329
# File 'lib/constantrecord.rb', line 327

def self.logger
  @@logger
end

.logger=(value) ⇒ Object

Set the logger; ConstantRecord will try to use the Rails logger, if it’s there. Otherwise an error will be raised; set your own logger by calling ConstantRecord::Base.logger = MyAwesomeLogger.new



334
335
336
# File 'lib/constantrecord.rb', line 334

def self.logger=(value)
  @@logger = value
end

.match(*args) ⇒ Object

Rails 3.0: this method is checked in ActiveRecord::Base.compute_type



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

def self.match(*args)
  true
end

.method_missing(symbol, *args) ⇒ Object

Handle find_by_xxx calls on the class



216
217
218
219
220
221
222
# File 'lib/constantrecord.rb', line 216

def self.method_missing(symbol, *args)  #:nodoc:
  if /^find_by_([_a-zA-Z]\w*)$/ =~ (symbol.to_s)
    return find(:first, :conditions => {$1.to_sym => args[0]})
  end
  self.log(:debug, "#{self}::method_missing(:#{symbol})")
  super symbol, *args
end

.namesObject

Returns an array of the first column of your data. This method will be removed, if you override your class with columns, that do not include a column called :name



315
316
317
# File 'lib/constantrecord.rb', line 315

def self.names
  @data.map(&:first)
end

.options_for_select(options = {}) ⇒ Object

Creates options for a select box in a form. The result is basically the same as the following code with ActiveRecord:

MyActiveRecord.find(:all).collect{|obj| [obj.name, obj.id]}

Usage

With the class:

class Currency < ConstantRecord::Base
  columns :name, :description
  data ['EUR', 'Euro'],
       ['USD', 'US Dollar']
end

The following erb code:

<%= f.select :currency_id, Currency.options_for_select %>

Results to:

<select id="invoice_currency_id" name="invoice[currency_id]">
  <option value="1">EUR</option>
  <option value="2">USD</option>
</select>

While:

<%= f.select :currency_id, Currency.options_for_select(
  :display => Proc.new { |obj| "#{obj.name} (#{obj.description})" },
  :value => :name, :include_null => true,
  :null_text => 'Please choose one', :null_value => nil ) %>

Results to:

<select id="invoice_currency_id" name="invoice[currency_id]">
  <option value="">Please choose one</option>
  <option value="EUR">EUR (Euro)</option>
  <option value="USD">USD (US Dollar)</option>
</select>

Options

:display

The attribute to call to display the text in the select box or a Proc object.

:value

The value to use for the option value. Default is the id of the record.

:include_null

Make an entry with the value 0 in the selectbox. Default is false.

:null_text

The text to show with on value 0. Default is ‘-’.

:null_value

The value of the null option. Default is 0.



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/constantrecord.rb', line 284

def self.options_for_select(options = {})
  display = options[:display] || get_columns.invert[0]
  raise "#{self}.options_for_select: :display must be either Symbol or Proc." unless display.kind_of?(Symbol) ||display.kind_of?(Proc)

  if display.kind_of?(Symbol)
    display_col_nr = get_columns[display]
    raise "Unknown column :#{display}" unless display_col_nr
  end

  value = options[:value] || :id

  i = 0
  result = @data.collect do |datum|
    i += 1
    obj = self.new(i, *datum)
    option_show = display.kind_of?(Symbol) ? datum[display_col_nr] : display.call(obj)
    option_value = value == :id ? i : obj.send(value)

    [option_show, option_value]
  end

  if options[:include_null] == true
    result.unshift [ options[:null_text] || '-', options.key?(:null_value) ? options[:null_value] : 0 ]
  end

  result
end

.respond_to?(symbol) ⇒ Boolean

Keep this to spot problems in integration with ActiveRecord

Returns:

  • (Boolean)


225
226
227
228
229
# File 'lib/constantrecord.rb', line 225

def self.respond_to?(symbol)  #:nodoc:
  result = super symbol
  self.log(:debug, "#{self}::respond_to?(:#{symbol}) => #{result}") if !result
  result
end

.tableObject

Show output in the form of ‘SELECT * FROM tablename;`



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/constantrecord.rb', line 177

def self.table
  #  get columns in the form of {0 => :id, 1 => :name, ...}
  cols = {:id => 0}.merge(Hash[*(get_columns.collect{|col_name, index| [col_name, index + 1]}.flatten)]).invert.sort

  #  calculate the maximum width of each column
  max_size = []
  cols.each do |index, name|
    woci = width_of_column(index)
    max_size << (woci > name.to_s.length ? woci : name.to_s.length)
  end

  output = ''
  #  build table header
  output += '+-' + max_size.collect{|o| '-' * o}.join('-+-') + "-+\n"
  output += '| ' + cols.collect{|o| o[1].to_s.ljust(max_size[o[0]])}.join(' | ') + " |\n"
  output += '+-' + max_size.collect{|o| '-' * o}.join('-+-') + "-+\n"
  #  build table data
  @data.each_with_index do |row, row_number|
    output += '| ' + (row_number + 1).to_s.ljust(max_size[0]) + ' | '
    index = 0
    output += row.collect{|o| index += 1; o.to_s.ljust(max_size[index])}.join(' | ') + " |\n"
  end
  output += '+-' + max_size.collect{|o| '-' * o}.join('-+-') + "-+\n"
end

Instance Method Details

#[](attr) ⇒ Object



152
153
154
# File 'lib/constantrecord.rb', line 152

def [](attr)
  instance_variable_get("@#{attr}")
end

#destroyed?Boolean

A ConstantRecord will never be destroyed

Returns:

  • (Boolean)


167
168
169
# File 'lib/constantrecord.rb', line 167

def destroyed? #:nodoc:
  false
end

#empty?Boolean

A ConstantRecord should never be empty

Returns:

  • (Boolean)


162
163
164
# File 'lib/constantrecord.rb', line 162

def empty?  #:nodoc:
  false
end

#new_record?Boolean

A ConstantRecord will never be a new record

Returns:

  • (Boolean)


157
158
159
# File 'lib/constantrecord.rb', line 157

def new_record?  #:nodoc:
  false
end

#respond_to?(symbol) ⇒ Boolean

Keep this to spot problems in integration with ActiveRecord

Returns:

  • (Boolean)


209
210
211
212
213
# File 'lib/constantrecord.rb', line 209

def respond_to?(symbol)  #:nodoc:
  result = super(symbol)
  self.class.log(:debug, "#{self.class}#respond_to?(:#{symbol}) => #{result}") if !result
  result
end