poro 0.1.1 → 0.1.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/README.rdoc +4 -0
- data/lib/poro/context.rb +6 -1
- data/lib/poro/context_factories.rb +2 -1
- data/lib/poro/context_factories/namespace_factory.rb +118 -0
- data/lib/poro/context_factory.rb +6 -1
- data/lib/poro/contexts/hash_context.rb +32 -11
- data/lib/poro/contexts/mongo_context.rb +4 -4
- data/lib/poro/version.rb +1 -1
- data/spec/hash_context_spec.rb +8 -7
- data/spec/namespace_context_factory_spec.rb +94 -0
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -160,6 +160,10 @@ mailto:jeff@paploo.net
|
|
160
160
|
|
161
161
|
= Version History
|
162
162
|
|
163
|
+
[0.1.2 - 2010-Sep-30] Feature Additions
|
164
|
+
* Added a module namespace factory.
|
165
|
+
* HashContext: Find one is faster when the conditions restrict on the primary key.
|
166
|
+
* Many HashContext bugs fixed.
|
163
167
|
[0.1.1 - 2010-Sep-24] Minor Additions and Bug Fixes.
|
164
168
|
* MongoContext now can optionally encode Symbols as hashes
|
165
169
|
or just leave them as strings.
|
data/lib/poro/context.rb
CHANGED
@@ -37,7 +37,7 @@ module Poro
|
|
37
37
|
#
|
38
38
|
# This really just fetches (and creates, if necessary) the
|
39
39
|
# Context for the class, and then yields it to the block. Returns the context.
|
40
|
-
def self.
|
40
|
+
def self.configure_for_class(klass)
|
41
41
|
context = self.fetch(klass)
|
42
42
|
yield(context) if block_given?
|
43
43
|
return context
|
@@ -53,6 +53,11 @@ module Poro
|
|
53
53
|
ContextFactory.instance = context_factory
|
54
54
|
end
|
55
55
|
|
56
|
+
# Returns true if there is a factory assigned to the application.
|
57
|
+
def self.factory?
|
58
|
+
ContextFactory.has_instance?
|
59
|
+
end
|
60
|
+
|
56
61
|
# Initizialize this context for the given class. Yields self if a block
|
57
62
|
# is given, so that instances can be easily configured at instantiation.
|
58
63
|
#
|
@@ -1 +1,2 @@
|
|
1
|
-
require 'poro/context_factories/single_store'
|
1
|
+
require 'poro/context_factories/single_store'
|
2
|
+
require 'poro/context_factories/namespace_factory'
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Poro
|
2
|
+
module ContextFactories
|
3
|
+
# The NamespaceFactory uses the module namespace of the class to determine
|
4
|
+
# which factory should be used to create a class instance.
|
5
|
+
#
|
6
|
+
# On initialization, the default factory should be set. Until other
|
7
|
+
# factories are registerd, all classes will recieve contexts created by
|
8
|
+
# this factory.
|
9
|
+
#
|
10
|
+
# Other factories can be registered against various namespaces. Namespaces
|
11
|
+
# are given as a string and are evaluated from the bottom up. For example,
|
12
|
+
# if a factory is registered against the class Foo::Bar, then Foo::Bar and
|
13
|
+
# Foo::Bar::Baz would each recieve contexts created via that factory, while
|
14
|
+
# contexts registered against Foo::Zap would use the default context.
|
15
|
+
class NamespaceFactory < ContextFactory
|
16
|
+
|
17
|
+
# Initialize this namespace factory instance with the given default
|
18
|
+
# factory. Use <tt>register_factory</tt> to add more kinds of factories
|
19
|
+
# to this app.
|
20
|
+
#
|
21
|
+
# If given a block, it is yielded self for further configuration. This
|
22
|
+
# departs slightly from the standard of yielding a Class or
|
23
|
+
# Class and Context for configuration, but as this is more useful for
|
24
|
+
# practicle applications. (It also doesn't configure a context itself,
|
25
|
+
# so giving separate blocks to the sub-factories given to it is more
|
26
|
+
# appropriate.
|
27
|
+
def initialize(default_factory=nil)
|
28
|
+
@root_node = CacheNode.new
|
29
|
+
@root_node.factory = default_factory
|
30
|
+
|
31
|
+
yield(self) if block_given?
|
32
|
+
|
33
|
+
super() do |klass|
|
34
|
+
factory = self.fetch_factory(klass)
|
35
|
+
factory.nil? ? nil : factory.fetch(klass)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Registers a factory for a given namespace, given by a string. If a
|
40
|
+
# factory is already registered for this namespace, it overrides it.
|
41
|
+
#
|
42
|
+
# This registers the given factory not only for this namespace, but for
|
43
|
+
# any sub-namespaces that haven't been specifically overriden. Sub-namespace
|
44
|
+
# overrides can be registered either before or after the namespace it overrides.
|
45
|
+
def register_factory(factory, namespace='')
|
46
|
+
namespace_stack = namespace.to_s.split('::')
|
47
|
+
|
48
|
+
parse_block = lambda do |factory, namespace_stack, node|
|
49
|
+
if( namespace_stack.length == 0 )
|
50
|
+
node.factory = factory
|
51
|
+
else
|
52
|
+
name, *rest = namespace_stack
|
53
|
+
child_node = node.children[name] || CacheNode.new
|
54
|
+
node.children[name] = child_node
|
55
|
+
parse_block.call(factory, rest, child_node)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
parse_block.call(factory, namespace_stack, @root_node)
|
60
|
+
|
61
|
+
return factory
|
62
|
+
end
|
63
|
+
|
64
|
+
# Fetches the factory for a given namespace.
|
65
|
+
#
|
66
|
+
# This grabs the factory for the most specific matching namespace.
|
67
|
+
def fetch_factory(namespace='')
|
68
|
+
namespace_stack = namespace.to_s.split('::')
|
69
|
+
|
70
|
+
lookup_block = lambda do |namespace_stack, last_seen_factory, node|
|
71
|
+
last_seen_factory = node.factory || last_seen_factory
|
72
|
+
if( namespace_stack.length == 0 )
|
73
|
+
last_seen_factory
|
74
|
+
elsif( !(node.children.include?(namespace_stack[0])) )
|
75
|
+
last_seen_factory
|
76
|
+
else
|
77
|
+
name, *rest = namespace_stack
|
78
|
+
lookup_block.call(rest, last_seen_factory, node.children[name])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
return lookup_block.call(namespace_stack, nil, @root_node)
|
83
|
+
end
|
84
|
+
|
85
|
+
# The internal class used to manage the namespace tree. This should
|
86
|
+
# never be used outside of this factory.
|
87
|
+
#
|
88
|
+
# This class stores the child notes for the namespace, as well as the
|
89
|
+
# factory for this level in the namespace.
|
90
|
+
class CacheNode
|
91
|
+
|
92
|
+
# Initialize an empty node.
|
93
|
+
def initialize
|
94
|
+
@children = {}
|
95
|
+
@factory = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the children--as a hash. The keys are the module/class name,
|
99
|
+
# and the values are the associated child node.
|
100
|
+
#
|
101
|
+
# This is the raw hash and can be manipulated directly.
|
102
|
+
attr_reader :children
|
103
|
+
|
104
|
+
# Returns the factory for this node, or nil if there is none.
|
105
|
+
attr_reader :factory
|
106
|
+
|
107
|
+
# Sets the factory for this node.
|
108
|
+
attr_writer :factory
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
return {:factory => factory, :children => children}.inspect
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/poro/context_factory.rb
CHANGED
@@ -23,6 +23,11 @@ module Poro
|
|
23
23
|
@instance = instance
|
24
24
|
end
|
25
25
|
|
26
|
+
# Returns true if a context factory instance is configured.
|
27
|
+
def self.has_instance?
|
28
|
+
return (@instance != nil)
|
29
|
+
end
|
30
|
+
|
26
31
|
# Takes a factory block that delivers a configured context for the class
|
27
32
|
# passed to it.
|
28
33
|
def initialize(&context_factory_block)
|
@@ -31,7 +36,7 @@ module Poro
|
|
31
36
|
end
|
32
37
|
|
33
38
|
def context_managed_class?(klass)
|
34
|
-
return klass.include?(Poro::Persistify)
|
39
|
+
return klass && klass.include?(Poro::Persistify)
|
35
40
|
end
|
36
41
|
|
37
42
|
# Fetches the context for a given class, or returns nil if the given object
|
@@ -18,9 +18,12 @@ module Poro
|
|
18
18
|
# Save the object in the underlying hash, using the object id as the key.
|
19
19
|
def save(obj)
|
20
20
|
pk_id = self.primary_key_value(obj)
|
21
|
-
|
21
|
+
if(pk_id.nil?)
|
22
|
+
pk_id = obj.object_id
|
23
|
+
self.set_primary_key_value(obj, pk_id)
|
24
|
+
end
|
22
25
|
|
23
|
-
data_store[
|
26
|
+
data_store[pk_id] = convert_to_data(obj)
|
24
27
|
return self
|
25
28
|
end
|
26
29
|
|
@@ -28,7 +31,7 @@ module Poro
|
|
28
31
|
def remove(obj)
|
29
32
|
pk_id = self.primary_key_value(obj)
|
30
33
|
if( pk_id != nil )
|
31
|
-
data_store.delete(
|
34
|
+
data_store.delete(pk_id)
|
32
35
|
self.set_primary_key_value(obj, nil)
|
33
36
|
end
|
34
37
|
return self
|
@@ -44,24 +47,42 @@ module Poro
|
|
44
47
|
|
45
48
|
private
|
46
49
|
|
47
|
-
def clean_id(id)
|
48
|
-
return id && id.to_i
|
49
|
-
end
|
50
|
-
|
51
50
|
# Searching a hash is incredibly slow because the following steps must
|
52
51
|
# be taken:
|
53
52
|
# 1. If there is an order, we first have to sort ALL values by the order.
|
54
53
|
# 2. Then we must find all matching records.
|
55
54
|
# 3. Then we must apply limit and offset to fetch the correct record.
|
56
55
|
#
|
56
|
+
# There are several optimizations to this that have already been done:
|
57
|
+
# * If the conditions include the primary key, use fetch and drop that
|
58
|
+
# condition.
|
59
|
+
# * If the offset is higher than the total number of stored records, then
|
60
|
+
# we know there will be no matches.
|
61
|
+
#
|
57
62
|
# There are several optimizations that can be made in the future:
|
58
|
-
#
|
63
|
+
# * When matching the last key in the list, we can stop processing when
|
59
64
|
# we reach the limit+offset number of records.
|
60
|
-
# 2. If the offset is higher than the total number of stored records, then
|
61
|
-
# we know there will be no matches.
|
62
65
|
def find_all(opts)
|
63
66
|
opts = clean_find_opts(opts)
|
64
|
-
|
67
|
+
|
68
|
+
# If the offset is bigger than the stored number of records, we know that
|
69
|
+
# we'll get nothing:
|
70
|
+
return [] if( (opts[:limit]&&opts[:limit][:offset]).to_i > data_store.length )
|
71
|
+
|
72
|
+
# If a search condition is the primary key, we can significantly limit our work.
|
73
|
+
values = nil
|
74
|
+
data = nil
|
75
|
+
if( opts[:conditions].has_key?( self.primary_key ) )
|
76
|
+
pk_value = opts[:conditions].delete(self.primary_key)
|
77
|
+
obj = self.fetch( pk_value )
|
78
|
+
values = obj.nil? ? [] : [obj]
|
79
|
+
data = limit( filter( values, opts[:conditions]), opts[:limit] )
|
80
|
+
else
|
81
|
+
values = data_store.values
|
82
|
+
data = limit( filter( sort( values, opts[:order] ), opts[:conditions] ), opts[:limit])
|
83
|
+
end
|
84
|
+
|
85
|
+
# Now do the search.
|
65
86
|
return data.map {|data| convert_to_plain_object(data)}
|
66
87
|
end
|
67
88
|
|
@@ -149,7 +149,7 @@ module Poro
|
|
149
149
|
obj.kind_of?(Float) ||
|
150
150
|
obj.kind_of?(String) ||
|
151
151
|
obj.kind_of?(Time) ||
|
152
|
-
(self.encode_symbols && obj.kind_of?(Symbol)) ||
|
152
|
+
(!self.encode_symbols && obj.kind_of?(Symbol)) ||
|
153
153
|
obj==true ||
|
154
154
|
obj==false ||
|
155
155
|
obj.nil? ||
|
@@ -207,7 +207,7 @@ module Poro
|
|
207
207
|
return encode_class(obj)
|
208
208
|
elsif( obj.kind_of?(Set) )
|
209
209
|
return encode_set(obj)
|
210
|
-
elsif(
|
210
|
+
elsif( self.encode_symbols && obj.kind_of?(Symbol) )
|
211
211
|
return encode_symbol(obj)
|
212
212
|
elsif( Context.managed_class?(obj.class) && Context.fetch(obj.class).kind_of?(self.class) )
|
213
213
|
return encode_mongo_managed_object(obj)
|
@@ -223,7 +223,7 @@ module Poro
|
|
223
223
|
# Recursively encode a hash's contents.
|
224
224
|
def encode_hash(hash)
|
225
225
|
return hash.inject({}) do |hash,(k,v)|
|
226
|
-
hash[k] = self.convert_to_data(
|
226
|
+
hash[k] = self.convert_to_data(v, :embedded => true)
|
227
227
|
hash
|
228
228
|
end
|
229
229
|
end
|
@@ -387,7 +387,7 @@ module Poro
|
|
387
387
|
# Decode the set depending on if it was encoded as an array or as a raw
|
388
388
|
# object.
|
389
389
|
def decode_set(set_data)
|
390
|
-
if( set_data.include?
|
390
|
+
if( set_data.include?('values') )
|
391
391
|
return Set.new(set_data['values'])
|
392
392
|
else
|
393
393
|
return decode_unmanaged_object(set_data)
|
data/lib/poro/version.rb
CHANGED
data/spec/hash_context_spec.rb
CHANGED
@@ -76,18 +76,21 @@ describe "HashContext" do
|
|
76
76
|
|
77
77
|
class HashContextPerson
|
78
78
|
include Poro::Persistify
|
79
|
-
def initialize(id, first_name, last_name, friends)
|
79
|
+
def initialize(id, first_name, last_name, friends=[])
|
80
80
|
@id = id
|
81
81
|
@first_name = first_name
|
82
82
|
@last_name = last_name
|
83
83
|
@friends = friends
|
84
84
|
end
|
85
|
+
|
86
|
+
attr_reader :id
|
87
|
+
attr_writer :id
|
85
88
|
end
|
86
89
|
|
87
|
-
george_smith = HashContextPerson.new(1, 'George', 'Smith'
|
90
|
+
george_smith = HashContextPerson.new(1, 'George', 'Smith')
|
88
91
|
george_archer = HashContextPerson.new(2, 'George', 'Archer', [george_smith])
|
89
|
-
bridgette_smith =
|
90
|
-
karen_zeta =
|
92
|
+
bridgette_smith = HashContextPerson.new(3, 'Bridgette', 'Smith')
|
93
|
+
karen_zeta = HashContextPerson.new(4, 'Karen', 'Zeta', [george_archer, george_smith])
|
91
94
|
@data = [
|
92
95
|
george_smith,
|
93
96
|
george_archer,
|
@@ -96,9 +99,7 @@ describe "HashContext" do
|
|
96
99
|
]
|
97
100
|
|
98
101
|
@context = Poro::Contexts::HashContext.new(HashContextPerson)
|
99
|
-
|
100
|
-
# Cheat to jam the data in there:
|
101
|
-
@context.data_store = @data
|
102
|
+
@data.each {|person| @context.save(person)}
|
102
103
|
end
|
103
104
|
|
104
105
|
it 'should get shallow values' do
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe "Namespace Context Factory" do
|
4
|
+
|
5
|
+
it 'should register and fetch a default factory' do
|
6
|
+
default_factory = :default_factory
|
7
|
+
|
8
|
+
factory = Poro::ContextFactories::NamespaceFactory.new(default_factory)
|
9
|
+
factory.fetch_factory.should == default_factory
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should register and fetch a namespaced factory' do
|
13
|
+
default_factory = :default_factory
|
14
|
+
foo_factory = :foo_factory
|
15
|
+
|
16
|
+
factory = Poro::ContextFactories::NamespaceFactory.new(default_factory)
|
17
|
+
factory.register_factory(foo_factory, "Foo")
|
18
|
+
|
19
|
+
factory.fetch_factory.should == default_factory
|
20
|
+
factory.fetch_factory("Foo").should == foo_factory
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should get the nearest matching factory' do
|
24
|
+
default_factory = :default_factory
|
25
|
+
foo_factory = :foo_factory
|
26
|
+
bar_factory = :bar_factory
|
27
|
+
chocolate_factory = :chocolate_factory
|
28
|
+
|
29
|
+
factory = Poro::ContextFactories::NamespaceFactory.new(default_factory)
|
30
|
+
factory.register_factory(foo_factory, "Foo")
|
31
|
+
factory.register_factory(bar_factory, "Foo::Bar")
|
32
|
+
factory.register_factory(chocolate_factory, "Candy::Chocolate")
|
33
|
+
|
34
|
+
factory.fetch_factory.should == default_factory
|
35
|
+
|
36
|
+
factory.fetch_factory("Foo").should == foo_factory
|
37
|
+
factory.fetch_factory("Foo::Bar").should == bar_factory
|
38
|
+
factory.fetch_factory("Foo::Bar::Baz").should == bar_factory
|
39
|
+
factory.fetch_factory("Foo::Fiz").should == foo_factory
|
40
|
+
|
41
|
+
factory.fetch_factory("Chocolate").should == default_factory
|
42
|
+
factory.fetch_factory("Candy::Chocolate").should == chocolate_factory
|
43
|
+
factory.fetch_factory("Candy::Chocolate::Dark").should == chocolate_factory
|
44
|
+
factory.fetch_factory("Candy").should == default_factory
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should fetch a context' do
|
48
|
+
@klass_1 = Class.new(Object)
|
49
|
+
@klass_1.send(:include, Poro::Persistify)
|
50
|
+
@klass_2 = Class.new(Object)
|
51
|
+
@klass_2.send(:include, Poro::Persistify)
|
52
|
+
::PoroNamespacedContextFactoryTestFetchOne = @klass_1
|
53
|
+
::PoroNamespacedContextFactoryTestFetcgTwo = @klass_2
|
54
|
+
|
55
|
+
@klass_1.name.should_not == @klass_2.name
|
56
|
+
|
57
|
+
default_factory = Poro::ContextFactory.new {|klass| "default #{klass.name}"}
|
58
|
+
foo_factory = Poro::ContextFactory.new {|klass| "foo #{klass.name}"}
|
59
|
+
|
60
|
+
factory = Poro::ContextFactories::NamespaceFactory.new(default_factory)
|
61
|
+
factory.register_factory(foo_factory, @klass_2.name)
|
62
|
+
|
63
|
+
context = factory.fetch(@klass_1)
|
64
|
+
context.should == "default #{@klass_1.name}"
|
65
|
+
|
66
|
+
context = factory.fetch(@klass_2)
|
67
|
+
context.should == "foo #{@klass_2.name}"
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should take a configuration block' do
|
71
|
+
@klass_1 = Class.new(Object)
|
72
|
+
@klass_1.send(:include, Poro::Persistify)
|
73
|
+
@klass_2 = Class.new(Object)
|
74
|
+
@klass_2.send(:include, Poro::Persistify)
|
75
|
+
::PoroNamespacedContextFactoryTestBlockOne = @klass_1
|
76
|
+
::PoroNamespacedContextFactoryTestBlockTwo = @klass_2
|
77
|
+
|
78
|
+
@klass_1.name.should_not == @klass_2.name
|
79
|
+
|
80
|
+
default_factory = Poro::ContextFactory.new {|klass| "default #{klass.name}"}
|
81
|
+
foo_factory = Poro::ContextFactory.new {|klass| "foo #{klass.name}"}
|
82
|
+
|
83
|
+
factory = Poro::ContextFactories::NamespaceFactory.new(default_factory) do |f|
|
84
|
+
f.register_factory(foo_factory, @klass_2.name)
|
85
|
+
end
|
86
|
+
|
87
|
+
context = factory.fetch(@klass_1)
|
88
|
+
context.should == "default #{@klass_1.name}"
|
89
|
+
|
90
|
+
context = factory.fetch(@klass_2)
|
91
|
+
context.should == "foo #{@klass_2.name}"
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 2
|
9
|
+
version: 0.1.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jeff Reinecke
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-30 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -31,6 +31,7 @@ files:
|
|
31
31
|
- LICENSE.txt
|
32
32
|
- Rakefile
|
33
33
|
- lib/poro/context.rb
|
34
|
+
- lib/poro/context_factories/namespace_factory.rb
|
34
35
|
- lib/poro/context_factories/README.txt
|
35
36
|
- lib/poro/context_factories/single_store/hash_factory.rb
|
36
37
|
- lib/poro/context_factories/single_store/mongo_factory.rb
|
@@ -56,6 +57,7 @@ files:
|
|
56
57
|
- spec/modelfy.rb
|
57
58
|
- spec/module_finder_spec.rb
|
58
59
|
- spec/mongo_context_spec.rb
|
60
|
+
- spec/namespace_context_factory_spec.rb
|
59
61
|
- spec/single_store_spec.rb
|
60
62
|
- spec/spec_helper.rb
|
61
63
|
has_rdoc: true
|