candy 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ # Candy History
2
+
3
+ This document aims to provide only an overview. Further, we've only really been tracking things since **v0.2**. For obsessive detail, just check out the `git log`.
4
+
5
+ ## v0.2.1 - 2010-04-04 (the "Oops" release)
6
+
7
+ I screwed up in my use of Jeweler, and managed to get my versions out of sync between Github and Rubygems.org. I tried to `gem yank` the one from Rubygems, but it won't let me push again with the same version number. To justify bumping the patch number, I added this changelog. Yeah, I know. Pathetic.
8
+
9
+ * The HISTORY file you're reading
10
+
11
+ ## v0.2.0 - 2010-04-04 (the "Candy for Easter" release)
12
+
13
+ A nearly total rewrite. Some specs still remain from v0.1, but very little actual code. Added in this release were:
14
+
15
+ * Candy::Collection
16
+ * Candy::CandyHash
17
+ * Candy::CandyArray
18
+ * Dynamic class methods for finders
19
+ * Object embedding
20
+ * Module-level configuration properties (`Candy.host`, `Candy.db`, etc.)
21
+ * A novel-length README file
22
+ * The Don't Be a Dick License
23
+ * Other stuff I've surely forgotten about
24
+
25
+ ## v0.1 - 2010-02-16
26
+
27
+ Let's just call this one a "proof of concept" release. It worked, but clumsily. Only Candy::Piece was really implemented, with no embedding, and the bulk of the code was directly in `method_missing`. Global variables like **$MONGO_HOST** were used instead of module properties, and the separation of driver and framework concerns was nonexistent. Please don't look at it. You'll make me blush.
@@ -0,0 +1,110 @@
1
+ The Don't Be a Dick License
2
+ ===========================
3
+ _version 0.1_
4
+
5
+ **Project:** [Candy](http://github.com/SFEley/candy) v0.2
6
+ **Author:** Stephen Eley (<sfeley@gmail.com>)
7
+
8
+ This is a proposed draft of the **Don't Be a Dick** license for open source projects. The purpose of this license is to permit the broadest feasible scope for reuse and modification of creative work, restricted only by the requirement that one is not a dick about it.
9
+
10
+ 1. Legal Parameters
11
+ -------------------
12
+ For legal purposes, the DBAD license is a superset of the [Apache License, Version 2.0][1] and incorporates all terms, conditions, privileges and limitations therein. The following text is a binding part of this license for this project:
13
+
14
+ > Copyright 2010 Stephen Eley
15
+
16
+ > Licensed under the Apache License, Version 2.0 (the "License");
17
+ you may not use this file except in compliance with the License.
18
+ You may obtain a copy of the License at
19
+
20
+ > <http://www.apache.org/licenses/LICENSE-2.0>
21
+
22
+ > Unless required by applicable law or agreed to in writing, software
23
+ distributed under the License is distributed on an "AS IS" BASIS,
24
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
+ See the License for the specific language governing permissions and
26
+ limitations under the License.
27
+
28
+ Nothing in the following text should be construed to inhibit, contradict, or override any part of the Apache License, Version 2.0.
29
+
30
+ 2. Definitions
31
+ --------------
32
+ The following terms and definitions shall be in effect for the rest of this document.
33
+
34
+ > **NOTE:** Singular nouns and pronouns are used throughout this License for
35
+ grammatical convenience. However, any of the terms below _may_ refer to a
36
+ collective work or group of people. If this pegs your punctiliousness, you
37
+ are directed to mentally substitute "Person _or persons_," "Dick _or
38
+ dicks,_" etc., throughout this license. Just don't tell us about it.
39
+
40
+ ### A. Project
41
+
42
+ A creative work of (software | writing | visual art | music) into which significant time and energy have been invested by people who _are not you,_ and which has been released into the world for the benefit of the general public (_including you._)
43
+
44
+ The **Project** may include, incorporate, derive from, or be inspired by other works. The Author, being a Reasonable Person, has made use of such source materials only as permitted by their own licenses or applicable law. The License you are reading applies only to those portions of the Project which are original and distinct to it. Source materials may be covered by their own licenses or conditions, and should not be assumed to have coverage under this License. (However, you are strongly encouraged not to be a dick about them either.)
45
+
46
+ ### B. Author
47
+
48
+ A person who has invested significant time and energy into the Project licensed herein. Given the Author's release of the Project to the world under a liberal license, the Author is declared a Reasonable Person (at least within this context) and inherits all attributes, freedoms, and responsibilities thereof. No other assumptions are made about the Author, nor should you make any.
49
+
50
+ ### C. Reasonable Person
51
+
52
+ A person who respects the time and energy that have been invested in the Project licensed herein, and acts accordingly. A Reasonable Person is broadly characterized as one who exercises his or her privilege to use, _not_ use, redistribute, modify, improve, worsen, review, report issues upon, participate in the community of, or ignore the work _without_ placing undue demands upon the Author. I.e., a Reasonable Person is a constituent of the majority of open source users and the population at large who are not Dicks.
53
+
54
+ ### D. Dick
55
+
56
+ A person who _does not_ respect the time and energy that have been invested in the Project, and acts to punish such effort by giving others associated with the Project -- including, but not limited to, the Author -- a hard time. A Dick is nearly always selfish, but not necessarily with deliberate intent; some Dicks are merely thoughtless. The distinguishing characteristic of a Dick is that he or she places burdens upon Reasonable People, reducing their motivation to engage in open source activities. This damping effect is a significant detriment to the Project, to open source in general, to the production of new intellectual value in the world -- and, ultimately, to the Dick himself or herself.
57
+
58
+ > **NOTE:** Despite its original gender association, the word "Dick" is used herein in a _cultural_ context and not a _genital_ context. This License has chosen to adopt the term for its linguistic force and psychological impact, and sincerely regrets any inference of sexism. For purposes of the terms and conditions contained herein, it is understood that both men and women are equally capable of being Dicks.
59
+
60
+ > (But they shouldn't be.)
61
+
62
+ 3. Permissions
63
+ --------------
64
+
65
+ The following privileges are granted explicitly and exclusively to Reasonable People. Although the Author acknowledges the practical unfeasibility of barring Dicks from enjoying the same privileges, they are nevertheless asked to refrain from any activity related to the Project _and/or_ to reconsider their behavior and stop being Dicks.
66
+
67
+ 1. You are permitted to use the Project or any component of the Project.
68
+
69
+ 2. You are permitted to redistribute the Project or any component of the Project, by itself or as part of a different work, provided that you give fair and reasonable credit back to the Author.
70
+
71
+ 3. You are permitted to change the Project for your own needs or anyone else's. You may keep your changes to yourself or submit them back to the Author or the community, as you see fit, provided you are not a Dick about it.
72
+
73
+ 4. You are permitted and encouraged to participate in any community related to the Project, or to create new communities.
74
+
75
+ 5. You are permitted to report issues or problems with the Project, and to request that they be addressed, so long as the request is made in a reasonable fashion. (This privilege does _not_ oblige the Author to respond.)
76
+
77
+ 6. You are permitted to make money from the Project. No recompense is owed to the Author unless by separate agreement, although sharing opportunities for mutual benefit is by no means discouraged.
78
+
79
+ 7. You are permitted to discuss the Project in any medium, in any positive or negative spirit, so long as criticism is not libelous nor _ad hominem._ (I.e., don't lie, and keep it about the _work_ and not the _people._)
80
+
81
+ 8. You are permitted to ignore the Project completely and make no use of it whatsoever. This right is irrevocable and absolute, and extended to Dicks and Reasonable People alike.
82
+
83
+ 4. Limitations
84
+ --------------
85
+
86
+ The following restrictions are imposed explicitly and exclusively upon Dicks. These limitations are the inverse of the above privileges and are also tautological, as the prohibited actions are those _definitive_ of Dickhood.
87
+
88
+ 1. You may not impede Reasonable People from exercising their privilege to use, redistribute, change, participate in, profit from, discuss, or ignore the Project.
89
+
90
+ 2. You may not withhold the Author's credit for the Project, nor otherwise present the work of anyone else as your own.
91
+
92
+ 3. You may not hold the Author responsible for any use or abuse of the Project by you or anyone else.
93
+
94
+ 4. You may not troll, flame, nor dumb down any community associated with the Project.
95
+
96
+ 5. Barring separate agreements, you may not _demand_ any time or attention on the part of the Author nor anyone else in the community. You may not hold the Author personally accountable for any issues you discover nor otherwise spread your own problems to other people.
97
+
98
+ 6. You may not attempt to _compel_ time nor money from the Author nor any other community member by any means, including but not limited to legal action, intimidation, or annoyance.
99
+
100
+ 7. You may not engage in _ad hominem_ (i.e. personal) attacks or criticism of the Author in the course of criticizing the Project.
101
+
102
+ 8. You may not impede the absolute and irrevocable privilege of the Author and other members of the community to ignore you.
103
+
104
+ 5. Contact
105
+ ----------
106
+ For more information about this Project or the DBAD License, contact the Author at:
107
+
108
+ * Stephen Eley - <sfeley@gmail.com>
109
+
110
+ [1]: http://apache.org/licenses/LICENSE-2.0
@@ -1,49 +1,105 @@
1
1
  # Candy
2
2
 
3
- __"Mongo like candy!"__ _Blazing Saddles_
3
+ __"Mongo like candy!"__ -- _Blazing Saddles_
4
4
 
5
- Candy aims to be the simplest possible ORM for the MongoDB database. If MongoMapper is Rails, Candy is Sinatra. Mix the Candy module into any class, and every new object for that class will create a Mongo document. Objects act like OpenStructs -- you can assign or retrieve any property without declaring it in code.
5
+ Candy's goal is to provide the simplest possible object persistence for the [MongoDB][1] database. By "simple" we mean "nearly invisible." Candy doesn't try to mirror ActiveRecord or DataMapper. Instead, we play to MongoDB's unusual strengths -- extremely fast writes and a set of field-specific update operators -- and do away with the cumbersome, unnecessary methods of last-generation workflows.
6
6
 
7
- Other distinctive features:
7
+ Methods like `find`.
8
8
 
9
- * Sane defaults are used for the connection, database, and collection. If you're running Mongo locally you can have zero configuration.
10
- * Candy has no `save` or `save!` method. Property changes are persisted to the database immediately. Mongo's atomic update operators (in particular $set) are used so that updates are as fast as possible and don't clobber unrelated fields.
11
- * Candy properties have _no memory._ Every value retrieval goes against the database, so your objects are never stale. (You can always implement caching via Ruby attributes if you want it.)
12
- * Query result sets are Enumerator objects on top of Mongo cursors. If you're using Ruby 1.9 you'll get very efficient enumeration using fibers.
13
- * Whole documents are never written nor read. Queries only return the **_id** field, and getting or setting a property only accesses that property.
14
- * __Coming soon:__ Array and embedded document operations.
15
- * __Coming soon:__ A smart serializer (Candy::Wrapper) to convert almost any object for assignment to a Candy property.
9
+ Or `save`.
16
10
 
17
- Candy was extracted from [Candygram](http://github.com/SFEley/candygram), my delayed job system for MongoDB. I'm presently in the middle of refactoring Candygram to depend on Candy instead, which will simplify a lot of the Mongo internals.
11
+ ## Overview
18
12
 
13
+ When you mix the **Candy::Piece** module into a class, the class gains a Mongo collection as an alter ego. Objects are saved to Mongo the first time you set a property. Any property you set thereafter is sent to Mongo _immediately_ and _atomically._ You don't need to declare the properties; we use `method_missing` to drive the getting and setting of any field you want in any record. Or you can use the hashlike `[]` and `[]=` operators if that's more in your comfort zone.
14
+
15
+ class Person
16
+ include Candy::Piece
17
+ end
18
+
19
+ me = Person.new
20
+ me.last_name = 'Eley' # New record created and saved to Mongo
21
+ me.id # => ObjectID(4bb606f9609c8417cf00004b) or thereabouts
22
+ me[:height] = 67 # Or me.height = 67 -- either way, updates with a Mongo $set
23
+
24
+ ### Embedded Documents
25
+
26
+ We got 'em. Candy pieces can contain each other recursively, to any arbitrary depth. There's no need for complex `has_and_belongs_to_many :through {:your => 'mother'}` type declarations. Just assign an object or a bunch of objects to a field. Hashes and arrays become Candy-aware analogues of themselves (**CandyHash** and **CandyArray**) with live updating and the same recursive embedding. Non-Candy objects are serialized into a flat hash structure that retains their class and instance variables, so they can be rehydrated later.
27
+
28
+ me.favorites = { composer: 'Yoko Kanno',
29
+ seafood: 'Maryland blue crabs',
30
+ scotch: ['Glenmorangie Port Wood Finish',
31
+ 'Balvenie Single Barrel']}
32
+ me.spouse = Person.embed(first_name: 'Anna', eyes: :blue)
33
+ me.spouse.eyes # => :blue
34
+ me.favorites.scotch[1] # => 'Balvenie Single Barrel'
35
+
36
+ ### Retrieval
37
+
38
+ Again, transparency is the key. The same `method_missing` tactic applies to class methods to retrieve individual records:
39
+
40
+ Person.last_name('Smith') # Returns the first Smith
41
+ Person.age(21) # Returns the first legal drinker (in the U.S.)
42
+ Person(12345) # Returns the person with an _id of 12345
43
+
44
+ Take note of that last example. It's moderately deep magic, and we take care not to stomp on any class-like methods you've already defined. But it's the simplest possible way to retrieve a record by ID. `Person.first('_id' => 12345)` works too, of course.
45
+
46
+ ### Collections
47
+
48
+ Some applications don't need to iterate through all records of a query; you might just need the first record from a queue or something. When you do need them, the anonymous "sort of like an array, except when it isn't" encapsulation of collections in other ORMs is clunky and confusing. So enumerable cursors live in their own **Candy::Collection** module, which you explicitly mix into a class and then link back to the **Candy::Piece** class:
49
+
50
+ class People
51
+ include Candy::Collection
52
+ collects :person # Declares the Mongo collection is 'Person'
53
+ end # (and so is the Candy::Piece class)
54
+
55
+ People.last_name('Smith') # Returns an enumeration of all Smiths
56
+ People.age(19).sort(:birthdate, :down).limit(10) # We can chain options
57
+ People(limit: 47, occupation: :ronin) # Or People.all(params) or People.new(params)
58
+ People.each(|p| p.shout = 'Norm!') # Where everybody knows your name...
59
+
60
+ You can also, of course, just do `People.new()` with a bunch of query conditions. You don't need two separate hashes for your fields and your Mongo options; Candy knows which keys are MongoDB query options and will automatically separate them for you. The collection module is really just a thin wrapper around a **Mongo::Cursor** and passes most of its behavior to the cursor -- so you can do `each`, `next`, et cetera.
61
+
62
+ **Q:** _Why can't I just have Person automatically link to People? I want my Raaaaails!_
63
+
64
+ **A:** Because including ActiveSupport as a dependency would be nuts, whereas pasting in my own table of plural inflections would merely double the code base. I'm not against magic, obviously, but that's expensive magic for little benefit. You'll just have to type those three lines of code yourself.
65
+
66
+ ## Prerequisites
67
+
68
+ * **Ruby 1.9.x** The code uses the [new hash syntax][2], UTF-8 encoding, and 1.9ish enumerable methods. No whining. If you're starting a _new_ project in mid-2010 or later and you're still using 1.8, you're hurting us all. And kittens. You don't want to hurt kittens, do you?
69
+
70
+ * **MongoDB 1.4+** You could probably get away with 1.2 for _some_ functionality, but the new array operators and [findAndModify][3] were too useful to pass up. It's a safe and easy upgrade, so if you're not on the latest Mongo yet... Well, you're not hurting kittens, but you're hurting _yourself._
71
+
72
+ * **mongo gem 0.19+** The Ruby gem seems to lag behind actual Mongo development by quite a bit sometimes. 0.19.1 is the latest at the time of this writing, and some commands (e.g. `findAndModify`) have been implemented in Candy because the gem doesn't have methods for them yet. We'll continue to streamline our code as the driver allows.
73
+
19
74
  ## Installation
20
75
 
21
76
  Come on, you've done this before:
22
77
 
23
78
  $ sudo gem install candy
24
79
 
25
- Candygram requires the **mongo** gem, and you'll probably be much happier if you install the **mongo\_ext** gem as well. The author uses only Ruby 1.9, but it _should_ work in Ruby 1.8.7. If it doesn't, please report a bug in Github's issue tracking system. (If you're using 1.8.6, I hosed you by using the Enumerator class. Sorry. I might fix this if enough noise gets made.)
80
+ (Or leave off the _sudo_ if you're smart enough to be using [RVM][4].)
26
81
 
27
82
  ## Configuration
28
83
 
29
84
  The simplest possible thing that works:
30
85
 
31
86
  class Zagnut
32
- include Candy
87
+ include Candy::Piece
33
88
  end
34
89
 
35
90
  That's it. Honest. Some Mongo plumbing is hooked in and instantiated the first time the `.collection` attribute is accessed:
36
91
 
37
92
  Zagnut.connection # => Defaults to localhost port 27017
38
- Zagnut.db # => Defaults to your username
93
+ Zagnut.db # => Defaults to your username, or 'candy' if unknown
39
94
  Zagnut.collection # => Defaults to the class name ('Zagnut')
40
95
 
41
- You can override the DB or collection by providing new name strings or Mongo::DB and Mongo::Collection objects. Or you can set certain global variables to make it easier for multiple Candy classes in an application to use the same database:
96
+ You can override the DB or collection by providing name strings or **Mongo::DB** and **Mongo::Collection** objects. Or you can set certain module-level properties to make it easier for multiple Candy classes in an application to use the same database:
42
97
 
43
- * **$MONGO_HOST**
44
- * **$MONGO_PORT**
45
- * **$MONGO_OPTIONS** (A hash of options to the Connection object)
46
- * **$MONGO_DB** (A simple string with the database name)
98
+ * **Candy.host**
99
+ * **Candy.port**
100
+ * **Candy.connection**
101
+ * **Candy.connection_options** (A hash of options to the Connection object)
102
+ * **Candy.db** (Can provide a string or a database object)
47
103
 
48
104
  All of the above is pretty general-purpose. If you want to use this class-based Mongo functionality in your own projects, simply include `Candy::Crunch` in your own classes.
49
105
 
@@ -52,54 +108,226 @@ All of the above is pretty general-purpose. If you want to use this class-based
52
108
  The trick here is to think of Candy objects like OpenStructs. Or if that's too technical, imagine the objects as thin candy shells around a chewy `method_missing` center:
53
109
 
54
110
  class Zagnut
55
- include Candy
111
+ include Candy::Piece
56
112
  end
57
113
 
58
114
  zag = Zagnut.new # A blank document enters the Zagnut collection
59
115
  zag.taste = "Chewy!" # Properties are created and saved as they're used
60
116
  zag.calories = 600
61
117
 
62
- nut = Zagnut.first(:taste => "Chewy!")
118
+ nut = Zagnut.taste ("Chewy!") # Or Zagnut(taste: 'Chewy!')
63
119
  nut.calories # => 600
64
120
 
65
121
  kingsize = Zagnut.new
66
- kingsize.calories = 900
122
+ kingsize.calories = 900 # Or kingsize[:calories] = 900
123
+ kingsize.ingredients = ['cocoa', 'peanut butter']
124
+ kingsize.ingredients << ['corn syrup']
125
+ kingsize.nutrition = { sodium: '115mg', protein: '3g' }
126
+ kingsize.nutrition.fat = {saturated: '4g', total: '9g'}
127
+ kingsize[:nutrition][:fat][:saturated] # => '4g'
128
+
129
+ class Zagnuts
130
+ include Candy::Collection
131
+ collects Zagnut
132
+ end
133
+
134
+ bars = Zagnuts # Or Zagnuts.all or Zagnuts.new
135
+ bar.count # => 2
136
+ sum = Zagnuts.inject {|sum,bar| sum + bar.calories} # => 1500
67
137
 
68
- bars = Zagnut.all # => An Enumerator object with #each and friends
69
- sum = bars.inject {|sum,bar| sum + bar.calories} # => 1500
138
+ Note that writes are always live, but reads hold onto the retrieved document and cache its values to avoid query delays. You can force a requery at any time with the `refresh` method. (An expiration feature wherein documents are requeried after a set time has elapsed is being considered for the future.)
139
+
140
+ ## Advanced Classes
141
+
142
+ Candy properties are fundamentally just entries in a hash, with some hooks to the MongoDB `$set` updater when something changes. The primary reason we've implemented Candy as modules is so that you keep control of your own classes' behavior and inheritance. To have properties that _don't_ store to the Mongo collection, all you have to do is define them explicitly:
143
+
144
+ class Weight
145
+ include Candy::Piece
146
+
147
+ attr_accessor :gravity # This won't be stored in MongoDB
148
+
149
+ def kilograms
150
+ pounds * 2.2046 # 'pounds' is undeclared, so Candy retrieves it
151
+ end
152
+
153
+ def kilograms=(val)
154
+ self.pounds = val/2.2046 # 'pounds=' is undeclared; Candy stores it
155
+ end
156
+ end
157
+
158
+ Embedded hashes are of type **CandyHash** unless you explicitly assign an object that includes **Candy::Piece**. (CandyHash itself is really just a Candy piece that doesn't store its classname.) If you want truly quick-and-dirty persistence, you can even just use a CandyHash as a standalone object and skip creating your own classes:
159
+
160
+ hash = CandyHash.new(foo: 'bar')
161
+ hash[:yoo] = :yar # Persists to the 'candy' collection by default
162
+ hash.too = [:tar, :car, :far]
70
163
 
164
+ hash2 = CandyHash(hash.id)
165
+ hash2.foo # => 'bar'
166
+ hash2.yoo # => :yar
167
+ hash2[:too][1] # => :car
168
+
169
+ Embedded arrays are of type **CandyArray**. Unlike CandyHashes, CandyArrays do _not_ include **Candy::Piece** and cannot operate as standalone objects. They only make sense when embedded in a Candy piece. That's just the way Mongo works.
71
170
 
72
- If, in the middle of that code execution, somebody else changed the properties of one of the objects, you might get different answers. Every property access requeries the Mongo document. That sounds insane, but Mongo is _fast_ so we can get away with it; and it avoids any brittleness or complexity of having refresh methods or checking for stale data. (Later versions may include more document-based access via block operations. Let me know if you would like to see that.)
171
+ ## Good Practices
73
172
 
74
- ### Method_missing? _Really?_
173
+ _(I'm not going to call them "Best" practices because you might think of better ones than me.)_
75
174
 
76
- Yes. It may seem at first like an inversion: Candy only stores attributes that you _don't_ declare in your class definition. But there's a method to this madn... (No, wait, it's missing.)
175
+ ### Validations
77
176
 
78
- Here's the reason. I have no idea what kind of logic you might want to put in your classes. I don't want to guess what you want to store or not -- and more to the point, I don't want to make _you_ guess. Unless you want to.
177
+ The long-term plan includes support for ActiveModel features as an optional extension. In the meantime, one simple trick is to decorate your properties:
79
178
 
80
- Candy properties are dumb. They don't have calculations. They don't cache. They have nothing to do with instance variables. If you _want_ to make something smarter, just set up your accessors and have them talk to Candy behind the scenes:
179
+ class Person
180
+ include Candy::Piece
181
+
182
+ def email=(val)
183
+ raise "Invalid email address!" if val !~ /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/
184
+ super
185
+ end
186
+ end
81
187
 
82
- class Zagnut
83
- include Candy
188
+ _Exceptions for validation failures?_ Why yes. One consequence of a doctrine of instant persistence is that _not_ persisting connotes something's wrong. I personally prefer rescues to deeply nested 'else' clauses to handle failures anyway. If you disagree, you could implement the above in a kinder, gentler way:
189
+
190
+ def email=(val)
191
+ if val =~ /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/
192
+ super
193
+ else
194
+ (@errors ||= []) << "Invalid email address!"
195
+ false # (Or nil, but that gets confusing if you can actually assign nil.)
196
+ end
197
+ end
198
+
199
+ If that seems like a lot of work compared to `validates_format_of` -- well, you're right. As I said, it's coming. In the meantime, just don't validate frivolously. Leave minor tips and cleanups to the interface layer, and only refuse to save in cases where allowing the value would break something.
200
+
201
+ ### Named Scopes
202
+
203
+ Dirt simple. Use a method in the collection class.
204
+
205
+ class People
206
+ include Candy::Collection
207
+ collects :person
84
208
 
85
- def weight
86
- @weight ||= _weight # _weight is undeclared, so Candy looks it up
209
+ def voters
210
+ age('$gt' => 18).citizen(true)
87
211
  end
212
+ end
213
+
214
+ ### Required Fields
215
+
216
+ Instant document creation makes it problematic to wait to see if a user's going to fill in a field. One tactic is to require those fields in the constructor:
88
217
 
89
- def weight=(val)
90
- self._weight = @weight = val # _weight= is undeclared, so Candy stores it
91
- end
218
+ class Person
219
+ include Candy::Piece
220
+ def initialize(options={})
221
+ raise "Last name is required!" unless options[:last_name]
222
+ raise "Email address is required!" unless options[:email]
223
+ super
224
+ end
92
225
  end
93
-
94
226
 
95
- == Contributing
227
+ p = Person.new(last_name: 'Eley', email: 'sfeley@gmail.com') # This is valid
228
+
229
+ Or you can use the above scoping trick to make sure your application's standard collections operate only on records that are "complete." You could even make it intrinsic to the class itself:
230
+
231
+ class ValidPeople < People
232
+ def initialize(options={})
233
+ options.merge!(last_name: {'$exists' => true}, email: {'$exists' => true})
234
+ super
235
+ end
236
+ end
237
+
238
+
239
+ ## Philosophy
240
+
241
+ Even relative to other ORMs, Candy's pretty opinionated. Here are some of the opinions behind the design.
242
+
243
+ * Applications should be beautiful.
244
+
245
+ * In a beautiful application, most of the code clearly and obviously furthers primary activities. (Business needs, use cases, user stories, the critical path, call it whatever you want.)
246
+
247
+ * Data storage is not a primary activity. It's a supporting activity. It's something you have to do so that the primary activities you do today remain done tomorrow.
248
+
249
+ * An application structure which reflects the constraints of supporting activities more than the achievement of primary activities is not beautiful.
250
+
251
+ * Current Ruby ORMs go a long way towards eliminating the boilerplate cruft found in other languages' frameworks. But they don't go far _enough._
252
+
253
+ * `Save` sucks. The 'write-now-commit-later' pattern of most ORMs creates brittleness and uncertainty. If I change the state of something, _I want it changed._ My framework shouldn't hold its breath waiting for me to say "Simon Says."
254
+
255
+ * Frameworks should be beautiful.
256
+
257
+ * A beautiful framework is one whose fundamentals can be read and understood by a journeyman developer with appropriate background knowledge in one sitting.
258
+
259
+ * Frameworks that are too large or complex to be beautiful can sometimes be broken down into smaller frameworks that _are_ beautiful.
260
+
261
+ * Beautiful frameworks should be transparent and non-constraining. A _truly_ beautiful framework is one you have to squint to see.
96
262
 
97
- At this early stage, one of the best things you could do is just to tell me that you have an interest in using this thing. You can email me at sfeley@gmail.com -- if I get more than, say, three votes of interest, I'll throw a projects page on the wiki.
263
+ * _Magic_ (defined as "behavior whose workings are not immediately apparent") is fine in a framework. In a sense it's what frameworks _are._ But magical behavior should be restrained, consistent, clearly documented, and must not violate the Principle of Least Surprise.
264
+
265
+ * Thomas Jefferson wrote that software frameworks should be subject to revolution every couple of years. ("The tree of agility must be refreshed from time to time with the blood of senior architects and project managers. It is its natural manure.") I will be disappointed if Candy is not roundly decried for being too complex, bloated, and intrusive by 2013 at the latest.
266
+
267
+ * There is a finite amount of seriousness allowed in any open source project. A project that takes itself too seriously is using up its reserves, and is less likely to be taken seriously by its user base.
268
+
269
+ * A project that appeals to playfulness is more likely to be explored with vigor, and if it withstands the exploration, creates _passion._
270
+
271
+ * A README with this many bullet points in a single list should probably stop.
272
+
273
+ ## Caveats, Limitations, and To-Dos
274
+
275
+ This is very, very alpha software. I'm using it in some non-trivial projects right now, but it's far from bulletproof, and a lot of things aren't implemented yet. In particular:
276
+
277
+ * The API is still in flux and subject to overhauling, undermining, or carpetbombing at any time. Candy v0.2, for instance, has barely a wisp of resemblance to Candy v0.1. (My apologies to the 155 of you who downloaded 0.1.)
278
+
279
+ * CandyHashes and CandyArrays don't yet implement the full set of methods you'd expect from hashes and arrays. I mean to flesh them out to make them more compatible. (You can help by creating issues to tell me what methods you need most.)
280
+
281
+ * Collections are not terribly robust nor well-tested yet. They 'work' in the sense that they pass a bunch of things to **Mongo::Cursor**, but I personally consider the cursor functionality to be a bit wonky. I'd like to make enumerations more repeatable and have the cursors more certain to be released after garbage collection.
282
+
283
+ * Currently every property assignment is a separate write to the database (mostly using **$set**.) This is fine, but for cases where a lot of properties are set at once we will eventually have transaction-like behavior using blocks.
284
+
285
+ * Many Mongo update operators, such as **$pushAll** and **$pop** and **$addToSet**, are not implemented yet or are not fully leveraged. (Saving a full document isn't implemented either, but that's a deliberate feature.)
286
+
287
+ * For high-concurrency use cases or for huge documents, more control of the document caching is called for. I'd like to have an option to declare only certain fields to be retrieved by default, and have the internal cache expire after a set time or clear itself on every read.
288
+
289
+ * 'Safe mode' is never used. Making it an option for classes or specific updates would be...well...safer.
290
+
291
+ * There's no support yet for deleting records, apart from driver calls on the class's collection. Somebody might want to someday.
292
+
293
+ * Index creation is currently left as an exercise to be performed out-of-band. I do believe a proper persistence framework, even a transparent one, should have some facility for it.
294
+
295
+ * Likewise, there's no way yet to set interesting collection options (capped collections, etc.) except to make the **Mongo::Collection** object separately and hand it to the class.
296
+
297
+ * For that matter, capped collections haven't been tested at all and might operate weirdly if properties are continually being set on them.
298
+
299
+ * I have only begun optimizing for code beauty, and have not optimized at all yet for performance. Mongo is fast. I make no guarantees that my _code_ is fast at this time.
300
+
301
+ * I haven't tested it at all in Windows. Witness my regret. (Wait, there isn't any.)
302
+
303
+ * This library isn't thread-safe yet. (Which is to say: I haven't tried to confirm one way or the other, but I'd be shocked if it was.)
304
+
305
+ * There's no support yet for ActiveModel or similar validations, et cetera. It's on my list to create an extension system, with Rails 3 and ActiveModel support being the first use case. Right now this is more of a Sinatra sort of data thingy than a Rails data thingy.
306
+
307
+ ## Resources
308
+
309
+ We have the usual array of stuff for your learning pleasure...
310
+
311
+ * **Home page:** <http://github.com/SFEley/candy>
312
+ * **Documentation:** <http://rdoc.info/projects/SFEley/candy>
313
+ * **Report issues:** <http://github.com/SFEley/candy/issues>
314
+ * **Discussion list:** <http://groups.google.com/groups/candy-users>
315
+
316
+ ## Contributing
317
+
318
+ At this early stage, one of the best things you could do is just to tell me that you have an interest in using this thing. Join the [discussion list][5] and let us know what you think.
98
319
 
99
320
  Beyond that, report issues, please. If you want to fork it and add features, fabulous. Send me a pull request.
100
321
 
101
322
  Oh, and if you like science fiction stories, check out my podcast [Escape Pod](http://escapepod.org). End of plug.
102
323
 
103
- == Copyright
324
+ ## License
325
+
326
+ This project is licensed under the [Don't Be a Dick License][6], version 0.1, and is copyright 2010 by Stephen Eley. See the [LICENSE.markdown][6] file for elaboration on not being a dick. (But you probably already know.)
104
327
 
105
- Copyright (c) 2010 Stephen Eley. See LICENSE for details.
328
+ [1]: http://mongodb.org
329
+ [2]: http://snippets.dzone.com/posts/show/7891
330
+ [3]: http://www.mongodb.org/display/DOCS/findandmodify+Command
331
+ [4]: http://rvm.beginrescueend.com/
332
+ [5]: http://groups.google.com/groups/candy-users
333
+ [6]: http://github.com/SFEley/candy/blob/master/LICENSE.markdown