lore 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/LICENSE +19 -0
  2. data/README +74 -0
  3. data/aspect.rb +80 -0
  4. data/behaviours/lockable.rb +41 -0
  5. data/behaviours/movable.rb +54 -0
  6. data/behaviours/versioned.rb +24 -0
  7. data/benchmark.rb +193 -0
  8. data/bits.rb +52 -0
  9. data/cache/abstract_entity_cache.rb +82 -0
  10. data/cache/bits.rb +22 -0
  11. data/cache/cacheable.rb +202 -0
  12. data/cache/cached_entities.rb +116 -0
  13. data/cache/file_index.rb +35 -0
  14. data/cache/mmap_entity_cache.rb +67 -0
  15. data/clause.rb +528 -0
  16. data/connection.rb +155 -0
  17. data/custom_functions.sql +14 -0
  18. data/exception/ambiguous_attribute.rb +14 -0
  19. data/exception/cache_exception.rb +30 -0
  20. data/exception/invalid_klass_parameters.rb +63 -0
  21. data/exception/invalid_parameter.rb +42 -0
  22. data/exception/unknown_typecode.rb +19 -0
  23. data/file_index.sql +56 -0
  24. data/gui/erb_template.rb +79 -0
  25. data/gui/erb_template_helpers.rhtml +19 -0
  26. data/gui/form.rb +314 -0
  27. data/gui/form_element.rb +676 -0
  28. data/gui/form_generator.rb +151 -0
  29. data/gui/templates/button.rhtml +2 -0
  30. data/gui/templates/checkbox.rhtml +3 -0
  31. data/gui/templates/checkbox_row.rhtml +1 -0
  32. data/gui/templates/file.rhtml +2 -0
  33. data/gui/templates/file_readonly.rhtml +3 -0
  34. data/gui/templates/form_element.rhtml +5 -0
  35. data/gui/templates/form_element_horizontal.rhtml +3 -0
  36. data/gui/templates/form_element_listed.rhtml +8 -0
  37. data/gui/templates/form_table.rhtml +3 -0
  38. data/gui/templates/form_table_blank.rhtml +3 -0
  39. data/gui/templates/form_table_horizontal.rhtml +8 -0
  40. data/gui/templates/password.rhtml +2 -0
  41. data/gui/templates/password_readonly.rhtml +3 -0
  42. data/gui/templates/radio.rhtml +1 -0
  43. data/gui/templates/radio_row.rhtml +1 -0
  44. data/gui/templates/select.rhtml +23 -0
  45. data/gui/templates/text.rhtml +2 -0
  46. data/gui/templates/text_readonly.rhtml +3 -0
  47. data/gui/templates/textarea.rhtml +3 -0
  48. data/gui/templates/textarea_readonly.rhtml +4 -0
  49. data/lore.gemspec +40 -0
  50. data/lore.rb +94 -0
  51. data/migration.rb +48 -0
  52. data/model.rb +139 -0
  53. data/model_factory.rb +202 -0
  54. data/model_shortcuts.rb +16 -0
  55. data/query_shortcuts.rb +367 -0
  56. data/reserved_methods.txt +3 -0
  57. data/result.rb +100 -0
  58. data/symbol.rb +58 -0
  59. data/table_accessor.rb +1926 -0
  60. data/table_deleter.rb +115 -0
  61. data/table_inserter.rb +168 -0
  62. data/table_instance.rb +384 -0
  63. data/table_selector.rb +314 -0
  64. data/table_updater.rb +155 -0
  65. data/test/README +31 -0
  66. data/test/env.rb +5 -0
  67. data/test/lore_test.log +8218 -0
  68. data/test/model.rb +142 -0
  69. data/test/prepare.rb +37 -0
  70. data/test/tc_aspect.rb +58 -0
  71. data/test/tc_cache.rb +80 -0
  72. data/test/tc_clause.rb +104 -0
  73. data/test/tc_deep_inheritance.rb +49 -0
  74. data/test/tc_factory.rb +57 -0
  75. data/test/tc_filter.rb +37 -0
  76. data/test/tc_form.rb +32 -0
  77. data/test/tc_model.rb +86 -0
  78. data/test/tc_prepare.rb +45 -0
  79. data/test/tc_refined_query.rb +88 -0
  80. data/test/tc_table_accessor.rb +265 -0
  81. data/test/test.log +181 -0
  82. data/test/test_db.sql +400 -0
  83. data/test/ts_lore.rb +49 -0
  84. data/types.rb +55 -0
  85. data/validation/message.rb +60 -0
  86. data/validation/parameter_validator.rb +104 -0
  87. data/validation/reason.rb +54 -0
  88. data/validation/type_validator.rb +91 -0
  89. data/validation.rb +65 -0
  90. metadata +170 -0
@@ -0,0 +1,151 @@
1
+
2
+ require('lore/types')
3
+ require('lore/gui/form')
4
+
5
+ module Lore
6
+ module GUI
7
+
8
+ # Usage:
9
+ #
10
+ # generator = Form_Generator.new(Table_Accessor, Lang, :readonly | :mutable)
11
+ # form = generator.generate
12
+ #
13
+ # See documentation of Cuba::GUI::Form for usage of form instances.
14
+ #
15
+ class Form_Generator
16
+
17
+ def initialize(klass=nil, labels={}, mode=:mutable, custom_elements={}) # {{{
18
+
19
+ @logger = Lore.logger
20
+ @form = Form.new()
21
+ @labels = labels
22
+ @klass = klass
23
+ @custom_elements = custom_elements
24
+
25
+ Textarea.reset_counter
26
+
27
+ @logger.debug('CUSTOM ELEMENTS: ' << @custom_elements.inspect)
28
+
29
+ if @klass.nil? then return end
30
+ @logger.debug('GET_ATTRIBUTES: ' << @klass.inspect)
31
+ @logger.debug('GET_ATTRIBUTES: ' << @klass.get_attributes.inspect)
32
+ @klass.get_attributes.each_pair { |table, attributes|
33
+
34
+ # handle attributes passed via attrib_setup:
35
+
36
+ attributes.each { |attrib|
37
+
38
+ label_tag = table.gsub('.','--') << '--' << attrib
39
+ if(@labels[label_tag].to_s != '' && @labels[label_tag] != label_tag) then
40
+ label = @labels[label_tag]
41
+ else
42
+ label = attrib.gsub('_',' ').capitalize
43
+ end
44
+
45
+ # @custom_elements is a hash mapping attribute names to
46
+ # Custom_Element instances.
47
+ if ((@custom_elements[table]) and
48
+ (@custom_elements[table][attrib])) then
49
+
50
+ form_element = @custom_elements[table][attrib].new(table, attrib, label)
51
+ form_element.mode = mode
52
+ @form.add(form_element)
53
+
54
+ elsif (@klass.get_primary_keys[table].nil? or
55
+ !@klass.get_primary_keys[table].include? attrib) and
56
+ (@klass.get_implicit_attributes[table].nil? or
57
+ !@klass.get_implicit_attributes[table].include? attrib) and
58
+ (@klass.get_has_a_klasses.nil? or
59
+ @klass.get_has_a_klasses[table+'.'+attrib].nil?) and
60
+ (@klass.get_hidden_attributes[table].nil? or
61
+ !@klass.get_hidden_attributes[table].include? attrib)
62
+ then
63
+
64
+ case @klass.get_attribute_types[table][attrib]
65
+
66
+ when Lore::PG_BOOL
67
+ form_element = Radio.new(table, attrib, label, ['t','f'])
68
+
69
+ when Lore::PG_SMALLINT || Lore::PG_INT
70
+ form_element = Text.new(table, attrib, label, nil)
71
+
72
+ when Lore::PG_VARCHAR
73
+ form_element = Text.new(table, attrib, label, nil, nil)
74
+ if (!@klass.get_maxlength.nil? &&
75
+ !@klass.get_maxlength[table].nil? &&
76
+ !@klass.get_maxlength[table][attrib.intern].nil?) then
77
+ form_element.set_length(@klass.get_maxlength[table][attrib.intern])
78
+ end
79
+
80
+ when Lore::PG_TEXT
81
+ form_element = Textarea.new(table, attrib, label, nil, nil)
82
+ if (!@klass.get_maxlength.nil? &&
83
+ !@klass.get_maxlength[table].nil? &&
84
+ !@klass.get_maxlength[table][attrib.intern].nil?) then
85
+ form_element.set_length(@klass.get_maxlength[table][attrib.intern])
86
+ end
87
+
88
+ when Lore::PG_TIMESTAMP_TIMEZONE
89
+ form_element = Date.new(table, attrib, label, nil)
90
+
91
+ when Lore::PG_DATE
92
+ form_element = Date.new(table, attrib, label, nil)
93
+
94
+ else
95
+ form_element = Text.new(table, attrib, label, nil)
96
+ end
97
+
98
+ form_element.set_mode(mode)
99
+ @form.add(form_element)
100
+
101
+ elsif (!@klass.get_has_a_klasses.nil? and
102
+ !@klass.get_has_a_klasses[table+'.'+attrib].nil?)
103
+ then
104
+ foreign_klass = @klass.get_has_a_klasses[table+'.'+attrib]
105
+ foreign_table = foreign_klass.table_name
106
+
107
+ form_element = Klass_Select.new(foreign_klass, table, attrib, label)
108
+ form_element.set_mode(mode)
109
+ @form.add(form_element)
110
+
111
+ elsif (!@klass.get_aggregate_klasses.nil? and
112
+ !@klass.get_aggregate_klasses[table+'.'+attrib].nil?)
113
+ then
114
+ foreign_klass = @klass.get_aggregate_klasses[table+'.'+attrib]
115
+ foreign_table = foreign_klass.table_name
116
+
117
+ form_element = Klass_Select.new(foreign_klass, table, attrib, label)
118
+ form_element.set_mode(mode)
119
+ @form.add(form_element)
120
+
121
+ # Attribute is explixit (expected/required) but not
122
+ # catched before -> Add attribute as hidden field:
123
+ elsif (!@klass.get_explicit_attributes[table].nil? and
124
+ @klass.get_explicit_attributes[table].include? attrib)
125
+ then
126
+ form_element = Hidden.new(table, attrib)
127
+ @form.add(form_element)
128
+
129
+ elsif (!@klass.get_implicit_attributes[table].nil? and
130
+ @klass.get_implicit_attributes[table].include? attrib)
131
+ then
132
+ @logger.debug(attrib+' is implicit')
133
+
134
+ end
135
+
136
+ }
137
+ }
138
+ end # }}}
139
+
140
+ # Returns instance of Cuba::GUI::Form configured
141
+ # for klass passed in constructor:
142
+ def generate() # {{{
143
+ return @form
144
+ end # }}}
145
+
146
+ end # class
147
+
148
+
149
+ end # module
150
+ end # module
151
+
@@ -0,0 +1,2 @@
1
+
2
+ <input <%= tag_attributes(attributes) %> />
@@ -0,0 +1,3 @@
1
+
2
+ <%= label %>
3
+ <input type="checkbox" <%= tag_attributes(attributes) %> />
@@ -0,0 +1 @@
1
+ <div class="lore_checkbox_row"><%= checkboxes %></div>
@@ -0,0 +1,2 @@
1
+
2
+ <input class="lore_file" type="file" <%= tag_attributes(attributes) %> />
@@ -0,0 +1,3 @@
1
+ <div class="lore_readonly_wrap" <%= tag_attributes(attributes) %> >
2
+ <%= attributes[:value] %>
3
+ </div>
@@ -0,0 +1,5 @@
1
+ <li class="form_attribute_row" id="<%= element_obj.attribute_id %>_wrap">
2
+ <%= label %>
3
+ <%= element %>
4
+ <div id="<%= element_obj.attribute_id %>_message" style="display: none" class="lore_form_element_message" ></div>
5
+ </li>
@@ -0,0 +1,3 @@
1
+ <td height="20" valign="top" class="lore_form_element_horizontal">
2
+ <%= element %>
3
+ </td>
@@ -0,0 +1,8 @@
1
+ <tr align="left" valign="middle">
2
+ <td valign="top" class="lore_form_element_listed left">
3
+ <b><%= label %></b>
4
+ </td>
5
+ <td valign="top" class="lore_form_element_listed right">
6
+ <%= element %>
7
+ </td>
8
+ </tr>
@@ -0,0 +1,3 @@
1
+ <ul class="form_elements">
2
+ <%= form %>
3
+ </ul>
@@ -0,0 +1,3 @@
1
+
2
+ <%= form %>
3
+
@@ -0,0 +1,8 @@
1
+
2
+ <div class="form_table">
3
+ <table border="0" cellspacing="0" cellpadding="0">
4
+ <tr>
5
+ <%= form %>
6
+ </tr>
7
+ </table>
8
+ </div>
@@ -0,0 +1,2 @@
1
+
2
+ <input type="password" <%= tag_attributes(attributes) %> />
@@ -0,0 +1,3 @@
1
+ <div class="lore_readonly_wrap" <%= tag_attributes(attributes) %> >
2
+ *********
3
+ </div>
@@ -0,0 +1 @@
1
+ <nobr><font style="width: 120px; "><input type="radio" <%= tag_attributes(attributes) %> /> <%= attributes[:label] %> </font></nobr>
@@ -0,0 +1 @@
1
+ <div class="lore_radio_row"><%= radios %></div>
@@ -0,0 +1,23 @@
1
+ <div class="lore_select_wrap">
2
+ <select <%= tag_attributes(attributes) %> >
3
+ <%=
4
+ option_index = 0
5
+ options = "\n"
6
+
7
+ range.each { |attrib|
8
+ attrib = attrib.to_s
9
+
10
+ if value.to_s == attrib.to_s then
11
+ selected = 'selected'
12
+ else selected = '' end
13
+
14
+ options += '<option value="'+attrib.to_s+'" '+selected+' >'
15
+ options += labels[option_index].to_s
16
+ options += '</options>'
17
+
18
+ option_index += 1
19
+ }
20
+ options
21
+ %>
22
+ </select>
23
+ </div>
@@ -0,0 +1,2 @@
1
+
2
+ <input type="text" <%= tag_attributes(attributes) %> />
@@ -0,0 +1,3 @@
1
+ <div class="lore_readonly_wrap" <%= tag_attributes(attributes) %> >
2
+ <%= attributes[:value] %>
3
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="lore_textarea_wrap">
2
+ <textarea <%= tag_attributes(attributes) %> ><%= value %></textarea>
3
+ </div>
@@ -0,0 +1,4 @@
1
+
2
+ <div class="textarea_lore_readonly">
3
+ <%= value.gsub('</p>', "\n\n").gsub('<br />',"\n").gsub(/<[^>]+>/,'')[0..1000].gsub("\n", '<br />') %>
4
+ </div>
data/lore.gemspec ADDED
@@ -0,0 +1,40 @@
1
+
2
+ require 'rake'
3
+
4
+ spec = Gem::Specification.new { |s|
5
+
6
+ s.name = 'lore'
7
+ s.rubyforge_project = 'lore'
8
+ s.summary = 'A flexible ORM based on PostgreSQL'
9
+ s.description = <<-EOF
10
+ Lore is an object-relational mapping (ORM) implementation
11
+ providing many features like prepared statements,
12
+ (multiple) inheritance, a comfortable query syntax,
13
+ highly customizable automated form generation,
14
+ and result caching using memory mapping (MMap).
15
+ Lore is currently using PostgreSQL as database backend.
16
+ EOF
17
+ s.version = '0.4.2'
18
+ s.author = 'Tobias Fuchs'
19
+ s.email = 'fuchs@atomnode.net'
20
+ s.date = Time.now
21
+ s.files = '*.rb'
22
+ s.add_dependency('mmap', '>= 0.1')
23
+ s.add_dependency('postgres', '>= 0.1')
24
+ s.files = FileList['*',
25
+ 'behaviours/*',
26
+ 'test/*',
27
+ 'cache/*',
28
+ 'validation/*',
29
+ 'gui/*',
30
+ 'gui/templates/*',
31
+ 'exception/*'].to_a
32
+
33
+ s.has_rdoc = true
34
+ s.rdoc_options << '--title' << 'Lore ORM' <<
35
+ '--main' << 'README' <<
36
+ '--line-numbers'
37
+
38
+ s.homepage = 'http://lore.rubyforge.org'
39
+
40
+ }
data/lore.rb ADDED
@@ -0,0 +1,94 @@
1
+
2
+ require('logger')
3
+
4
+ module Lore
5
+
6
+ @logfile = '/var/log/lore/query.log'
7
+ def self.logfile
8
+ @logfile
9
+ end
10
+ def self.logfile=(file)
11
+ @logger = Logger.new(file)
12
+ end
13
+ def self.logger
14
+ @logger
15
+ end
16
+ @logger = Logger.new(Lore.logfile)
17
+
18
+ def self.log(&log_block)
19
+ return if Lore.logging_disabled?
20
+ Lore.logger.debug(&log_block)
21
+ end
22
+
23
+ def self.log_queries?
24
+ true
25
+ end
26
+ def self.logging_disabled?
27
+ false
28
+ end
29
+
30
+ @cache_entities = false
31
+
32
+ @logins = {
33
+ }
34
+
35
+ def self.set_login_data(login_hash)
36
+ @logins = login_hash
37
+ end
38
+ def self.add_login_data(login_hash)
39
+ @logins.update(login_hash)
40
+ end
41
+
42
+ def self.path
43
+ File.expand_path(File.dirname(__FILE__)) + '/'
44
+ end
45
+
46
+ @pg_server = 'localhost'
47
+ @pg_port = 5432
48
+
49
+ def self.pg_server
50
+ @pg_server
51
+ end
52
+ def self.pg_port
53
+ @pg_port
54
+ end
55
+ def self.pg_server=(s)
56
+ @pg_server = s
57
+ end
58
+ def self.pg_port=(p)
59
+ @pg_port = p
60
+ end
61
+
62
+ def self.user_for(dbname)
63
+ begin
64
+ @logins[dbname.to_s][0].to_s
65
+ rescue ::Exception => excep
66
+ raise ::Exception.new('Unable to resolve user for database ' << dbname.inspect)
67
+ end
68
+ end
69
+ def self.pass_for(dbname)
70
+ @logins[dbname.to_s][1].to_s
71
+ end
72
+
73
+ def self.disable_cache
74
+ @cache_entities = false
75
+ end
76
+
77
+ def self.enable_cache
78
+ @cache_entities = true
79
+ end
80
+
81
+ def self.cache_enabled?
82
+ @cache_entities
83
+ end
84
+
85
+ def self.on_connect_commands()
86
+ "set client_encoding to Unicode; set datestyle to 'European'"
87
+ end
88
+
89
+ end
90
+
91
+ require('lore/validation/parameter_validator')
92
+ require('lore/exception/invalid_parameter')
93
+ require('lore/exception/invalid_klass_parameters')
94
+ require('lore/connection')
data/migration.rb ADDED
@@ -0,0 +1,48 @@
1
+
2
+ module Lore
3
+
4
+
5
+ # Usage:
6
+ #
7
+ # Using auto-bootstrap of models:
8
+ #
9
+ # class My_Model < Lore::Model
10
+ # extend Lore::Migration
11
+ #
12
+ # table :my_model, :public
13
+ # primary_key :key_a, :key_a_seq
14
+ # primary_key :key_b
15
+ #
16
+ # has_attributes {
17
+ # :foo, Lore::Type.integer, { :unique => true, :default => 1 }
18
+ # :bar, Lore::Type.integer, { :not_null => true, :check => 'bar < 1000' }
19
+ # }
20
+ #
21
+ # def self.up
22
+ # bootstrap()
23
+ # My_Model.create(:foo => 23, :bar => 123)
24
+ # end
25
+ #
26
+ # end
27
+ #
28
+ module Migration
29
+
30
+ def has_attributes(attrib_hash)
31
+ @model_schema = attrib_hash
32
+ end
33
+
34
+ def update_schema
35
+ @factory = Model_Factory.new(self.class.to_s)
36
+ @model_schema.each_pair { |attrib_name, attrib_props|
37
+ @factory.add_attribute(attrib_name.to_s, attrib_props)
38
+ }
39
+ end
40
+
41
+ def bootstrap
42
+ @factory.build_table
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
data/model.rb ADDED
@@ -0,0 +1,139 @@
1
+
2
+ require('logger')
3
+
4
+ require('lore/aspect');
5
+ require('lore/table_selector');
6
+ require('lore/table_inserter');
7
+ require('lore/table_updater');
8
+ require('lore/table_deleter');
9
+ require('lore/table_instance');
10
+ require('lore/query_shortcuts');
11
+ require('lore/validation');
12
+ require('lore/migration');
13
+ require('lore/validation/parameter_validator');
14
+ require('lore/exception/invalid_parameter');
15
+ require('lore/exception/invalid_klass_parameters');
16
+ require('lore/cache/cacheable');
17
+ require('lore/table_accessor.rb')
18
+
19
+ module Lore
20
+
21
+ # For API details see Lore::Table_Accessor
22
+ #
23
+ #
24
+ # =How to define models
25
+ #
26
+ # Example:
27
+ #
28
+ # require 'lore/model'
29
+ #
30
+ # class Vehicle < Lore::Model
31
+ # end
32
+ #
33
+ # class Car < Vehicle
34
+ # end
35
+ #
36
+ # Sets following defaults:
37
+ #
38
+ # class Vehicle
39
+ # table :vehicle, :vehicle_id
40
+ # primary_key :id # or :vehicle_id if :id is not present
41
+ # end
42
+ #
43
+ # class Car < Vehicle
44
+ # table :car, :public
45
+ # primary_key :id # or :car_id if :id is not present
46
+ # is_a Vehicle, :vehicle_id
47
+ # end
48
+ #
49
+ # = How to use models
50
+ #
51
+ # == Creating entities (INSERTs)
52
+ #
53
+ # manuf_1 = Manufacturer.create(:name => 'Audi')
54
+ # manuf_2 = Manufacturer.create(:name => 'BMW')
55
+ # car = Car.create(:manufacturer => manuf, :name => 'TT')
56
+ #
57
+ # == Changing entities (UPDATEs)
58
+ #
59
+ # car['name'] = 'BMW318i'
60
+ # car.manufacturer = manuf_2 # same as car[:manufacturer_id] = manuf_2.manufacturer_id
61
+ # car.commit
62
+ #
63
+ # == Deleting entities (DELETEs)
64
+ #
65
+ # car.delete!
66
+ # is the same as
67
+ # Car.delete.where(Car.car_id == car.car_id).perform
68
+ # is the same as
69
+ # Lore.perform Car.delete.where(Car.car_id == car.car_id)
70
+ # is the same as
71
+ # Car.delete { |c|
72
+ # c.where(c.car_id = car.car_id)
73
+ # }
74
+ #
75
+ # =How to disable/enable features
76
+ #
77
+ # Lore::Model extends
78
+ # * Lore::Cache::Cacheable
79
+ # * Lore::Query_Shortcuts
80
+ # * Lore::Validation
81
+ # * Lore::Aspect
82
+ #
83
+ # Each of them is optional. If you want, for example, a minimalistic
84
+ # version of Lore::Model with comfortable query interfaces you can define
85
+ # your own Model class that extends the Lore::Query_Shortcuts module only:
86
+ #
87
+ # class Slim_Model < Lore::Table_Accessor
88
+ # extend Lore::Query_Shortcuts
89
+ # end
90
+ #
91
+ # You can use it as base class for other Model classes, too:
92
+ #
93
+ # class Cache_Model < Slim_Model
94
+ # extend Lore::Query_Shortcuts
95
+ # extend Lore::Cacheable
96
+ # end
97
+ #
98
+ # You also can define this differently for every model by deriving them
99
+ # from Lore::Table_Accessor and extending every single one of them:
100
+ #
101
+ # class User < Lore::Table_Accessor
102
+ # extend Lore::Query_Shortcuts
103
+ # extend Lore::Aspect
104
+ # extend Lore::Validation
105
+ #
106
+ # table :user, :public
107
+ # primary_key :user_id
108
+ #
109
+ # end
110
+ #
111
+ # =How to use Lore::Model in a Rails app
112
+ #
113
+ # By default, Lore::Model doesn't extend Rails interface bridges. You
114
+ # either can define your own Model base class (see: How to disable/enable
115
+ # features) and extend it by Lore::Rails::Model
116
+ #
117
+ # class My_Model < Lore::Model
118
+ # extend Lore::Rails::Model
119
+ # end
120
+ #
121
+ # ... or extend Lore::Model directly:
122
+ #
123
+ # Lore::Model.extend Lore::Rails_Bridge
124
+ #
125
+ # In this case, every Model in your app will include Rails interfaces.
126
+ #
127
+ class Model < Table_Accessor
128
+ extend Lore::Cache::Cacheable
129
+ extend Lore::Query_Shortcuts
130
+ extend Lore::Validation
131
+ extend Lore::Aspect
132
+ extend Lore::Migration
133
+
134
+ def by_id(pkey_id)
135
+ _by_id(pkey_id).first # Auto-defined in Lore::Table_Accessor.primary_key
136
+ end
137
+ end
138
+
139
+ end