lore 0.4.2
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/LICENSE +19 -0
- data/README +74 -0
- data/aspect.rb +80 -0
- data/behaviours/lockable.rb +41 -0
- data/behaviours/movable.rb +54 -0
- data/behaviours/versioned.rb +24 -0
- data/benchmark.rb +193 -0
- data/bits.rb +52 -0
- data/cache/abstract_entity_cache.rb +82 -0
- data/cache/bits.rb +22 -0
- data/cache/cacheable.rb +202 -0
- data/cache/cached_entities.rb +116 -0
- data/cache/file_index.rb +35 -0
- data/cache/mmap_entity_cache.rb +67 -0
- data/clause.rb +528 -0
- data/connection.rb +155 -0
- data/custom_functions.sql +14 -0
- data/exception/ambiguous_attribute.rb +14 -0
- data/exception/cache_exception.rb +30 -0
- data/exception/invalid_klass_parameters.rb +63 -0
- data/exception/invalid_parameter.rb +42 -0
- data/exception/unknown_typecode.rb +19 -0
- data/file_index.sql +56 -0
- data/gui/erb_template.rb +79 -0
- data/gui/erb_template_helpers.rhtml +19 -0
- data/gui/form.rb +314 -0
- data/gui/form_element.rb +676 -0
- data/gui/form_generator.rb +151 -0
- data/gui/templates/button.rhtml +2 -0
- data/gui/templates/checkbox.rhtml +3 -0
- data/gui/templates/checkbox_row.rhtml +1 -0
- data/gui/templates/file.rhtml +2 -0
- data/gui/templates/file_readonly.rhtml +3 -0
- data/gui/templates/form_element.rhtml +5 -0
- data/gui/templates/form_element_horizontal.rhtml +3 -0
- data/gui/templates/form_element_listed.rhtml +8 -0
- data/gui/templates/form_table.rhtml +3 -0
- data/gui/templates/form_table_blank.rhtml +3 -0
- data/gui/templates/form_table_horizontal.rhtml +8 -0
- data/gui/templates/password.rhtml +2 -0
- data/gui/templates/password_readonly.rhtml +3 -0
- data/gui/templates/radio.rhtml +1 -0
- data/gui/templates/radio_row.rhtml +1 -0
- data/gui/templates/select.rhtml +23 -0
- data/gui/templates/text.rhtml +2 -0
- data/gui/templates/text_readonly.rhtml +3 -0
- data/gui/templates/textarea.rhtml +3 -0
- data/gui/templates/textarea_readonly.rhtml +4 -0
- data/lore.gemspec +40 -0
- data/lore.rb +94 -0
- data/migration.rb +48 -0
- data/model.rb +139 -0
- data/model_factory.rb +202 -0
- data/model_shortcuts.rb +16 -0
- data/query_shortcuts.rb +367 -0
- data/reserved_methods.txt +3 -0
- data/result.rb +100 -0
- data/symbol.rb +58 -0
- data/table_accessor.rb +1926 -0
- data/table_deleter.rb +115 -0
- data/table_inserter.rb +168 -0
- data/table_instance.rb +384 -0
- data/table_selector.rb +314 -0
- data/table_updater.rb +155 -0
- data/test/README +31 -0
- data/test/env.rb +5 -0
- data/test/lore_test.log +8218 -0
- data/test/model.rb +142 -0
- data/test/prepare.rb +37 -0
- data/test/tc_aspect.rb +58 -0
- data/test/tc_cache.rb +80 -0
- data/test/tc_clause.rb +104 -0
- data/test/tc_deep_inheritance.rb +49 -0
- data/test/tc_factory.rb +57 -0
- data/test/tc_filter.rb +37 -0
- data/test/tc_form.rb +32 -0
- data/test/tc_model.rb +86 -0
- data/test/tc_prepare.rb +45 -0
- data/test/tc_refined_query.rb +88 -0
- data/test/tc_table_accessor.rb +265 -0
- data/test/test.log +181 -0
- data/test/test_db.sql +400 -0
- data/test/ts_lore.rb +49 -0
- data/types.rb +55 -0
- data/validation/message.rb +60 -0
- data/validation/parameter_validator.rb +104 -0
- data/validation/reason.rb +54 -0
- data/validation/type_validator.rb +91 -0
- data/validation.rb +65 -0
- metadata +170 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2005-2008 Tobias Fuchs (fuchs@wortundform.de)
|
3
|
+
Lore has evolved from Cuba::DB, Copyright (c) 2004 Tobias Fuchs (fuchs@wortundform.de)
|
4
|
+
|
5
|
+
(MIT Licence)
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
8
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
9
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
10
|
+
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
13
|
+
the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
16
|
+
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
18
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
Q: Are there adapters for other DBMS than PostgreSQL?
|
3
|
+
A: PostgreSQL is - to me and everyone using Lore i know of -
|
4
|
+
the best (free) choice for sophisticated database-driven
|
5
|
+
apps, so frankly said: Implementing adapters is not on my
|
6
|
+
top priority list. Adapters also come with some cost in
|
7
|
+
performance, by introducing an additional level of
|
8
|
+
abstraction.
|
9
|
+
So far, Lore supports PostgreSQL (7.4 to 8.3 tested).
|
10
|
+
Adapters are planned for oracle mysql and sqlite though.
|
11
|
+
Lore is SQL 92 compatible and not using any exotic
|
12
|
+
Postgres features, so it *might* work fine on e.g. mysql
|
13
|
+
and sqlite with just minor modifications. It is caring
|
14
|
+
about cascading dependencies etc, so you don't even need
|
15
|
+
foreign keys or table inheritance.
|
16
|
+
You will have to change the code establishing DB
|
17
|
+
connections (see connection.rb), but as those are
|
18
|
+
abstracted, just a couple of lines of code in connection.rb
|
19
|
+
had to be changed.
|
20
|
+
|
21
|
+
Q: How do i establish a DB connection at all?
|
22
|
+
A: In /lore.rb, edit the Hash instance @logins. Lore is caring
|
23
|
+
about connections itself.
|
24
|
+
You have to enter a so-called context (database, that is),
|
25
|
+
though:
|
26
|
+
|
27
|
+
Lore::Context.enter :my_dbname
|
28
|
+
|
29
|
+
Q: What about connection pooling?
|
30
|
+
A: Use PGPool for that, it's way better than any
|
31
|
+
implementation of connection pooling in ruby.
|
32
|
+
Install PGPool and tell lore to connect to PGPool's port
|
33
|
+
instead of PostgreSQL's one.
|
34
|
+
Example:
|
35
|
+
|
36
|
+
Lore.server = 'localhost:9999'
|
37
|
+
|
38
|
+
That's it!
|
39
|
+
|
40
|
+
Q: Which solution do you recommend for query result caching?
|
41
|
+
A: There are many possibilities: Tempfiles, MMap and Memcached
|
42
|
+
are the most widespread solutions known to me.
|
43
|
+
Many will agree that MMap is the best solution in matters
|
44
|
+
of performance on a single server, and memcached as soon as
|
45
|
+
more than one server is involved in your application.
|
46
|
+
|
47
|
+
Q: There are failing unit tests!!
|
48
|
+
A: I know. Those document missing features i'm implementing
|
49
|
+
at the very moment :) Be sure to update once in a while!
|
50
|
+
|
51
|
+
Q: After creating a model via Model_Factory: How do i change
|
52
|
+
it?
|
53
|
+
A: At the moment, this is not implemented, but tracked as
|
54
|
+
'missing feature'. This will be possible in version 1.0 at
|
55
|
+
the latest.
|
56
|
+
|
57
|
+
Q: There's so much documentation missing, the API isn't really
|
58
|
+
helpful. Where can i get more info and examples?
|
59
|
+
A: Have a look at the wiki. I'm updating it quite often, and
|
60
|
+
new features are explained in the wiki first:
|
61
|
+
http://infranode.com/wiki/
|
62
|
+
|
63
|
+
Q: I found a bug!! What now?
|
64
|
+
A: In case you're registered at rubyforge, visit
|
65
|
+
http://lore.rubyforge.org and submit a bug report.
|
66
|
+
Otherwise, just send me a mail (fuchs@atomnode.net).
|
67
|
+
If you're using Lore and are your progress is blocked by a
|
68
|
+
bug, i'll fix it almost instantly. I'm checking my mails
|
69
|
+
more often than the tickets at rubyforge, though.
|
70
|
+
|
71
|
+
Q: I want to join the dev team.
|
72
|
+
A: Great! Just send a mail to fuchs@wortundform.de, i will add
|
73
|
+
you on Rubyforge if you got some code to share.
|
74
|
+
|
data/aspect.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Lore
|
4
|
+
|
5
|
+
# Steps taken in .create are:
|
6
|
+
#
|
7
|
+
# * .before_create(attribs)
|
8
|
+
# * applying input filters
|
9
|
+
# * .before_create_after_filters(attribs)
|
10
|
+
# * distributing attributes to tables:
|
11
|
+
# { :attrib => 'value' } -> { 'public.my_table' => { :attrib => 'value'} }
|
12
|
+
# * .before_create_and_validation(table_attribs)
|
13
|
+
# * validation
|
14
|
+
# * .before_insert(table_attribs)
|
15
|
+
# * insert operation
|
16
|
+
# * .before_load(attribs)
|
17
|
+
# * load new instance from DB (thus getting final table values)
|
18
|
+
# * .after_create(created_instance)
|
19
|
+
|
20
|
+
module Aspect
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def before_delete(args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def after_delete(args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Expects arguments (value hash) passed
|
31
|
+
# to Table_Accessor.create
|
32
|
+
def before_create(args)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Expects arguments (value hash) passed
|
36
|
+
# to Table_Accessor.create
|
37
|
+
def after_filters(filtered_args)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Expects arguments (value hash) passed
|
41
|
+
# to Table_Accessor.create and distributed
|
42
|
+
# to table names
|
43
|
+
def before_validation(distributed_args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Expects arguments (value hash) passed
|
47
|
+
# to Table_Accessor.create and distributed
|
48
|
+
# to table names
|
49
|
+
def before_insert(validated_args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def before_load(completed_args)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Expects object that has been created.
|
56
|
+
def after_create(obj)
|
57
|
+
end
|
58
|
+
|
59
|
+
public
|
60
|
+
|
61
|
+
# Expects model instance to be deleted.
|
62
|
+
def before_instance_delete(model_instance)
|
63
|
+
end
|
64
|
+
# Expects model instance table rows have been
|
65
|
+
# deleted of
|
66
|
+
def after_instance_delete(model_instance)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Expects arguments (value hash) passed
|
70
|
+
# to Table_Accessor.create and object
|
71
|
+
# to be updated.
|
72
|
+
def before_commit(obj)
|
73
|
+
end
|
74
|
+
# Expects updated object.
|
75
|
+
def after_commit(obj)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module Lore
|
3
|
+
module Behaviours
|
4
|
+
|
5
|
+
module Lockable
|
6
|
+
|
7
|
+
# Defines which attribute to use for locking.
|
8
|
+
# Default is 'lock'.
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# If a block is given, a lock is only set when it
|
12
|
+
# evaluates to true:
|
13
|
+
#
|
14
|
+
# lock_by(:lock) { |me|
|
15
|
+
# me.user_id != $user.user_id
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
def lock_by(attrib, &block)
|
19
|
+
@lock_proc = block
|
20
|
+
@lock_attr = attrib
|
21
|
+
@lock_attr_name = attrib.to_s.split('.')[-1].intern
|
22
|
+
end
|
23
|
+
|
24
|
+
def lock!(inst)
|
25
|
+
inst[@lock_attr] = true
|
26
|
+
commit
|
27
|
+
end # def
|
28
|
+
|
29
|
+
def release!(inst)
|
30
|
+
inst[@lock_attr] = false
|
31
|
+
commit
|
32
|
+
end # def
|
33
|
+
|
34
|
+
def locked?(inst)
|
35
|
+
(inst[@lock_attr] == true) || (inst[@lock_attr] == 't')
|
36
|
+
end # def
|
37
|
+
|
38
|
+
end # module
|
39
|
+
|
40
|
+
end # module
|
41
|
+
end # module
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
module Lore
|
3
|
+
module Behaviours
|
4
|
+
|
5
|
+
module Movable
|
6
|
+
|
7
|
+
def ordered_by(attr)
|
8
|
+
@order_attr = attr
|
9
|
+
@order_attr_name = attr.to_s.split('.')[-1].intern
|
10
|
+
end
|
11
|
+
|
12
|
+
def move(inst, sortpos, criteria)
|
13
|
+
|
14
|
+
sortpos = sortpos.to_i
|
15
|
+
return if sortpos < 1
|
16
|
+
|
17
|
+
sortpos_old = inst.attr[@order_attr_name].to_i
|
18
|
+
|
19
|
+
# move down:
|
20
|
+
if sortpos.to_i > sortpos_old then
|
21
|
+
|
22
|
+
self.update { |na|
|
23
|
+
na.set({@order_attr_name => @order_attr-1}).where(
|
24
|
+
(criteria) &
|
25
|
+
(@order_attr <= sortpos) &
|
26
|
+
(@order_attr > sortpos_old)
|
27
|
+
)
|
28
|
+
}
|
29
|
+
|
30
|
+
elsif sortpos.to_i < sortpos_old then
|
31
|
+
|
32
|
+
self.update { |na|
|
33
|
+
na.set({@order_attr_name => @order_attr+1}).where(
|
34
|
+
(criteria) &
|
35
|
+
(@order_attr >= sortpos) &
|
36
|
+
(@order_attr < sortpos_old)
|
37
|
+
)
|
38
|
+
}
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
if sortpos != sortpos_old then
|
43
|
+
|
44
|
+
inst.set_attribute_value(@order_attr_name, sortpos)
|
45
|
+
inst.commit()
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end # def
|
50
|
+
|
51
|
+
end # module
|
52
|
+
|
53
|
+
end # module
|
54
|
+
end # module
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Lore
|
3
|
+
module Behaviours
|
4
|
+
|
5
|
+
module Versioned
|
6
|
+
|
7
|
+
# Defines attribute the version number is stored in.
|
8
|
+
# Default is 'version'.
|
9
|
+
def version_by(attrib)
|
10
|
+
@version_attr = attrib
|
11
|
+
@version_attr_name = attrib.to_s.split('.')[-1].intern
|
12
|
+
end
|
13
|
+
|
14
|
+
# Overloads commit so it increments the version attribute
|
15
|
+
# before saving instance to database.
|
16
|
+
def commit()
|
17
|
+
set_attribute_value(@version_attr, self.attr[@version_attr].to_i + 1)
|
18
|
+
super()
|
19
|
+
end
|
20
|
+
|
21
|
+
end # module
|
22
|
+
|
23
|
+
end # module
|
24
|
+
end # module
|
data/benchmark.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
|
2
|
+
require('lore/model')
|
3
|
+
require('lore/cache/mmap_entity_cache')
|
4
|
+
|
5
|
+
Lore::Model.use_entity_cache Lore::Cache::MMap_Entity_Cache
|
6
|
+
# Aurita.load_project :default
|
7
|
+
# Aurita.import_plugin_model :wiki, :article
|
8
|
+
|
9
|
+
# include Aurita::Plugins::Wiki
|
10
|
+
|
11
|
+
require('benchmark')
|
12
|
+
include Benchmark
|
13
|
+
# require('profile')
|
14
|
+
Lore.logfile = './benchmark_query.log'
|
15
|
+
|
16
|
+
require('rubygems')
|
17
|
+
require('activerecord')
|
18
|
+
|
19
|
+
ActiveRecord::Base.establish_connection(
|
20
|
+
:adapter => 'postgresql',
|
21
|
+
:host => 'localhost',
|
22
|
+
:username => 'cuba',
|
23
|
+
:password => 'cuba23',
|
24
|
+
:database => 'artest'
|
25
|
+
)
|
26
|
+
|
27
|
+
Lore.add_login_data('artest' => ['cuba', 'cuba23'])
|
28
|
+
Lore::Context.enter :artest
|
29
|
+
|
30
|
+
module AR
|
31
|
+
class Content < ActiveRecord::Base
|
32
|
+
end
|
33
|
+
|
34
|
+
class Article < Content
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module Lore
|
39
|
+
class Content < Lore::Model
|
40
|
+
table :contents, :public
|
41
|
+
primary_key :id, :content_id_seq
|
42
|
+
end
|
43
|
+
|
44
|
+
class Article < Content
|
45
|
+
table :articles, :public
|
46
|
+
primary_key :id, :article_id_seq
|
47
|
+
is_a Content, :content_id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
num_loops = 10000
|
52
|
+
|
53
|
+
class Result_Parser
|
54
|
+
require 'inline'
|
55
|
+
|
56
|
+
def initialize(result) # expects PGresult
|
57
|
+
|
58
|
+
@result = result
|
59
|
+
@field_types = nil
|
60
|
+
@result_rows = Array.new
|
61
|
+
@num_fields = @result.num_fields
|
62
|
+
@field_name_split = Array.new
|
63
|
+
@field_name = String.new
|
64
|
+
@table_name = String.new
|
65
|
+
@field_counter = 0
|
66
|
+
|
67
|
+
end # def initialize
|
68
|
+
|
69
|
+
=begin
|
70
|
+
inline { |builder|
|
71
|
+
builder.c <<-EOC
|
72
|
+
VALUE get_row_c(VALUE result, int row_num) {
|
73
|
+
unsigned int field_counter;
|
74
|
+
VALUE row_result, field_name, table_name, field_name_split;
|
75
|
+
VALUE value_set = rb_hash_new();
|
76
|
+
field_name = rb_string_new();
|
77
|
+
table_name = rb_string_new();
|
78
|
+
row_result = rb_hash_new();
|
79
|
+
for(field_counter = 0; field_counter < num_fields; field_counter++) {
|
80
|
+
value_set = result->getvalue(row_num, UINT2NUM(field_counter));
|
81
|
+
// if(row_result[@table_name].nil?) then
|
82
|
+
if(rb_hash_aref(row_result, table_name) == QNil) {
|
83
|
+
// row_result[@table_name] = Hash.new()
|
84
|
+
rb_hash_aset(row_result, table_name, value_set)
|
85
|
+
}
|
86
|
+
rb_hash_aset(rb_hash_aref)row_result, table_name), value_set);
|
87
|
+
}
|
88
|
+
return row_result;
|
89
|
+
}
|
90
|
+
EOC
|
91
|
+
}
|
92
|
+
=end
|
93
|
+
|
94
|
+
def get_row(row_num=0)
|
95
|
+
|
96
|
+
return if @result.num_tuples == 0
|
97
|
+
|
98
|
+
row_result = Hash.new
|
99
|
+
|
100
|
+
@field_name_split = Array.new
|
101
|
+
@field_name = String.new
|
102
|
+
@table_name = String.new
|
103
|
+
@field_counter = 0
|
104
|
+
for @field_counter in 0...@num_fields do
|
105
|
+
# admin.rd__content.name -> [admin, rd__content, name]
|
106
|
+
@field_name = @result.fieldname(@field_counter)
|
107
|
+
|
108
|
+
row_result[@field_name] = @result.getvalue(row_num,@field_counter)
|
109
|
+
end
|
110
|
+
|
111
|
+
@result_rows[row_num] = row_result
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_rows()
|
116
|
+
if !@result_rows.first then
|
117
|
+
for tuple_counter in 0...@result.num_tuples do
|
118
|
+
get_row(tuple_counter)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@result_rows
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
db_name = 'artest'
|
127
|
+
connection = PGconn.connect(Lore.pg_server, Lore.pg_port, '', '', db_name.to_s, Lore.user_for(db_name), Lore.pass_for(db_name.to_s))
|
128
|
+
|
129
|
+
sql = 'SELECT *
|
130
|
+
FROM public.articles
|
131
|
+
JOIN public.contents on (public.contents.id = public.articles.content_id)
|
132
|
+
WHERE public.articles.id BETWEEN 1100 AND 1200 LIMIT 100 OFFSET 0'
|
133
|
+
|
134
|
+
bmbm(12) { |test|
|
135
|
+
test.report("rows_raw") {
|
136
|
+
num_loops.times {
|
137
|
+
result = connection.exec(sql)
|
138
|
+
for row in 0...result.num_tuples do
|
139
|
+
for field_index in 0...result.num_fields do
|
140
|
+
result.getvalue(row,field_index)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
}
|
144
|
+
}
|
145
|
+
test.report("rows_raw_parsed") {
|
146
|
+
num_loops.times {
|
147
|
+
result = connection.exec(sql)
|
148
|
+
p = Result_Parser.new(result)
|
149
|
+
# puts p.get_row_c(result, 0)
|
150
|
+
p.get_rows
|
151
|
+
}
|
152
|
+
}
|
153
|
+
test.report("rows_lore") {
|
154
|
+
num_loops.times {
|
155
|
+
result = connection.exec(sql)
|
156
|
+
lore_result = Lore::Result.new('',result)
|
157
|
+
lore_result.get_rows.each { |row|
|
158
|
+
# noop
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
test.report("ac_instances") {
|
163
|
+
num_loops.times {
|
164
|
+
result = connection.exec(sql)
|
165
|
+
lore_result = Lore::Result.new('',result)
|
166
|
+
model_instances = []
|
167
|
+
lore_result.get_rows.each { |row|
|
168
|
+
i = Lore::Article.new(Lore::Article,row)
|
169
|
+
model_instances.push(i)
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
test.report("full_auto") {
|
174
|
+
Lore.disable_cache
|
175
|
+
num_loops.times {
|
176
|
+
Lore::Article.find(100).with(Lore::Article.id.in(1100..1200)).entities
|
177
|
+
}
|
178
|
+
}
|
179
|
+
test.report("ar") {
|
180
|
+
num_loops.times {
|
181
|
+
AR::Article.find(:all, :conditions => "id > 1100 AND id < 1200").each { |a|
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
test.report('using cache') {
|
186
|
+
Lore.enable_cache
|
187
|
+
num_loops.times {
|
188
|
+
Lore::Article.find(100).with(Lore::Article.id.in(1100..1200)).entities
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
|
data/bits.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
module Lore
|
3
|
+
|
4
|
+
def self.resolve_passed_value(values, table_name, attrib_name)
|
5
|
+
return { :value => values[attrib_name].to_s, :field => attrib_name }
|
6
|
+
idx = attrib_name.to_s.length
|
7
|
+
value = nil
|
8
|
+
while value.nil? && idx > 1 do
|
9
|
+
name_part = attrib_name.to_s[0..idx]
|
10
|
+
value = values[name_part]
|
11
|
+
if value == '' then
|
12
|
+
name_part = table_name + '.' << name_part
|
13
|
+
value = values[name_part]
|
14
|
+
end
|
15
|
+
|
16
|
+
idx -= 1
|
17
|
+
end
|
18
|
+
{ :value => value, :field => name_part }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class Hash
|
24
|
+
|
25
|
+
def deep_merge!(second)
|
26
|
+
merger = proc { |key,v1,v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
27
|
+
self.merge!(second, &merger)
|
28
|
+
end
|
29
|
+
|
30
|
+
def nested_hash(array)
|
31
|
+
node = self
|
32
|
+
array.each do |i|
|
33
|
+
node[i]=Hash.new if node[i].nil?
|
34
|
+
node = node[i]
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def merge_nested_hash!(nested_hash)
|
40
|
+
deep_merge!(nested_hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
def keys_flat(keys_result=[])
|
44
|
+
keys.each { |k|
|
45
|
+
keys_result << k
|
46
|
+
child = self[k]
|
47
|
+
child.keys_flat(keys_result) if (child && child.kind_of?(Hash))
|
48
|
+
}
|
49
|
+
return keys_result
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
require('logger')
|
3
|
+
require('pstore')
|
4
|
+
require('digest/md5')
|
5
|
+
|
6
|
+
module Lore
|
7
|
+
module Cache
|
8
|
+
|
9
|
+
# When implementing your own entity cache, derive from and implement
|
10
|
+
# Abstract_Entity_Cache.
|
11
|
+
# Expected class methods are:
|
12
|
+
# - flush
|
13
|
+
# - read(query_string)
|
14
|
+
# - create(model_klass, query_string, result)
|
15
|
+
# - include?(query_string)
|
16
|
+
# - delete(cache_index)
|
17
|
+
# cache_index being an MD5 sum of the query_string that
|
18
|
+
# generated the cache entry.
|
19
|
+
# In case you want cache indices other than MD5 sums,
|
20
|
+
# define class method
|
21
|
+
# index_for(query_string)
|
22
|
+
#
|
23
|
+
# Every model may use a different caching implementation.
|
24
|
+
# To enable a specific caching implenentation for a model, use:
|
25
|
+
#
|
26
|
+
# class Your_Model < Lore::Model
|
27
|
+
# ...
|
28
|
+
# use_cache_impl The_Cache_Implementation_Klass
|
29
|
+
# ...
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
class Abstract_Entity_Cache
|
33
|
+
|
34
|
+
# Delete all cache entries for this model
|
35
|
+
def self.flush
|
36
|
+
raise ::Exception.new('Not implemented')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Read cached result for a specific query string.
|
40
|
+
# Returns array of model instances.
|
41
|
+
def self.read(accessor, query_string)
|
42
|
+
raise ::Exception.new('Not implemented')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create cache entry for a specific query_string on a model.
|
46
|
+
# Expects result from given query string as it has to be
|
47
|
+
# returned when calling Cache_Implementation.read(query_string)
|
48
|
+
def self.create(accessor, query_string, result)
|
49
|
+
raise ::Exception.new('Not implemented')
|
50
|
+
end
|
51
|
+
|
52
|
+
# Whether there is a cache entry for a query or not.
|
53
|
+
def self.include?(accessor, query_string)
|
54
|
+
raise ::Exception.new('Not implemented')
|
55
|
+
end
|
56
|
+
|
57
|
+
# Delete a specific cache entry, parameter index being
|
58
|
+
# primary key (e.g. a hash value) in cache generated from
|
59
|
+
# a query.
|
60
|
+
def self.delete(index)
|
61
|
+
raise ::Exception.new('Not implemented')
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
module Cache_Helpers
|
67
|
+
def index_for(query)
|
68
|
+
Digest::MD5.hexdigest(query)
|
69
|
+
end
|
70
|
+
|
71
|
+
def storefile_of(model_name, query)
|
72
|
+
'/tmp/lore_cache__' << model_name + '_' << Digest::MD5.hexdigest(query)
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_store(storefile_name)
|
76
|
+
store = PStore.new(storefile_name) unless storefile_name.nil?
|
77
|
+
return store
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
data/cache/bits.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Lore
|
3
|
+
module Cache
|
4
|
+
|
5
|
+
@@ignore_params = [ 'cb__model', 'cb__controller', 'cb__mode' ]
|
6
|
+
|
7
|
+
def self.store_name(klass_name, controller, mode, keys)
|
8
|
+
key_string = ''
|
9
|
+
keys.each_pair { |name, value|
|
10
|
+
key_string += '@@@'+name+'==' << value.to_s unless @@ignore_params.include? name
|
11
|
+
}
|
12
|
+
controller = '*' if controller == :all
|
13
|
+
mode = '*' if mode == :all
|
14
|
+
|
15
|
+
store_name = '/tmp/cb__cache__' << klass_name.to_s << '@@@' << controller.to_s << '@@@' << mode.to_s
|
16
|
+
store_name += key_string unless key_string == ''
|
17
|
+
|
18
|
+
return store_name
|
19
|
+
end
|
20
|
+
|
21
|
+
end # module
|
22
|
+
end # module
|