candy 0.0.1 → 0.0.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.
Files changed (5) hide show
  1. data/VERSION +1 -1
  2. data/candy.gemspec +75 -0
  3. data/lib/candy.rb +71 -2
  4. data/spec/candy_spec.rb +101 -0
  5. metadata +2 -1
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -0,0 +1,75 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{candy}
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Stephen Eley"]
12
+ s.date = %q{2010-01-20}
13
+ s.description = %q{Candy is a lightweight ORM for the MongoDB database. If MongoMapper is Rails, Candy is Sinatra.
14
+ It provides a module you mix into any class, enabling the class to connect to Mongo on its own
15
+ and push its objects into a collection. Candied objects act like OpenStructs, allowing attributes
16
+ to be defined and updated in Mongo immediately without having to be declared in the class.
17
+ Mongo's atomic operators are used whenever possible, and a smart serializer (Candy::Wrapper)
18
+ converts almost any object for assignment to any attribute.
19
+ }
20
+ s.email = %q{sfeley@gmail.com}
21
+ s.extra_rdoc_files = [
22
+ "LICENSE",
23
+ "README.rdoc"
24
+ ]
25
+ s.files = [
26
+ ".document",
27
+ ".gitignore",
28
+ "LICENSE",
29
+ "README.rdoc",
30
+ "Rakefile",
31
+ "VERSION",
32
+ "candy.gemspec",
33
+ "lib/candy.rb",
34
+ "lib/candy/crunch.rb",
35
+ "lib/candy/exceptions.rb",
36
+ "spec/candy/crunch_spec.rb",
37
+ "spec/candy_spec.rb",
38
+ "spec/spec.opts",
39
+ "spec/spec.watchr",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/SFEley/candy}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.5}
46
+ s.summary = %q{The simplest MongoDB ORM}
47
+ s.test_files = [
48
+ "spec/candy/crunch_spec.rb",
49
+ "spec/candy_spec.rb",
50
+ "spec/spec_helper.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ s.add_runtime_dependency(%q<mongo>, [">= 0.18"])
59
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
60
+ s.add_development_dependency(%q<yard>, [">= 0"])
61
+ s.add_development_dependency(%q<mocha>, [">= 0.9.8"])
62
+ else
63
+ s.add_dependency(%q<mongo>, [">= 0.18"])
64
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
65
+ s.add_dependency(%q<yard>, [">= 0"])
66
+ s.add_dependency(%q<mocha>, [">= 0.9.8"])
67
+ end
68
+ else
69
+ s.add_dependency(%q<mongo>, [">= 0.18"])
70
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
71
+ s.add_dependency(%q<yard>, [">= 0"])
72
+ s.add_dependency(%q<mocha>, [">= 0.9.8"])
73
+ end
74
+ end
75
+
@@ -3,18 +3,87 @@ require 'candy/crunch'
3
3
 
4
4
  # Mix me into your classes and Mongo will like them!
5
5
  module Candy
6
+
6
7
  module ClassMethods
8
+ include Crunch::ClassMethods
9
+
10
+ # Retrieves an object from Mongo by its ID and returns it. Returns nil if the ID isn't found in Mongo.
11
+ def find(id)
12
+ if collection.find_one(id)
13
+ self.new({:_candy => id})
14
+ end
15
+ end
16
+
17
+ # Retrieves a single object from Mongo by its search attributes, or nil if it can't be found.
18
+ def first(conditions={})
19
+ options = extract_options(conditions)
20
+ if record = collection.find_one(conditions, options)
21
+ find(record["_id"])
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ # Retrieves all objects matching the search attributes as an Enumerator (even if it's an empty one).
28
+ # The option fields from Mongo::Collection#find can be passed as well, and will be extracted from the
29
+ # condition set if they're found.
30
+ def all(conditions={})
31
+ options = extract_options(conditions)
32
+ cursor = collection.find(conditions, options)
33
+ Enumerator.new do |yielder|
34
+ while record = cursor.next_document do
35
+ yielder.yield find(record["_id"])
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+ # Returns a hash of options matching those enabled in Mongo::Collection#find, if any of them exist
42
+ # in the set of search conditions.
43
+ def extract_options(conditions)
44
+ options = {:fields => []}
45
+ [:fields, :skip, :limit, :sort, :hint, :snapshot, :timeout].each do |option|
46
+ options[option] = conditions.delete(option) if conditions[option]
47
+ end
48
+ options
49
+ end
7
50
 
8
51
  end
9
52
 
10
53
  module InstanceMethods
54
+ include Crunch::InstanceMethods
55
+
56
+ # We push ourselves into the DB before going on with our day.
11
57
  def initialize(*args, &block)
12
- @__candy = self.class.collection.insert({})
58
+ @__candy = check_for_candy(args) || self.class.collection.insert({})
13
59
  super
14
60
  end
15
61
 
62
+ # Shortcut to the document ID.
16
63
  def id
17
- @id
64
+ @__candy
65
+ end
66
+
67
+ # Candy's magic ingredient. Assigning to any unknown attribute will push that value into the Mongo collection.
68
+ # Retrieving any unknown attribute will return that value from this record in the Mongo collection.
69
+ def method_missing(name, *args, &block)
70
+ if name =~ /(.*)=$/ # We're assigning
71
+ self.class.collection.update({"_id" => @__candy}, {"$set" => {$1 => args[0]}})
72
+ else
73
+ self.class.collection.find_one(@__candy, :fields => [name.to_s])[name.to_s]
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Returns the secret decoder ring buried in the arguments to "new"
80
+ def check_for_candy(args)
81
+ if (candidate = args.pop).is_a?(Hash) and candidate[:_candy]
82
+ candidate[:_candy]
83
+ else # This must not be for us, so put it back
84
+ args.push candidate if candidate
85
+ nil
86
+ end
18
87
  end
19
88
 
20
89
  end
@@ -6,12 +6,113 @@ describe "Candy" do
6
6
  include Candy
7
7
  end
8
8
 
9
+ before(:all) do
10
+ @verifier = Zagnut.collection
11
+ end
12
+
9
13
  before(:each) do
10
14
  @this = Zagnut.new
11
15
  end
12
16
 
17
+
13
18
  it "inserts a document immediately" do
14
19
  @this.id.should be_a(Mongo::ObjectID)
15
20
  end
21
+
22
+ it "saves any attribute it doesn't already handle to the database" do
23
+ @this.bite = "Tasty!"
24
+ @verifier.find_one["bite"].should == "Tasty!"
25
+ end
26
+
27
+ it "retrieves any attribute it doesn't already know about from the database" do
28
+ @verifier.update({:_id => @this.id}, {:chew => "Yummy!", :bite => "Ouch."})
29
+ @this.chew.should == "Yummy!"
30
+ end
31
+
32
+ it "can roundtrip effectively" do
33
+ @this.swallow = "Gulp."
34
+ @this.swallow.should == "Gulp."
35
+ end
36
+
37
+ it "handles missing attributes gracefully" do
38
+ @this.licks.should == nil
39
+ end
40
+
41
+ it "allows multiple attributes to be set" do
42
+ @this.licks = 7
43
+ @this.center = 0.5
44
+ @this.licks.should == 7
45
+ end
46
+
47
+ describe "retrieval" do
48
+ it "can find a record by its ID" do
49
+ @this.licks = 10
50
+ that = Zagnut.find(@this.id)
51
+ that.licks.should == 10
52
+ end
53
+
54
+ it "roundtrips across identical objects" do
55
+ that = Zagnut.find(@this.id)
56
+ @this.calories = 7500
57
+ that.calories.should == 7500
58
+ end
59
+
60
+ it "returns nil on an object that can't be found" do
61
+ id = Mongo::ObjectID.new
62
+ Zagnut.find(id).should be_nil
63
+ end
64
+
65
+ it "can get a single object by attributes" do
66
+ @this.pieces = 7.5
67
+ @this.color = "red"
68
+ that = Zagnut.first("pieces" => 7.5)
69
+ that.color.should == "red"
70
+ end
71
+
72
+ it "returns nil if a first object can't be found" do
73
+ @this.pieces = 11
74
+ Zagnut.first("pieces" => 5).should be_nil
75
+ end
76
+ end
77
+
78
+ describe "collections" do
79
+ before(:each) do
80
+ @this.color = "red"
81
+ @this.weight = 11.8
82
+ @that = Zagnut.new
83
+ @that.color = "red"
84
+ @that.pieces = 6
85
+ @that.weight = -5
86
+ @the_other = Zagnut.new
87
+ @the_other.color = "blue"
88
+ @the_other.pieces = 7
89
+ @the_other.weight = 0
90
+ end
91
+
92
+ it "can get all objects in a collection" do
93
+ those = Zagnut.all
94
+ those.count.should == 3
95
+ end
96
+
97
+ it "can get all objects matching a search condition" do
98
+ those = Zagnut.all(:color => "red")
99
+ those.count.should == 2
100
+ end
101
+
102
+ it "still returns if nothing matches" do
103
+ Zagnut.all(:color => "green").to_a.should == []
104
+ end
105
+
106
+ it "can take options" do
107
+ those = Zagnut.all(:color => "red", :sort => ["weight", :asc])
108
+ those.collect{|z| z.weight}.should == [-5, 11.8]
109
+ end
110
+
111
+ end
112
+
113
+
114
+ after(:each) do
115
+ Zagnut.collection.remove
116
+ end
16
117
 
17
118
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: candy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Eley
@@ -75,6 +75,7 @@ files:
75
75
  - README.rdoc
76
76
  - Rakefile
77
77
  - VERSION
78
+ - candy.gemspec
78
79
  - lib/candy.rb
79
80
  - lib/candy/crunch.rb
80
81
  - lib/candy/exceptions.rb