public
Description: Ruby interface to CouchDB
Homepage: http://code.google.com/p/activecouch/
Clone URL: git://github.com/JackDanger/active_couch.git
Search Repo:
- marshal_load and marshal_dump methods added. (Now works with Memcache)
- Misc. bug fixes (integer to string conversion bugs)

git-svn-id: http://activecouch.googlecode.com/svn/trunk@71 
e44de9e2-1e40-0410-bb6c-c9d70e891a3e
arun.thampi (author)
Sat Feb 02 22:44:18 -0800 2008
commit  8b4e9c074f31df170a41605520f5e218be9e43a8
tree    fbac9eca0b8f2932e304095e2dfaae19590ecd63
parent  36a0f6f74f5c591b1695c83104de4ef424cc89f5
...
1
 
...
 
1
0
@@ -1,2 +1,2 @@
0
-0.1.0
0
+0.1.1
...
12
13
14
15
16
17
18
...
12
13
14
 
 
 
 
0
@@ -12,8 +12,4 @@
0
 require 'active_couch/connection'
0
 require 'active_couch/migrations'
0
 require 'active_couch/callbacks'
0
-
0
-#ActiveCouch::Base.class_eval do
0
-# include ActiveCouch::Callbacks
0
-#end
...
12
13
14
15
 
16
17
18
...
12
13
14
 
15
16
17
18
0
@@ -12,7 +12,7 @@
0
         # Use the inflector to get the correct class if it is not defined
0
         # in the :class key in the options hash
0
         # so has_many :contacts (will try to find the class Contact and set it to @klass)
0
- @klass = Inflector.constantize(Inflector.classify(@name))
0
+ @klass = @name.classify.constantize #Inflector.constantize(Inflector.classify(@name))
0
       end
0
     end
0
 
...
3
4
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
7
8
...
24
25
26
27
 
28
29
30
...
38
39
40
 
 
 
 
 
41
42
43
...
137
138
139
 
 
 
 
 
 
 
 
 
 
 
 
140
141
142
143
...
164
165
166
167
 
168
169
170
 
171
172
173
...
289
290
291
292
 
293
294
295
296
...
436
437
438
 
439
440
 
441
442
443
...
458
459
460
461
462
463
464
...
476
477
478
479
 
480
481
482
...
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
39
40
41
 
42
43
44
45
...
53
54
55
56
57
58
59
60
61
62
63
...
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
...
196
197
198
 
199
200
201
 
202
203
204
205
...
321
322
323
 
324
325
326
327
328
...
468
469
470
471
472
 
473
474
475
476
...
491
492
493
 
494
495
496
...
508
509
510
 
511
512
513
514
0
@@ -3,6 +3,21 @@
0
     SPECIAL_MEMBERS = %w(attributes associations connection callbacks)
0
     DEFAULT_ATTRIBUTES = %w(id rev)
0
     
0
+ # Initializes an ActiveCouch::Base object. The constructor accepts both a hash, as well as
0
+ # a block to initialize attributes
0
+ #
0
+ # Examples:
0
+ # class Person < ActiveCouch::Base
0
+ # has :name
0
+ # end
0
+ #
0
+ # person1 = Person.new(:name => "McLovin")
0
+ # person1.name # => "McLovin"
0
+ #
0
+ # person2 = Person.new do |p|
0
+ # p.name = "Seth"
0
+ # end
0
+ # person2.name # => "Seth"
0
     def initialize(params = {})
0
       # Object instance variable
0
       @attributes = {}; @associations = {}; @callbacks = Hash.new; @connection = self.class.connection
0
@@ -24,7 +39,7 @@
0
         self.instance_eval "def #{k}; associations[:#{k}].container; end"
0
         # If you have has_many :people, this will add a method called add_person to the object instantiated
0
         # from the class
0
- self.instance_eval "def add_#{Inflector.singularize(k)}(val); associations[:#{k}].push(val); end"
0
+ self.instance_eval "def add_#{k.singularize}(val); associations[:#{k}].push(val); end"
0
       end
0
       
0
       klass_callbacks.each_key do |k|
0
@@ -38,6 +53,11 @@
0
       
0
       # Set any instance variables if any, which are present in the params hash
0
       from_hash(params)
0
+ # Now you can do stuff like
0
+ # Person.new do |p|
0
+ # p.name = 'McLovin'
0
+ # end
0
+ yield self if block_given?
0
     end
0
 
0
     # Generates a JSON representation of an instance of a subclass of ActiveCouch::Base.
0
@@ -137,6 +157,18 @@
0
       end
0
     end
0
 
0
+ def marshal_dump # :nodoc:
0
+ self.to_json
0
+ end
0
+
0
+ def marshal_load(str) # :nodoc:
0
+ self.instance_eval do
0
+ hash = JSON.parse(str)
0
+ initialize(hash)
0
+ end
0
+ self
0
+ end
0
+
0
     class << self # Class methods
0
 
0
       # Returns the CouchDB database name that's backing this model. The database name is guessed from the name of the
0
0
@@ -164,10 +196,10 @@
0
         else
0
           # Nested classes are prefixed with singular parent database name.
0
           if parent < ActiveCouch::Base
0
- contained = Inflector.singularize(parent.database_name)
0
+ contained = parent.database_name.singularize
0
             contained << '_'
0
           end
0
- "#{contained}#{Inflector.underscore(Inflector.demodulize(Inflector.pluralize(base.name)))}"
0
+ "#{contained}#{base.name.pluralize.demodulize.underscore}"
0
         end)
0
         set_database_name(name)
0
         name
0
@@ -289,7 +321,7 @@
0
         case scope
0
           when :all then find_every(options)
0
           when :first then find_every(options).first
0
- else find_one(scope) #raise ArgumentError("find must have the first parameter as either :all or :first")
0
+ else find_one(scope))
0
         end
0
       end
0
 
0
0
@@ -436,8 +468,9 @@
0
         # So for example, if the params hash is :name => 'McLovin',
0
         # the view associated with it will be /by_name/by_name?key="McLovin"
0
         def query_string(params)
0
+ # TODO : Shouldn't check for multiple arguments
0
           if params.is_a?(Hash)
0
- params.each { |k,v| return "by_#{k}/by_#{k}?key=#{v..url_encode}" }
0
+ params.each { |k,v| return "by_#{k}/by_#{k}?key=#{v.to_s.url_encode}" }
0
           else
0
             raise ArgumentError, "The value for the key 'params' must be a Hash"
0
           end
0
@@ -458,7 +491,6 @@
0
         # Instantiates an ActiveCouch::Base object, based on the result obtained from
0
         # the GET URL
0
         def instantiate_object(result)
0
- puts result
0
           hash = JSON.parse(result)
0
           self.new(hash)
0
         end
0
@@ -476,7 +508,7 @@
0
               name, child_klass = assoc.name, assoc.klass
0
               v.each do |child|
0
                 child.is_a?(Hash) ? child_obj = child_klass.new(child) : child_obj = child
0
- self.send "add_#{Inflector.singularize(name)}", child_obj
0
+ self.send "add_#{name.singularize}", child_obj
0
               end
0
             end
0
           elsif v.is_a?(Hash) # This means this is a has_one association (which we might add later)
...
4
5
6
7
 
8
9
10
11
 
 
12
13
14
...
48
49
50
 
 
 
 
 
 
 
 
51
52
53
...
4
5
6
 
7
8
9
 
 
10
11
12
13
14
...
48
49
50
51
52
53
54
55
56
57
58
59
60
61
0
@@ -4,11 +4,11 @@
0
     
0
     def self.included(base)
0
       # Alias methods which will have callbacks, (for now only save and delete).
0
- # This creates 2 sets of methods: save_with_callbacks, save_without_callbacks,
0
+ # This creates 2 pairs of methods: save_with_callbacks, save_without_callbacks,
0
       # delete_with_callbacks, delete_without_callbacks
0
       #
0
- # save_without_callbacks and delete_without_callbacks have the same behaviour
0
- # as the save and delete methods, respectively
0
+ # save_without_callbacks and delete_without_callbacks
0
+ # have the same behaviour as the save and delete methods, respectively
0
       [:save, :delete].each do |method|
0
         base.send :alias_method_chain, method, :callbacks
0
       end
0
@@ -48,6 +48,14 @@
0
       result
0
     end
0
     private :delete_with_callbacks
0
+
0
+ def find_with_callbacks
0
+ return false if callback(:before_find) == false
0
+ result = find_without_callbacks
0
+ callback(:after_find)
0
+ result
0
+ end
0
+ private :find_with_callbacks
0
     
0
     private
0
       def callback(method)
...
56
57
58
59
 
60
61
62
...
56
57
58
 
59
60
61
62
0
@@ -56,7 +56,7 @@
0
       
0
       def get_view(view)
0
         view_name = view
0
- view_name = Inflector.underscore("#{self}") if view.nil? || view.length == 0
0
+ view_name = "#{self}".underscore if view.nil? || view.length == 0
0
         view_name
0
       end
0
 
...
1
2
 
 
 
 
3
4
5
6
7
 
 
 
 
 
 
 
 
8
9
10
...
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
...
1
2
3
4
5
6
7
8
 
 
 
9
10
11
12
13
14
15
16
17
18
19
...
56
57
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
60
61
0
@@ -1,10 +1,19 @@
0
 module ActiveCouch
0
 
0
+ Symbol.class_eval do
0
+ def singularize; Inflector.singularize(self); end
0
+ end
0
+
0
   String.class_eval do
0
     require 'cgi'
0
- def url_encode
0
- CGI.escape("\"#{self.to_s}\"")
0
- end
0
+ def url_encode; CGI.escape("\"#{self.to_s}\""); end
0
+ # Delegate to Inflector
0
+ def singularize; Inflector.singularize(self); end
0
+ def demodulize; Inflector.demodulize(self); end
0
+ def pluralize; Inflector.pluralize(self); end
0
+ def underscore; Inflector.underscore(self); end
0
+ def classify; Inflector.classify(self); end
0
+ def constantize; Inflector.constantize(self); end
0
   end
0
   
0
   Hash.class_eval do
0
@@ -47,27 +56,6 @@
0
       parent_name.empty? ? Object : Inflector.constantize(parent_name)
0
     end
0
     
0
- # Encapsulates the common pattern of:
0
- #
0
- # alias_method :foo_without_feature, :foo
0
- # alias_method :foo, :foo_with_feature
0
- #
0
- # With this, you simply do:
0
- #
0
- # alias_method_chain :foo, :feature
0
- #
0
- # And both aliases are set up for you.
0
- #
0
- # Query and bang methods (foo?, foo!) keep the same punctuation:
0
- #
0
- # alias_method_chain :foo?, :feature
0
- #
0
- # is equivalent to
0
- #
0
- # alias_method :foo_without_feature?, :foo?
0
- # alias_method :foo?, :foo_with_feature?
0
- #
0
- # so you can safely chain foo, foo?, and foo! with the same feature.
0
     def alias_method_chain(target, feature)
0
       # Strip out punctuation on predicates or bang methods since
0
       # e.g. target?_without_feature is not a valid method name.
...
1
2
3
4
5
6
7
8
...
1
2
 
 
 
3
4
5
0
@@ -1,8 +1,5 @@
0
 # This file is copied from the ActiveSupport project, which
0
 # is a part of the Ruby On Rails web-framework (http://rubyonrails.org).
0
-
0
-# TODO Consider a String extension that delegates to Inflector.
0
-
0
 require 'singleton'
0
 
0
 # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
...
236
237
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
0
@@ -236,4 +236,36 @@
0
     person.should == nil
0
   end
0
 end
0
+
0
+describe "ActiveCouch::Base #find method with non-String params passed as arguments" do
0
+ before(:each) do
0
+ class Person < ActiveCouch::Base
0
+ site 'http://localhost:5984/'
0
+ has :age
0
+ end
0
+ # Define the migration
0
+ class ByAge < ActiveCouch::Migration
0
+ define :for_db => 'people' do
0
+ with_key 'age'
0
+ end
0
+ end
0
+ # Create the database first
0
+ ActiveCouch::Migrator.create_database('http://localhost:5984', 'people')
0
+ # Create a view
0
+ ActiveCouch::Migrator.migrate('http://localhost:5984', ByAge)
0
+ # Save two objects
0
+ Person.create(:age => "21")
0
+ end
0
+
0
+ after(:each) do
0
+ # Delete the database last
0
+ ActiveCouch::Migrator.delete_database('http://localhost:5984', 'people')
0
+ Object.send(:remove_const, :Person)
0
+ end
0
+
0
+ it "should return an ActiveCouch::Base object" do
0
+ person = Person.find(:first, :params => {:age => 21})
0
+ person.age.should == "21"
0
+ end
0
+end
...
47
48
49
50
 
51
52
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
55
...
47
48
49
 
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
0
@@ -47,10 +47,46 @@
0
     Object.send(:remove_const, :Person)
0
   end
0
   
0
- it "should be able to initialize attributes correclty from the has, including CouchDB reserved attributes" do
0
+ it "should be able to initialize attributes correctly, including CouchDB reserved attributes" do
0
     p = Person.new(:name => 'McLovin', :id => '123')
0
     p.name.should == 'McLovin'
0
     p.id.should == '123'
0
+ end
0
+end
0
+
0
+describe "ActiveCouch::Base #new method with a block (and self being passed to the block)" do
0
+ before(:all) do
0
+ class Dog < ActiveCouch::Base
0
+ has :name
0
+ has :age, :which_is => :number
0
+ has :collar
0
+ end
0
+ end
0
+
0
+ after(:all) do
0
+ Object.send(:remove_const, :Dog)
0
+ end
0
+
0
+ it "should be able to initialize all the attributes correctly" do
0
+ dog = Dog.new do |d|
0
+ d.name = "Buster"
0
+ d.age = 2
0
+ d.collar = "Stray"
0
+ end
0
+
0
+ dog.name.should == "Buster"
0
+ dog.age.should == 2
0
+ dog.collar.should == "Stray"
0
+ end
0
+
0
+ it "should be able to initialize all attributes (including CouchDB reserved attributes)" do
0
+ dog = Dog.new do |d|
0
+ d.name = "Spike"
0
+ d.id = 'underdog_1'
0
+ end
0
+
0
+ dog.name.should == "Spike"
0
+ dog.id.should == 'underdog_1'
0
   end
0
 end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
0
@@ -1 +1,74 @@
0
+require File.dirname(__FILE__) + '/../spec_helper.rb'
0
+
0
+describe "ActiveCouch::Base #marshal_dump method with just simple attributes" do
0
+ before(:each) do
0
+ class Hotel < ActiveCouch::Base
0
+ has :name, :which_is => :text, :with_default_value => "Swissotel The Stamford"
0
+ has :star_rating, :which_is => :decimal, :with_default_value => 5.0
0
+ has :rooms, :which_is => :number, :with_default_value => 100
0
+ end
0
+
0
+ @h = Hotel.new
0
+ end
0
+
0
+ after(:each) do
0
+ Object.send(:remove_const, :Hotel)
0
+ end
0
+
0
+ it "should have to the marshal_dump method" do
0
+ @h.should respond_to(:marshal_dump)
0
+ end
0
+
0
+ it "should produce valid JSON output when sent the marshal_dump method" do
0
+ marshal_dump = @h.marshal_dump
0
+ # Check for JSON regex, since attributes can appear in any order
0
+ (marshal_dump =~ /"name":"Swissotel The Stamford"/).should_not == nil
0
+ (marshal_dump =~ /"rooms":100/).should_not == nil
0
+ (marshal_dump =~ /"star_rating":5.0/).should_not == nil
0
+ end
0
+
0
+ it "should produce valid JSON output when an attribute has been changed and the marshal_dump method is sent" do
0
+ @h.rooms = 200
0
+ marshal_dump = @h.marshal_dump
0
+ # Check for JSON regex, since attributes can appear in any order
0
+ (marshal_dump =~ /"name":"Swissotel The Stamford"/).should_not == nil
0
+ (marshal_dump =~ /"rooms":200/).should_not == nil
0
+ (marshal_dump =~ /"star_rating":5.0/).should_not == nil
0
+ end
0
+end
0
+
0
+describe "ActiveCouch::Base #marshal_dump with associations" do
0
+ before(:each) do
0
+ class Hospital < ActiveCouch::Base
0
+ has :name
0
+ end
0
+
0
+ class CrazyPerson < ActiveCouch::Base
0
+ has :name, :which_is => :text, :with_default_value => "Crazed McLovin"
0
+ has_many :hospitals
0
+ end
0
+
0
+ @c = CrazyPerson.new
0
+
0
+ @h1 = Hospital.new(:name => "Crazy Hospital 1")
0
+ @h2 = Hospital.new(:name => "Crazy Hospital 2")
0
+
0
+ @c.add_hospital(@h1)
0
+ @c.add_hospital(@h2)
0
+ end
0
+
0
+ after(:each) do
0
+ Object.send(:remove_const, :Hospital)
0
+ Object.send(:remove_const, :CrazyPerson)
0
+ end
0
+
0
+ it "should produce valid JSON when sent the marshal_dump method" do
0
+ marshal_dump = @c.marshal_dump
0
+ # Check for JSON regex, since attributes can appear in any order
0
+ (marshal_dump =~ /"name":"Crazed McLovin"/).should_not == nil
0
+ (marshal_dump =~ /"hospitals":\[.*?\]/).should_not == nil
0
+ (marshal_dump =~ /\{.*?"name":"Crazy Hospital 1".*?\}/).should_not == nil
0
+ (marshal_dump =~ /\{.*?"name":"Crazy Hospital 2".*?\}/).should_not == nil
0
+ end
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
0
@@ -1 +1,57 @@
0
+require File.dirname(__FILE__) + '/../spec_helper.rb'
0
+
0
+describe "ActiveCouch::Base #marshal_load method, with many attributes" do
0
+ before(:all) do
0
+ class Hotel < ActiveCouch::Base
0
+ has :name, :which_is => :text, :with_default_value => "Swissotel The Stamford"
0
+ has :star_rating, :which_is => :decimal, :with_default_value => 5.0
0
+ has :rooms, :which_is => :number, :with_default_value => 100
0
+ end
0
+
0
+ class Hospital < ActiveCouch::Base
0
+ has :name
0
+ end
0
+
0
+ class CrazyPerson < ActiveCouch::Base
0
+ has :name, :which_is => :text, :with_default_value => "Crazed McLovin"
0
+ has_many :hospitals
0
+ end
0
+ end
0
+
0
+ after(:all) do
0
+ Object.send(:remove_const, :Hotel)
0
+ Object.send(:remove_const, :Hospital)
0
+ Object.send(:remove_const, :CrazyPerson)
0
+ end
0
+
0
+ it "should have the marshal_load method" do
0
+ Hotel.new.should respond_to(:marshal_load)
0
+ Hospital.new.should respond_to(:marshal_load)
0
+ CrazyPerson.new.should respond_to(:marshal_load)
0
+ end
0
+
0
+ it "should instantiate an object when sent the marshal_load method with valid json as a parameter" do
0
+ h = Hotel.new
0
+ h = h.marshal_load("{\"name\":\"Swissotel The Stamford\",\"rooms\":200,\"star_rating\":4.0}")
0
+ h.class.should == Hotel
0
+ # Check whether all attributes are set correctly
0
+ h.name.should == "Swissotel The Stamford"
0
+ h.rooms.should == 200
0
+ h.star_rating.should == 4.0
0
+ end
0
+
0
+ it "should instantiate an object when sent the marshal_load method with valid JSON (containing associations) as a parameter" do
0
+ crazy = CrazyPerson.new.marshal_load('{"name":"Crazed McLovin","hospitals":[{"name":"Crazy Hospital 1"},{"name":"Crazy Hospital 2"}]}')
0
+ crazy.class.should == CrazyPerson
0
+
0
+ crazy.name == "Crazed McLovin"
0
+ crazy.hospitals.size.should == 2
0
+
0
+ hospitals = crazy.hospitals.collect{|h| h.name }
0
+ hospitals.sort!
0
+
0
+ hospitals.first.should == 'Crazy Hospital 1'
0
+ hospitals.last.should == 'Crazy Hospital 2'
0
+ end
0
+end

Comments

    No one has commented yet.