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.
@@ -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.
@@ -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.configure_for_klass(klass)
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
@@ -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
- self.set_primary_key_value(obj, obj.object_id) if(pk_id.nil?)
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[obj.id] = convert_to_data(obj)
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(obj.id)
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
- # 1. When matching the last key in the list, we can stop processing when
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
- data = limit( filter( sort( data_store.dup, opts[:order] ), opts[:conditions] ), opts[:limit])
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( !self.encode_symbols && obj.kind_of?(Symbol) )
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(value, :embedded => true)
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?['values'] )
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)
@@ -3,5 +3,5 @@
3
3
  # existing plain ol' ruby objects as little as possible. For more information
4
4
  # see README.rdoc.
5
5
  module Poro
6
- VERSION = '0.1.1'
6
+ VERSION = '0.1.2'
7
7
  end
@@ -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 = {'id' => 3, 'first_name' => 'Bridgette', 'last_name' => 'Smith'}
90
- karen_zeta = {:id => 4, :first_name => 'Karen', :last_name => 'Zeta', :friends => [george_archer, george_smith]}
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
- - 1
9
- version: 0.1.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-24 00:00:00 -07:00
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