peace_love 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.
data/License ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 Lachie Cox
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
21
+
data/Rakefile CHANGED
@@ -8,6 +8,8 @@ begin
8
8
  gemspec.homepage = "http://github.com/lachie/peace_love"
9
9
  gemspec.authors = ["Lachie Cox"]
10
10
 
11
+ gemspec.test_files = []
12
+
11
13
  gemspec.add_dependency("mongo", ['~>1.0.0'])
12
14
  gemspec.add_dependency("angry_hash", ['=0.0.5'])
13
15
  end
data/Readme.md ADDED
@@ -0,0 +1,205 @@
1
+ # PeaceLove & Mongo
2
+
3
+ PeaceLove is a simple mixin layer for enhancing hashes retrieved from MongoDB. PeaceLove eschews the mapping compulsion of ruby Mongo libraries.
4
+
5
+ ## Install
6
+
7
+ gem install peace_love
8
+
9
+ ## Basic Usage
10
+
11
+ require 'rubygems'
12
+
13
+ require 'bson_ext'
14
+ require 'mongo'
15
+
16
+ require 'peace_love'
17
+
18
+ (Right now you have to set up your database before defining any mixins. This limitation will be fixed soon.)
19
+
20
+ mongo = Mongo::Connection.new
21
+ PeaceLove.db = mongo['bean_db']
22
+
23
+ Now define some mixins
24
+
25
+ module Bean
26
+ include PeaceLove::Doc
27
+ mongo_collection 'beans'
28
+
29
+ def is_a_bean?; true end
30
+ end
31
+
32
+ Now lets insert something:
33
+
34
+ lima_bean = { :name => 'lima' }
35
+ human_bean = { :name => 'Arthur', :prefered_drinks => %w[tea] }
36
+
37
+ PeaceLove['beans'].insert(lima_bean)
38
+ PeaceLove['beans'].insert(human_bean)
39
+
40
+ `PeaceLove['beans']` returns a `PeaceLove::Collection` which thinly wraps a `Mongo::Collection`. See how to use it in the [Mongo Ruby API][monapi]
41
+
42
+ Notice how we're inserting hashes. PeaceLove only mixes in hashes coming _out_ of Mongo.
43
+
44
+ Also note that the wrapping of `Mongo::Collection` may not yet be complete.
45
+
46
+ ### Fetching single documents
47
+
48
+ Let's fetch Arthur:
49
+
50
+ arthur = PeaceLove['beans'].find_one(:name => 'Arthur')
51
+
52
+ Now what can we do with him?
53
+
54
+ arthur.is_a_bean? #=> true
55
+
56
+ `#is_a_bean?` was defined on the module `Bean`. Nice.
57
+
58
+ Now fetch some plain values:
59
+
60
+ arthur[:prefered_drinks] #=> [ 'tea' ]
61
+ arthur.prefered_drinks #=> [ 'tea' ]
62
+
63
+ arthur.is_a?(Hash) #=> true
64
+ arthur.class #=> AngryHash
65
+
66
+ Among other things, [`AngryHash`][ah] adds dot-notation accessors to hashes.
67
+
68
+ ### Updating
69
+
70
+ Arthur discovered a new drink. Let's update:
71
+
72
+ PeaceLove['beans'].update({:_id => arthur._id}, '$push' => {'prefered_drinks' => 'pan-galactic gargle blaster'})
73
+
74
+ arthur = PeaceLove['beans'].find_one(:name => 'Arthur')
75
+
76
+ arthur.prefered_drinks #=> [ 'tea', 'pan-galactic gargle blaster' ]
77
+
78
+ Here we're using normal Mongo powers to do an atomic push onto `arthur.prefered_drinks`
79
+
80
+ ### Fetching a list
81
+
82
+ PeaceLove['beans'].find(:name => 'lima').each {|bean| # ... work}
83
+
84
+ `#find` returns a `PeaceLove::Cursor` which thinly wraps a `Mongo::Cursor`. It mixes in `Enumerable`.
85
+
86
+ ### Building
87
+
88
+ To build a hash imbued with module powers, without touching mongo, use `#build`:
89
+
90
+ arthur = PeaceLove['beans'].build(:name => 'arthur')
91
+
92
+ ## Sub structure
93
+
94
+ ### Sub documents
95
+
96
+ Sub documents allow you to mix modules into parts of the document.
97
+
98
+ module Taste
99
+ def zesty?
100
+ spicy? && sour?
101
+ end
102
+ end
103
+
104
+ module Bean
105
+ include PeaceLove::Doc
106
+ mongo_collection 'beans'
107
+
108
+ sub_doc :taste, Taste
109
+ end
110
+
111
+ chaos = PeaceLove['beans'].build(:name => 'chaos', :taste => {:sour => true, :spicy => true})
112
+ chaos.taste.zesty? #=> true
113
+
114
+ ### Sub collections
115
+
116
+ Sub collections allow you to mix modules into each element of arrays (and in the future hashes) contained in the document.
117
+
118
+ module Colour
119
+ def happy?
120
+ name == 'red' || name == 'yellow'
121
+ end
122
+ end
123
+
124
+ module Bean
125
+ include PeaceLove::Doc
126
+ mongo_collection 'beans'
127
+
128
+ sub_col :taste, Taste
129
+ end
130
+
131
+ bean = PeaceLove['beans'].build(:name => 'jelly-belly', :colours => [
132
+ {:name => 'red'},
133
+ {:name => 'green'},
134
+ {:name => 'magenta'}
135
+ ])
136
+
137
+ bean.colours[0].name #=> 'red'
138
+ bean.colours[0].happy? #=> true
139
+ bean.colours[1].name #=> 'green'
140
+ bean.colours[1].happy? #=> false
141
+
142
+ ### Railtie
143
+
144
+ There's a simple rails 3 `railtie` for setting up the MongoDB connection using details in `database.yml`.
145
+
146
+ It will probably become more sophisticated over time.
147
+
148
+ ## Rationale
149
+
150
+ or: why not map Mongo?
151
+
152
+ ### Mongo's ruby driver is unusually good
153
+
154
+ Unusually good for database drivers, that is. By contrast SQL drivers (& underlying SQL engines) tend to be a bit shaggy due
155
+ to the profusion of databases and sellers thereof.
156
+ Thus, part of the attraction of an ORM is papering over all the SQL driver shagginess.
157
+
158
+ There's only one MongoDB implementation so far, and only one vendor, 10gen.
159
+
160
+ 10gen has created a good, rubyish driver for it (though apparently it can be a mite laggy, versionwise)
161
+
162
+ Therefore, lets enjoy what we have.
163
+
164
+ ### Mongo's data approach is good.
165
+
166
+ BSON is binary JSON, and Mongo's interface is (mostly) based on it (the notable exception being MapReduce, for which you use Javascript).
167
+
168
+ Day-to-day querying and commanding of the database is done through BSON, which means that its logic free & injection safe.
169
+
170
+ You can do so much with the BSON interface, simply and neatly; I don't quite see the point in abstracting it away behind an expensive ruby interface.
171
+
172
+ ### DIY or don't.
173
+
174
+ If you want to abstract or DRY stuff away, do it yourself, in a `PeaceLove::Doc` module.
175
+
176
+ Or, use one of the mappers :)
177
+
178
+ ## Limitations
179
+
180
+ Documents are hashes. This means that if your hash keys collide with method names, you'll either have to access them using normal `[:key]` syntax
181
+ or you can override the method:
182
+
183
+ def key; self['key'] end # Hash#key is defined in ruby 1.9.
184
+
185
+ The only exception is `Object#id`. In late ruby 1.8's its deprecated but still exists. In ruby 1.9 its been removed. I therefore decided to override it
186
+ in `PeaceLove::Doc` since I wanted to use `#id` to store my own non-mongo ids.
187
+
188
+ ## TODO
189
+
190
+ * The core mixin mechanics of PeaceLove aren't actually bound to MongoDB at all. I'd like to split out the mongo-specific & -non-specific parts.
191
+ * Sub collections can only be arrays. They should be able to be hashes too.
192
+
193
+ ## About
194
+
195
+ Please report problems at http://github.com/lachie/peace_love/issues.
196
+
197
+ PeaceLove is by Lachie Cox.
198
+
199
+ The code is hosted on GitHub and can be found at http://github.com/lachie/peace_love.
200
+
201
+ You're free to use PeaceLove under the MIT license, see License for details.
202
+
203
+ [ah]: http://github.com/plus2/angry_hash
204
+ [monapi]: http://api.mongodb.org/ruby/1.0.5/Mongo/Collection.html
205
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/examples/usage.eg.rb CHANGED
@@ -12,6 +12,9 @@ require 'peace_love'
12
12
  PeaceLove.db = $db
13
13
 
14
14
  module Kind
15
+ include PeaceLove::Doc
16
+ defaults :a_value => {:defaults => 'values'}
17
+
15
18
  def healthy; "healthy" end
16
19
 
17
20
  def kids_love?
@@ -23,9 +26,12 @@ module Bean
23
26
  include PeaceLove::Doc
24
27
  sub_doc :kind , Kind
25
28
  sub_col :examples, Bean
29
+ sub_hash :examples_hash, Bean
26
30
 
27
31
  mongo_collection 'beans'
28
32
 
33
+ defaults :ham => {:sandwich => 'golly yes!'}
34
+
29
35
  def texture; super.upcase end
30
36
  end
31
37
 
@@ -100,6 +106,15 @@ eg 'loading a list of documents' do
100
106
  }
101
107
  end
102
108
 
109
+ eg 'loading a hash of documents' do
110
+ mongo_beans.insert(:name => 'baked', :examples_hash => { 'yummy' => {:named => 'yummy', :texture => 'sandy'}, 'yucky' => {:named => 'yiccko'} } )
111
+
112
+ baked = peace_love_beans.find_one(:name => 'baked')
113
+
114
+ Assert( baked.examples_hash.yummy.named == 'yummy' )
115
+ Assert( baked.examples_hash.yummy.texture == 'SANDY' )
116
+ end
117
+
103
118
  eg 'looking into an array sub collection' do
104
119
  mongo_beans.insert(:name => 'jelly', :texture => 'wibbly', :kind => {:fictional => false},
105
120
  :examples => [
@@ -135,6 +150,27 @@ eg 'the id accessor works in ruby 1.8 & 1.9' do
135
150
  Assert( Bean.build(:id => 'abc').id == 'abc' )
136
151
  end
137
152
 
153
+ eg 'setting defaults on the mixin - via build' do
154
+ bean = Bean.build(:id => 'myd', :ham => {:with_pickles => 'also yes'} )
155
+
156
+ Assert( bean.ham.sandwich == 'golly yes!' )
157
+ Assert( bean.ham.with_pickles == 'also yes' )
158
+
159
+ Assert( bean.kind.a_value.defaults == 'values' )
160
+ end
161
+
162
+ eg 'setting defaults on the mixin - from db' do
163
+ mongo_beans.insert(:name => 'baked' , :ham => {'pigglish' => true}, :kind => {:a_value => {:other => 'apples'}})
164
+
165
+ bean = peace_love_beans.find_one(:name => 'baked')
166
+
167
+ Assert( bean.ham.sandwich == 'golly yes!' )
168
+ Assert( bean.ham.pigglish == true)
169
+
170
+ Assert( bean.kind.a_value.defaults == 'values' )
171
+ Assert( bean.kind.a_value.other == 'apples' )
172
+ end
173
+
138
174
  __END__
139
175
 
140
176
  eg 'looking into a hash sub collection' do
@@ -43,12 +43,13 @@ module PeaceLove
43
43
  hash
44
44
  end
45
45
 
46
- def __extend(hash)
46
+ def __extend(doc)
47
47
  if mixin
48
- hash.extend mixin
49
- hash.__source_collection = self if hash.respond_to?(:__source_collection=)
48
+ doc.extend mixin
49
+ doc.__source_collection = self if doc.respond_to?(:__source_collection=)
50
50
  end
51
- hash
51
+
52
+ doc
52
53
  end
53
54
 
54
55
  (Mongo::Collection.instance_methods - self.instance_methods).each do |name|
@@ -18,11 +18,15 @@ module PeaceLove
18
18
  @object_extensions ||= {}
19
19
  end
20
20
 
21
- def mark_extension(obj,with)
22
- if (previously_with = object_extensions[obj.__id__]) && previously_with != with
23
- raise "object #{obj} has already been extended by a different PeaceLove::Doc (was: #{previously_with}, now: #{with})"
21
+ def mark_extension(doc,mod)
22
+ # puts "mark_extension doc=#{doc.class} mod=#{mod}"
23
+
24
+ if (previous_mod = object_extensions[doc.__id__]) && previous_mod != mod
25
+ raise "doc #{doc} has already been extended by a different PeaceLove::Doc (was: #{previous_mod}, now: #{mod})"
24
26
  end
25
- object_extensions[obj.__id__] = with
27
+ object_extensions[doc.__id__] = mod
28
+
29
+ setup_extended_doc(doc,mod)
26
30
  end
27
31
 
28
32
  def mixin_registry
@@ -37,9 +41,25 @@ module PeaceLove
37
41
  mixin_registry[target_class][field.to_s] = [:array, mod, options]
38
42
  end
39
43
 
44
+ def register_mixin_hash(target_class, field, mod, options)
45
+ mixin_registry[target_class][field.to_s] = [:hash, mod, options]
46
+ end
47
+
40
48
  def extend_doc(doc,mod,parent_obj)
49
+ # puts "extend_doc doc=#{doc.class} mod=#{mod} parent_obj=#{parent_obj.class}"
50
+
51
+ if !parent_obj.nil? && doc.nil?
52
+ doc = AngryHash.new
53
+ end
54
+
41
55
  doc.extend mod
42
- doc.__parent_doc = parent_obj if doc.respond_to?(:__parent_doc=)
56
+
57
+ doc.__parent_doc = parent_doc if doc.respond_to?(:__parent_doc=)
58
+ doc
59
+ end
60
+
61
+ def setup_extended_doc(doc,mod)
62
+ mod.fill_in_defaults(doc) if mod.respond_to?(:fill_in_defaults)
43
63
  doc
44
64
  end
45
65
 
@@ -57,12 +77,15 @@ module PeaceLove
57
77
 
58
78
  case kind
59
79
  when :single
60
- extend_doc(obj,mod,parent_obj)
80
+ obj = extend_doc(obj,mod,parent_obj)
61
81
  when :array
62
82
  # XXX - this is ok for now... we really need to typecheck, perhaps wrap in a smart-array
63
-
64
-
65
- obj.map! {|elt| extend_doc elt, mod, parent_obj}
83
+ obj = obj.map {|elt| extend_doc(elt, mod, parent_obj)}
84
+ when :hash
85
+ obj = obj.inject(AngryHash.new) do |h,(k,elt)|
86
+ h[k] = extend_doc(elt,mod,parent_obj)
87
+ h
88
+ end
66
89
  end
67
90
  end
68
91
 
@@ -105,6 +128,19 @@ module PeaceLove
105
128
  def collection
106
129
  @collection
107
130
  end
131
+
132
+ def defaults(default_form=nil)
133
+ if default_form
134
+ @default_form = default_form
135
+ end
136
+ @default_form
137
+ end
138
+
139
+ def fill_in_defaults(doc)
140
+ if defaults
141
+ doc.reverse_deep_update(defaults)
142
+ end
143
+ end
108
144
 
109
145
  def sub_document(field,mod,options={})
110
146
  Doc.register_mixin(self,field,mod,options)
@@ -116,8 +152,13 @@ module PeaceLove
116
152
  end
117
153
  alias_method :sub_col, :sub_collection
118
154
 
155
+ def sub_hash(field,mod,options={})
156
+ Doc.register_mixin_hash(self,field,mod,options)
157
+ end
158
+
119
159
  def build(seed={})
120
160
  doc = AngryHash[ seed ]
161
+ self.fill_in_defaults(doc)
121
162
  doc.extend self
122
163
  doc
123
164
  end
data/peace_love.gemspec CHANGED
@@ -5,17 +5,19 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{peace_love}
8
- s.version = "0.1.0"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Lachie Cox"]
12
- s.date = %q{2010-07-18}
12
+ s.date = %q{2010-08-02}
13
13
  s.description = %q{A simple mixin layer for enhancing hashes retrieved from MongoDB. It eschews the normal 'mapping' compulsion of mongo libraries.}
14
14
  s.email = %q{lachie@smartbomb.com.au}
15
15
  s.files = [
16
16
  ".gitignore",
17
17
  "Gemfile",
18
+ "License",
18
19
  "Rakefile",
20
+ "Readme.md",
19
21
  "VERSION",
20
22
  "examples/eg.helper.rb",
21
23
  "examples/usage.eg.rb",
@@ -31,13 +33,6 @@ Gem::Specification.new do |s|
31
33
  s.require_paths = ["lib"]
32
34
  s.rubygems_version = %q{1.3.6}
33
35
  s.summary = %q{Peace, Love and Mongo.}
34
- s.test_files = [
35
- "examples/collections.eg.rb",
36
- "examples/documents.eg.rb",
37
- "examples/eg.helper.rb",
38
- "examples/readme.eg.rb",
39
- "examples/usage.eg.rb"
40
- ]
41
36
 
42
37
  if s.respond_to? :specification_version then
43
38
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
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
  - Lachie Cox
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-07-18 00:00:00 +10:00
17
+ date: 2010-08-02 00:00:00 +10:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -56,7 +56,9 @@ extra_rdoc_files: []
56
56
  files:
57
57
  - .gitignore
58
58
  - Gemfile
59
+ - License
59
60
  - Rakefile
61
+ - Readme.md
60
62
  - VERSION
61
63
  - examples/eg.helper.rb
62
64
  - examples/usage.eg.rb
@@ -96,9 +98,5 @@ rubygems_version: 1.3.6
96
98
  signing_key:
97
99
  specification_version: 3
98
100
  summary: Peace, Love and Mongo.
99
- test_files:
100
- - examples/collections.eg.rb
101
- - examples/documents.eg.rb
102
- - examples/eg.helper.rb
103
- - examples/readme.eg.rb
104
- - examples/usage.eg.rb
101
+ test_files: []
102
+
File without changes
File without changes
@@ -1,58 +0,0 @@
1
- require 'eg.helper'
2
- require 'peace_love'
3
-
4
- PeaceLove.db = $db
5
-
6
- eg.setup do
7
- PeaceLove['beans'].remove
8
- end
9
-
10
- module Bean
11
- include PeaceLove::Doc
12
- mongo_collection 'beans'
13
-
14
- def is_a_bean?; true end
15
- end
16
-
17
- eg 'arthur' do
18
- lima_bean = { :name => 'lima' }
19
- human_bean = { :name => 'Arthur', :prefered_drinks => %w[tea] }
20
-
21
- PeaceLove['beans'].insert(lima_bean)
22
- PeaceLove['beans'].insert(human_bean)
23
-
24
- arthur = PeaceLove['beans'].find_one(:name => 'Arthur')
25
-
26
- Show(arthur)
27
-
28
- Show( arthur.prefered_drinks ) #=> [ 'tea' ]
29
- Show( arthur.is_a_bean? ) #=> true
30
-
31
- Show(arthur.is_a? Hash)
32
- Show(arthur.class)
33
-
34
- PeaceLove['beans'].update({:name => 'Arthur'}, '$push' => {'prefered_drinks' => 'pan-galactic gargle blaster'})
35
- arthur = PeaceLove['beans'].find_one(:name => 'Arthur')
36
-
37
- Show( arthur.prefered_drinks )
38
- end
39
-
40
- eg 'chaos taste' do
41
- module Taste
42
- def zesty?
43
- spicy? && sour?
44
- end
45
- end
46
-
47
- module Bean2
48
- include PeaceLove::Doc
49
- mongo_collection 'beans'
50
-
51
- sub_doc :taste, Taste
52
- end
53
-
54
- PeaceLove['beans'].mixin = Bean2
55
-
56
- chaos = PeaceLove['beans'].build(:name => 'chaos', :taste => {:sour => true, :spicy => true})
57
- Show( chaos.taste.zesty? ) #=> true
58
- end