poro 0.1.0 → 0.1.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.
@@ -22,6 +22,11 @@ through whatever inferior homogenized API Poro may try to provide.
22
22
 
23
23
  = Installation
24
24
 
25
+ At this time, Poro has only ever been tested on ruby 1.9.2. It is expected to
26
+ work on any 1.9.x, but may require some effort to make work on 1.8.6 or later.
27
+ By version 1.0 I hope to have 1.8.6 and 1.8.7 support, however the development
28
+ of a production worthy gem in 1.9.2 takes the priority.
29
+
25
30
  Basic usage only requires the installation of the gem:
26
31
  gem install poro
27
32
  However to utilize any meaningful persistence data store, the underlying gems
@@ -155,6 +160,13 @@ mailto:jeff@paploo.net
155
160
 
156
161
  = Version History
157
162
 
163
+ [0.1.1 - 2010-Sep-24] Minor Additions and Bug Fixes.
164
+ * MongoContext now can optionally encode Symbols as hashes
165
+ or just leave them as strings.
166
+ * MongoContext can have conversion to BSON::ObjectId turned off.
167
+ * MongoContext can save Sets in various formats.
168
+ * MongoContext handles namespaced models better.
169
+ * Context doesn't error when trying to find by id.
158
170
  [0.1.0 - 2010-Sep-18] Initial public release.
159
171
  * Major base functionality is complete, though is subject
160
172
  to big changes as it is used in the real world.
@@ -167,7 +179,18 @@ mailto:jeff@paploo.net
167
179
 
168
180
  The following are the primary TODO items, roughly in priority order:
169
181
 
182
+ * YAML Connection Configuration:
183
+ * Make a Util module that is able to use a rails-compatible YAML
184
+ file--given by path--to get the elements needed for configuration of a
185
+ SingleStore factory.
186
+ * Modify SingleStore to use this file for configuration when appropriate.
170
187
  * Modelify: Break into modules for each piece of functionality.
188
+ * Modelify: Add callback functionality to Modelify (e.g. before/after save, after initialize).
189
+ * Add the callbacks to the Context first.
190
+ * Mongo Context: Add option to encode Sets as one of:
191
+ * A Set with the raw internal Hash.
192
+ * A Set with the internal Hash as an Array.
193
+ * An Array (which will have to be manually turned back into a Set).
171
194
  * Specs: Add specs for Context Find methods.
172
195
  * Check that private methods are private. (Should do on subclasses too.)
173
196
  * Check that the two main find methods pass through to the correct underlying
@@ -176,6 +199,8 @@ The following are the primary TODO items, roughly in priority order:
176
199
  * Mongo Context: Split into modules in separate files.
177
200
  * Context: Split out modules into files.
178
201
  * Contexts: Add SQL Context.
202
+ * Ruby: Verify support for ruby 1.9.x.
203
+ * Ruby: Evaluate adding support for ruby 1.8.6 and 1.8.7.
179
204
 
180
205
  = License
181
206
 
@@ -1,4 +1,5 @@
1
1
  # Require foundation.
2
+ require 'poro/version'
2
3
  require 'poro/util'
3
4
 
4
5
  # Require core classes and modules.
@@ -316,7 +316,7 @@ module Poro
316
316
  #
317
317
  # Subclasses SHOULD override this method.
318
318
  #
319
- # By default, this method aggregates separate calls to find_by_id. For
319
+ # By default, this method aggregates separate calls to fetch. For
320
320
  # most data stores this makes N calls to the server, decreasing performance.
321
321
  #
322
322
  # When possible, this method should be overriden by subclasses to be more
@@ -324,7 +324,7 @@ module Poro
324
324
  # filtered by the <tt>clean_id</tt> private method.
325
325
  def find_with_ids(*ids)
326
326
  ids = ids.flatten
327
- return ids.map {|id| find_by_id(id)}
327
+ return ids.map {|id| fetch(id)}
328
328
  end
329
329
 
330
330
  end
@@ -423,7 +423,7 @@ module Poro
423
423
  elsif( arg.respond_to?(:map) )
424
424
  return find_with_ids(arg)
425
425
  else
426
- return find_by_id(arg)
426
+ return fetch(arg)
427
427
  end
428
428
  end
429
429
 
@@ -440,9 +440,9 @@ module Poro
440
440
  # * :first or :one
441
441
  def data_store_find(first_or_all, *args, &block)
442
442
  if(first_or_all == :all || first_or_all == :many)
443
- return data_store_find_all(*first_or_all, &block)
443
+ return data_store_find_all(*args, &block)
444
444
  elsif( first_or_all == :first || first_or_all == :one)
445
- return data_store_find_first(*first_or_all, &block)
445
+ return data_store_find_first(*args, &block)
446
446
  else
447
447
  raise ArgumentError, "#{__method__} expects the first argument to be one of :all, :many, :first, or :one."
448
448
  end
@@ -22,7 +22,7 @@ module Poro
22
22
  raise ArgumentError, "No mongo connection was supplied to #{self.class.name}." if @connection.nil?
23
23
 
24
24
  super() do |klass|
25
- collection_name = Util::Inflector.pluralize(Util::Inflector.underscore(klass.name.to_s))
25
+ collection_name = Util::Inflector.pluralize(Util::Inflector.underscore(klass.name.to_s)).gsub('/', '_')
26
26
  context = Contexts::MongoContext.new(klass)
27
27
  context.data_store = @connection[collection_name]
28
28
  yield(klass, context) if block_given?
@@ -45,6 +45,12 @@ module Poro
45
45
  @persistent_attributes_whitelist = nil
46
46
  @persistent_attributes_blacklist = nil
47
47
 
48
+ # Some configuration variables
49
+ @encode_symbols = false
50
+ @attempt_id_conversion = true
51
+ @set_encoding_method = :embedded_array
52
+
53
+
48
54
  # Initialize
49
55
  super(klass)
50
56
  end
@@ -62,6 +68,27 @@ module Poro
62
68
  attr_reader :persistent_attributes_blacklist
63
69
  attr_writer :persistent_attributes_blacklist
64
70
 
71
+ # If true, it encodes Symbol as a hash with a class name property, and
72
+ # then decodes them back into Symbol. If false, it converts them to
73
+ # String for storage. Defaults to false.
74
+ #
75
+ # While one would think they want to preserve the type of the saved element,
76
+ # the change in storage method makes it harder to write Mongo queries on the
77
+ # data. Thus it is normally best to save symbols as strings for storage.
78
+ attr_reader :encode_symbols
79
+ attr_writer :encode_symbols
80
+
81
+ # Normally, one uses BSON::ObjectId instances for the IDs on a stored
82
+ # Mongo object. However, one can design a database to use many different
83
+ # values as sthe primary key.
84
+ #
85
+ # When this is set to true, fetch tries to convert the given ID into a
86
+ # BSON::ObjectId before doing a fetch. If the conversion fails, it tries using
87
+ # the raw value given. If this is set to false, then it always passes
88
+ # along the raw value, skipping the conversion step.
89
+ attr_reader :attempt_id_conversion
90
+ attr_writer :attempt_id_conversion
91
+
65
92
  def fetch(id)
66
93
  data = data_store.find_one( clean_id(id) )
67
94
  return convert_to_plain_object(data)
@@ -94,9 +121,8 @@ module Poro
94
121
  private
95
122
 
96
123
  def clean_id(id)
97
- #TODO: Make the attempted transform configurable; mongo doesn't require an ObjectId as the key.
98
124
  # Attempt to convert to an ObjectID if it looks like it should be.
99
- if( !(id.kind_of?(BSON::ObjectId)) && BSON::ObjectId.legal?(id.to_s) )
125
+ if( self.attempt_id_conversion && !(id.kind_of?(BSON::ObjectId)) && BSON::ObjectId.legal?(id.to_s) )
100
126
  id = BSON::ObjectId.from_string(id.to_s)
101
127
  end
102
128
  return id
@@ -123,6 +149,7 @@ module Poro
123
149
  obj.kind_of?(Float) ||
124
150
  obj.kind_of?(String) ||
125
151
  obj.kind_of?(Time) ||
152
+ (self.encode_symbols && obj.kind_of?(Symbol)) ||
126
153
  obj==true ||
127
154
  obj==false ||
128
155
  obj.nil? ||
@@ -178,6 +205,10 @@ module Poro
178
205
  return encode_array(obj)
179
206
  elsif( obj.kind_of?(Class) )
180
207
  return encode_class(obj)
208
+ elsif( obj.kind_of?(Set) )
209
+ return encode_set(obj)
210
+ elsif( !self.encode_symbols && obj.kind_of?(Symbol) )
211
+ return encode_symbol(obj)
181
212
  elsif( Context.managed_class?(obj.class) && Context.fetch(obj.class).kind_of?(self.class) )
182
213
  return encode_mongo_managed_object(obj)
183
214
  elsif( Context.managed_class?(obj.class))
@@ -207,6 +238,23 @@ module Poro
207
238
  return {'_class_name' => klass.class, 'name' => klass.name}
208
239
  end
209
240
 
241
+ # Encodes a symbol.
242
+ def encode_symbol(sym)
243
+ return {'_class_name' => 'Symbol', 'value' => sym.to_s}
244
+ end
245
+
246
+ # Encodes a Set as either :raw, :embedded_array, :array.
247
+ def encode_set(set)
248
+ method = @set_encoding_method
249
+ if( method == :raw )
250
+ return encode_unmanaged_object(set)
251
+ elsif( method == :embedded_array )
252
+ return {'_class_name' => 'Set', 'values' => self.convert_to_data(set.to_a, :embedded => true)}
253
+ else
254
+ return self.convert_to_data(set.to_a, :embedded => true)
255
+ end
256
+ end
257
+
210
258
  # Encode a hash that came from a DBRef dereferenced and decoded by this context.
211
259
  #
212
260
  # This will save the hash when its owning object is saved!
@@ -294,6 +342,10 @@ module Poro
294
342
  class_name = data['_class_name'].to_s
295
343
  if( class_name == 'Class' )
296
344
  return decode_class(data)
345
+ elsif( class_name == 'Symbol' )
346
+ return decode_symbol(data)
347
+ elsif( class_name == 'Set' )
348
+ return decode_set(data)
297
349
  elsif( class_name == self.klass.to_s )
298
350
  return decode_self_managed_object(data)
299
351
  elsif( class_name && data['managed'] )
@@ -321,6 +373,27 @@ module Poro
321
373
  return Util::ModuleFinder.find(class_data['name'])
322
374
  end
323
375
 
376
+ # Decode a symbol reference. If this users of the Context expect a Symbol
377
+ # to be encoded as a Symbol, then decode it as a Symbol. Otherwise the
378
+ # users of the Context wil be expecting a String.
379
+ def decode_symbol(symbol_data)
380
+ if self.encode_symbols
381
+ return symbol_data['value'].to_sym
382
+ else
383
+ return symbol_data['value'].to_s
384
+ end
385
+ end
386
+
387
+ # Decode the set depending on if it was encoded as an array or as a raw
388
+ # object.
389
+ def decode_set(set_data)
390
+ if( set_data.include?['values'] )
391
+ return Set.new(set_data['values'])
392
+ else
393
+ return decode_unmanaged_object(set_data)
394
+ end
395
+ end
396
+
324
397
  # Decode a BSON::DBRef. If there is a context for the reference, it is
325
398
  # wrapped in that object type. If there is no context, it is left as
326
399
  # a DBRef so that it will re-save as a DBRef (otherwise it'll save as a
@@ -438,7 +511,7 @@ module Poro
438
511
  end
439
512
 
440
513
  def data_store_find_all(*args, &block)
441
- return data_store.find(*args, &block).to_a.map {|data| self.convert_to_plain_object(doc)}
514
+ return data_store.find(*args, &block).to_a.map {|doc| self.convert_to_plain_object(doc)}
442
515
  end
443
516
 
444
517
  def data_store_find_first(*args, &block)
@@ -0,0 +1,7 @@
1
+ # Poro is a lightweight persistence engine that can use many different data stores
2
+ # to persist information. Poro takes a hands-off approach, attempting to change
3
+ # existing plain ol' ruby objects as little as possible. For more information
4
+ # see README.rdoc.
5
+ module Poro
6
+ VERSION = '0.1.1'
7
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 1
9
+ version: 0.1.1
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-18 00:00:00 -07:00
17
+ date: 2010-09-24 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -47,6 +47,7 @@ files:
47
47
  - lib/poro/util/inflector.rb
48
48
  - lib/poro/util/module_finder.rb
49
49
  - lib/poro/util.rb
50
+ - lib/poro/version.rb
50
51
  - lib/poro.rb
51
52
  - spec/context_factory_spec.rb
52
53
  - spec/context_spec.rb
@@ -60,7 +61,7 @@ files:
60
61
  has_rdoc: true
61
62
  homepage: http://www.github.com/paploo/poro
62
63
  licenses:
63
- - LICENSE
64
+ - BSD
64
65
  post_install_message:
65
66
  rdoc_options: []
66
67
 
@@ -90,6 +91,6 @@ rubyforge_project:
90
91
  rubygems_version: 1.3.7
91
92
  signing_key:
92
93
  specification_version: 3
93
- summary: A lightweight thin persistence engine that can utilize many different persistence engines.
94
+ summary: A lightweight thin persistence engine that can utilize many different persistence data stores.
94
95
  test_files: []
95
96