constantrecord 0.0.1

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/Manifest ADDED
@@ -0,0 +1,6 @@
1
+ constantrecord.gemspec
2
+ lib/constantrecord.rb
3
+ test/constantrecord_test.rb
4
+ Manifest
5
+ Rakefile
6
+ README.rdoc
data/README.rdoc ADDED
@@ -0,0 +1,86 @@
1
+ = ConstantRecord
2
+
3
+ == Usage
4
+
5
+ class Currency < ConstantRecord::Base
6
+ data 'EUR', 'USD', 'CAD', 'GBP', 'CHF'
7
+ end
8
+
9
+ or
10
+
11
+ class MoreDetailedCurrency < ConstantRecord::Base
12
+ columns :short, :description
13
+ data ['EUR', 'Euro'],
14
+ ['USD', 'US Dollar'],
15
+ ['CAD', 'Canadian Dollar'],
16
+ ['GBP', 'British Pound sterling'],
17
+ ['CHF', 'Swiss franc']
18
+ end
19
+
20
+
21
+ Inside ActiveRecord, it works just like a "normal" ActiveRecord association:
22
+
23
+ class Invoice < ActiveRecord::Base
24
+ belongs_to :currency # via currency_id database column
25
+
26
+ def print_currency
27
+ # Currency#name is the default attribute
28
+ puts self.currency.name if self.currency
29
+ end
30
+ end
31
+
32
+ ConstantRecord is meant as a substitute for ActiveRecord for tables, that will
33
+ never change in the lifespan of a mongrel process. I.e. an application might
34
+ let the user select a state of the US. Most applications will never have a
35
+ feature where a user/admin can edit the list of US states. They are considered
36
+ constant.
37
+
38
+ In my current application, my main/parent class has about 35 child classes,
39
+ that use over 40 different constant classes. The use of ConstantRecord meant
40
+ an estimated 15 to 20 per cent speed gain for my application.
41
+
42
+ ConstantRecord classes don't need accociations:
43
+
44
+ class CanadianProvince < ConstantRecord::Base
45
+ data = 'Alberta', ...
46
+ has_many :people # is not necessary, will not work
47
+ end
48
+
49
+ If you want to fetch all people living in Alberta, use:
50
+
51
+ Person.find(:all, :conditions =>
52
+ {:province_id => CanadianProvince.find_by_name('Alberta').id}
53
+
54
+ If you ever think, ConstantRecord lacks key features of ActiveRecord, (submit
55
+ a patch or) create the table and derive you class from ActiveRecord instead.
56
+ This is a key design objective for ConstantRecord.
57
+
58
+ == Features
59
+
60
+ Currency.find(1) >> #<Currency:0x332cbf4 @id=1, @name="EUR">
61
+
62
+ Just as in a database table, #id starts with 1.
63
+ When +data+ is a simple Array, #name is the default attribute.
64
+
65
+ MoreDetailedCurrency.find(3) >> #<MoreDetailedCurrency:0x2c8d94c
66
+ @description="Canadian Dollar",
67
+ @id=3, @short="CAD">
68
+
69
+ Currency.find_by_name('CHF') >> #<Currency:0x2c86ef8 @id=3, @name="CHF">
70
+
71
+ MoreDetailedCurrency.find_by_short('CAD')
72
+ >> #<MoreDetailedCurrency:0x2c8d94c
73
+ @description="Canadian Dollar",
74
+ @id=3, @short="CAD">
75
+
76
+ Currency.count :all >> 5
77
+
78
+
79
+ There is one special feature, that is not ActiveRecord compatible. In a Form
80
+ you can use:
81
+
82
+ <%= f.select :currency_id, Currency.options_for_select %>
83
+
84
+ or
85
+
86
+ <%= f.select :currency_id, Currency.options_for_select(:include_null => true) %>
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # Rakefile for constantrecord
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'echoe'
5
+
6
+ Echoe.new('constantrecord', '0.0.1') do |p|
7
+ p.description = "A tiny ActiveRecord substitute for small, never changing database tables."
8
+ p.url = "http://github.com/ChristophPetschnig/constantrecord"
9
+ p.author = "Christoph Petschnig"
10
+ p.email = "info@purevirtual.de"
11
+ p.ignore_pattern = ["tmp/*", "script/*", "rdoc/*", "pkg/*"]
12
+ p.development_dependencies = []
13
+ p.rdoc_pattern = /^(lib|bin|tasks|ext)|^README.rdoc|^CHANGELOG|^TODO|^MIT-LICENSE|^COPYING$/
14
+ end
15
+
16
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
17
+
18
+
19
+ task :default => :test
20
+
21
+ require 'rake/testtask'
22
+
23
+
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{constantrecord}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Christoph Petschnig"]
9
+ s.date = %q{2009-03-21}
10
+ s.description = %q{A tiny ActiveRecord substitute for small, never changing database tables.}
11
+ s.email = %q{info@purevirtual.de}
12
+ s.extra_rdoc_files = ["lib/constantrecord.rb", "README.rdoc"]
13
+ s.files = ["constantrecord.gemspec", "lib/constantrecord.rb", "test/constantrecord_test.rb", "Manifest", "Rakefile", "README.rdoc"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/ChristophPetschnig/constantrecord}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Constantrecord", "--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{constantrecord}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{A tiny ActiveRecord substitute for small, never changing database tables.}
21
+ s.test_files = ["test/constantrecord_test.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ else
29
+ end
30
+ else
31
+ end
32
+ end
@@ -0,0 +1,252 @@
1
+ #--
2
+ # Copyright (c) 2009 Christoph Petschnig
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module ConstantRecord
25
+ # ConstantRecord::Base is a tiny ActiveRecord substitute for small, never
26
+ # changing database tables.
27
+ #
28
+ # == Usage:
29
+ #
30
+ # class Currency < ConstantRecord::Base
31
+ # data 'EUR', 'USD', 'CAD', 'GBP', 'CHF'
32
+ # end
33
+ #
34
+ # or
35
+ #
36
+ # class MoreDetailedCurrency < ConstantRecord::Base
37
+ # columns :short, :description
38
+ # data ['EUR', 'Euro'],
39
+ # ['USD', 'US Dollar'],
40
+ # ['CAD', 'Canadian Dollar'],
41
+ # ['GBP', 'British Pound sterling'],
42
+ # ['CHF', 'Swiss franc']
43
+ # end
44
+ #
45
+ # To show all records in a HTML select field, use:
46
+ #
47
+ # <%= f.select :currency_id, Currency.options_for_select %>
48
+ #
49
+ class Base
50
+
51
+ private
52
+ attr_writer :id, :name
53
+
54
+ public
55
+ attr_reader :id, :name
56
+
57
+ # Set the column names of the constant table.
58
+ # Default is one column called `name`
59
+ def self.columns(*args)
60
+ # remove the default column name
61
+ undef_method :name
62
+
63
+ i = 0
64
+ col_ar = args.collect do |column|
65
+ raise TypeError.new("You can only pass Symbol or String object to #{self}::columns.") unless column.kind_of?(Symbol) || column.kind_of?(String)
66
+
67
+ class_eval do
68
+ private
69
+ attr_writer column
70
+ public
71
+ attr_reader column
72
+ end
73
+
74
+ i += 1
75
+ [column.to_sym, i - 1]
76
+ end.flatten
77
+
78
+ @columns = Hash[*col_ar]
79
+ end
80
+
81
+ # Set the data. Arguments must be an Array.
82
+ def self.data(*args)
83
+ @data = args.collect{|arg| arg.kind_of?(Array) ? arg : [arg]}
84
+ end
85
+
86
+ # Constructor; call with
87
+ # MyConstantRecord.new(1, 'value_of_name')
88
+ # or
89
+ # MyConstantRecord.new(1, 'values of column 1', 2, 3.333)
90
+ def initialize(id, *values)
91
+ @id = id
92
+
93
+ return if values.empty?
94
+
95
+ # set the instance variables
96
+ get_columns.each do |key, value|
97
+ instance_variable_set("@#{key}", values[value])
98
+ end
99
+ end
100
+
101
+ # Implement +find+. Warning: conditions are only supported with+:first+!
102
+ def self.find(*args)
103
+ selector = args[0]
104
+
105
+ # find might get called on constant_record_id.nil? == true
106
+ return nil if selector.nil?
107
+
108
+ raise TypeError.new("#{self}.find failed!\nArguments:#{args.inspect}") unless selector.kind_of?(Symbol) || selector.kind_of?(Fixnum)
109
+
110
+ if selector == :first
111
+ conditions = args[1][:conditions]
112
+
113
+ raise TypeError.new("#{self}.find failed!\nArguments:#{args.inspect}") unless conditions.kind_of?(Hash) && conditions.size == 1
114
+
115
+ compare_col_nr = get_columns[conditions.keys[0]]
116
+ raise "Unknown column :#{conditions.keys[0]}" unless compare_col_nr
117
+
118
+ @data.each_with_index do |datum, i|
119
+ return self.new(i, *datum) if datum[compare_col_nr] == conditions.values[0]
120
+ end
121
+
122
+ return nil
123
+ end
124
+
125
+ # ignore conditions on :all
126
+ return find_all if selector == :all
127
+
128
+ # ignore conditions if id is given as the first argument
129
+ return find_by_id(selector) if selector.kind_of?(Fixnum)
130
+
131
+ raise "#{self}.find failed!\nArguments:#{args.inspect}"
132
+ end
133
+
134
+ # Implement +count+. Warning: conditions are not supported!
135
+ def self.count(*args)
136
+ selector = args[0] || :all
137
+ raise TypeError.new("#{self}.find failed!\nArguments:#{args.inspect}") unless selector.kind_of?(Symbol)
138
+
139
+ # ignore conditions on :all
140
+ return @data.size if selector == :all
141
+
142
+ raise "#{self}.find failed!\nArguments:#{args.inspect}"
143
+ end
144
+
145
+ # A ConstantRecord will never be a new record
146
+ def new_record?
147
+ false
148
+ end
149
+
150
+ # A ConstantRecord should never be empty
151
+ def empty?
152
+ false
153
+ end
154
+
155
+ # Keep this to spot problems in integration with ActiveRecord
156
+ def method_missing(symbol, *args) #:nodoc:
157
+ p "ConstantRecord::Base#method_missing(#{symbol})"
158
+ super symbol, *args
159
+ end
160
+
161
+ # Keep this to spot problems in integration with ActiveRecord
162
+ def respond_to?(symbol) #:nodoc:
163
+ result = super(symbol)
164
+ p "ConstantRecord::Base#respond_to?(#{symbol}) => #{result}(#{self.class})" unless result
165
+ result
166
+ end
167
+
168
+ # Handle +find_by_xxx+ calls on the class
169
+ def self.method_missing(symbol, *args) #:nodoc:
170
+ if /^find_by_([_a-zA-Z]\w*)$/ =~ (symbol.to_s)
171
+ return find(:first, :conditions => {$1.to_sym => args[0]})
172
+ end
173
+ p "ConstantRecord::Base::method_missing(#{symbol})"
174
+ super symbol, *args
175
+ end
176
+
177
+ # Keep this to spot problems in integration with ActiveRecord
178
+ def self.respond_to?(symbol) #:nodoc:
179
+ result = super symbol
180
+ p "ConstantRecord::Base::respond_to?(#{symbol}) => #{result}(#{self.class})" unless result
181
+ result
182
+ end
183
+
184
+ # Creates options for a select box in a form
185
+ # options:
186
+ # <tt>:display</tt> The attribute to call to display the text in the select box or
187
+ # a Proc object:
188
+ # :display => Proc.new{ |obj| "#{obj.name} (#{obj.description})" }
189
+ # <tt>:value</tt> The value to use for the option value. Default is the id of the record.
190
+ # <tt>:include_null</tt> Make an entry with the value 0 in the selectbox. Default is +false+.
191
+ # <tt>:null_text</tt> The text to show with on value 0. Default is '-'.
192
+ # <tt>:null_value</tt> The value of the null option. Default is 0.
193
+ def self.options_for_select(options = {})
194
+ display = options[:display] || get_columns.keys[0]
195
+ raise "#{self}.options_for_select: :display must be either Symbol or Proc." unless display.kind_of?(Symbol) ||display.kind_of?(Proc)
196
+
197
+ if display.kind_of?(Symbol)
198
+ display_col_nr = get_columns[display]
199
+ raise "Unknown column :#{conditions.keys[0]}" unless display_col_nr
200
+ end
201
+
202
+ value = options[:value] || :id
203
+
204
+ i = 0
205
+ result = @data.collect do |datum|
206
+ i += 1
207
+ obj = self.new(i, *datum)
208
+ option_show = display.kind_of?(Symbol) ? datum[display_col_nr] : display.call(obj)
209
+ option_value = value == :id ? i : obj.send(value)
210
+
211
+ [option_show, option_value]
212
+ end
213
+
214
+ if options[:include_null] == true
215
+ result.unshift [ options[:null_text] || '-', options.key?(:null_value) ? options[:null_value] : 0 ]
216
+ end
217
+
218
+ result
219
+ end
220
+
221
+ private
222
+
223
+ # Get the name of the columns or the default value
224
+ def self.get_columns
225
+ # if columns were not set, the default value is one column
226
+ # with the name of "name"
227
+ @columns || { :name => 0 }
228
+ end
229
+
230
+ def get_columns #:nodoc:
231
+ self.class.get_columns
232
+ end
233
+
234
+ # Implementation of +find+(:all)
235
+ def self.find_all #:nodoc:
236
+ i = 0
237
+ @data.collect do |datum|
238
+ i += 1
239
+ self.new(i, *datum)
240
+ end
241
+ end
242
+
243
+ # Implementation of +find+(:id)
244
+ def self.find_by_id(id) #:nodoc:
245
+ # check for valid range of selector
246
+ return nil if id <= 0 || id > @data.size
247
+
248
+ self.new(id, *@data[id - 1])
249
+ end
250
+
251
+ end
252
+ end
@@ -0,0 +1,76 @@
1
+ require 'constantrecord'
2
+ require 'test/unit'
3
+
4
+ class SimpleClass < ConstantRecord::Base
5
+ data 'Lithuania', 'Latvia', 'Estonia'
6
+ end
7
+
8
+ class SimpleClass2 < ConstantRecord::Base
9
+ columns :album
10
+ data 'Sgt. Pepper', 'Magical Mystery Tour', 'Abbey Road'
11
+ end
12
+
13
+ class MultiColumnClass < ConstantRecord::Base
14
+ columns :short, :description
15
+ data ['EUR', 'Euro'],
16
+ ['USD', 'US Dollar'],
17
+ ['CAD', 'Canadian Dollar'],
18
+ ['GBP', 'British Pound sterling'],
19
+ ['CHF', 'Swiss franc']
20
+ end
21
+
22
+ class TestConstantRecord < Test::Unit::TestCase
23
+ def test_simple_finder
24
+ assert_equal 'Estonia', SimpleClass.find(3).name
25
+ assert_nil SimpleClass.find(4)
26
+ assert_nil SimpleClass.find(0)
27
+ assert_nil SimpleClass.find(nil)
28
+ assert_equal 'Estonia', SimpleClass.find_by_name('Estonia').name
29
+ assert_raise (RuntimeError) { SimpleClass.find_by_foo('bar') }
30
+ assert_equal [ 'Lithuania', 'Latvia', 'Estonia' ], SimpleClass.find(:all).collect{|o| o.name}
31
+ assert_equal 3, SimpleClass.count
32
+ end
33
+
34
+ def test_simple_finder_with_custom_column_name
35
+ assert_equal 'Abbey Road', SimpleClass2.find(3).album
36
+ assert_nil SimpleClass2.find(4)
37
+ assert_nil SimpleClass2.find(0)
38
+ assert_nil SimpleClass2.find(nil)
39
+ assert_equal 'Sgt. Pepper', SimpleClass2.find_by_album('Sgt. Pepper').album
40
+ assert_raise (RuntimeError) { SimpleClass2.find_by_name('Sgt. Pepper') }
41
+ assert_equal [ 'Sgt. Pepper', 'Magical Mystery Tour', 'Abbey Road' ], SimpleClass2.find(:all).collect{|o| o.album}
42
+ assert_equal 3, SimpleClass2.count
43
+ end
44
+
45
+ def test_multi_column_finder
46
+ all = MultiColumnClass.find(:all)
47
+ chf = all[4]
48
+ assert 5 == chf.id && chf.short && 'Swiss franc' == chf.description
49
+
50
+ assert_equal 'Canadian Dollar', MultiColumnClass.find_by_short('CAD').description
51
+
52
+ assert_nil MultiColumnClass.find(6)
53
+ assert_nil MultiColumnClass.find(0)
54
+ assert_nil MultiColumnClass.find(nil)
55
+ assert_raise (RuntimeError) { MultiColumnClass.find_by_name('EUR') }
56
+ assert_equal [ 'EUR', 'USD', 'CAD', 'GBP', 'CHF' ], MultiColumnClass.find(:all).collect{|o| o.short}
57
+ assert_equal 5, MultiColumnClass.count
58
+ end
59
+
60
+ def test_options_for_select
61
+ assert_equal [['Lithuania', 1], ['Latvia', 2], ['Estonia', 3]], SimpleClass.options_for_select
62
+ assert_equal [['n/a', 0], ['Lithuania', 1], ['Latvia', 2], ['Estonia', 3]],
63
+ SimpleClass.options_for_select(:include_null => true, :null_text => 'n/a')
64
+ assert_equal [['-', nil], ['Lithuania', 1], ['Latvia', 2], ['Estonia', 3]],
65
+ SimpleClass.options_for_select(:include_null => true, :null_value => nil)
66
+ assert_equal [['Euro', 1], ['US Dollar', 2], ['Canadian Dollar', 3],
67
+ ['British Pound sterling', 4], ['Swiss franc', 5]],
68
+ MultiColumnClass.options_for_select(:display => :description)
69
+ assert_equal [['-', "nothn'"], ['Euro', 'EUR'], ['US Dollar', 'USD'],
70
+ ['Canadian Dollar', 'CAD'], ['British Pound sterling', 'GBP'], ['Swiss franc', 'CHF']],
71
+ MultiColumnClass.options_for_select(:display => :description, :value => :short,
72
+ :include_null => true, :null_value => "nothn'")
73
+ assert_equal [['*Sgt. Pepper*', 1], ['*Magical Mystery Tour*', 2], ['*Abbey Road*', 3]],
74
+ SimpleClass2.options_for_select(:display => Proc.new{|obj| "*#{obj.album}*"})
75
+ end
76
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: constantrecord
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Christoph Petschnig
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-21 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A tiny ActiveRecord substitute for small, never changing database tables.
17
+ email: info@purevirtual.de
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - lib/constantrecord.rb
24
+ - README.rdoc
25
+ files:
26
+ - constantrecord.gemspec
27
+ - lib/constantrecord.rb
28
+ - test/constantrecord_test.rb
29
+ - Manifest
30
+ - Rakefile
31
+ - README.rdoc
32
+ has_rdoc: true
33
+ homepage: http://github.com/ChristophPetschnig/constantrecord
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --line-numbers
37
+ - --inline-source
38
+ - --title
39
+ - Constantrecord
40
+ - --main
41
+ - README.rdoc
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "1.2"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project: constantrecord
59
+ rubygems_version: 1.3.1
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: A tiny ActiveRecord substitute for small, never changing database tables.
63
+ test_files:
64
+ - test/constantrecord_test.rb