candy 0.0.1 → 0.0.2

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