peace_love 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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