Added:
trunk/spec/base/marshal_dump_spec.rb
trunk/spec/base/marshal_load_spec.rb
Modified:
trunk/VERSION
trunk/lib/active_couch.rb
trunk/lib/active_couch/associations/has_many_association.rb
trunk/lib/active_couch/base.rb
trunk/lib/active_couch/callbacks.rb
trunk/lib/active_couch/migrations/migration.rb
trunk/lib/active_couch/support/extensions.rb
trunk/lib/active_couch/support/inflector.rb
trunk/spec/base/find_spec.rb
trunk/spec/base/initialize_spec.rb
Log:
- marshal_load and marshal_dump methods added. (Now works with Memcache)
- Misc. bug fixes (integer to string conversion bugs)
Modified: trunk/VERSION
==============================================================================
--- trunk/VERSION (original)
+++ trunk/VERSION Sat Feb 2 22:44:18 2008
@@ -1 +1 @@
-0.1.0
\ No newline at end of file
+0.1.1
\ No newline at end of file
Modified: trunk/lib/active_couch.rb
==============================================================================
--- trunk/lib/active_couch.rb (original)
+++ trunk/lib/active_couch.rb Sat Feb 2 22:44:18 2008
@@ -11,8 +11,4 @@
require 'active_couch/base'
require 'active_couch/connection'
require 'active_couch/migrations'
-require 'active_couch/callbacks'
-
-#ActiveCouch::Base.class_eval do
-# include ActiveCouch::Callbacks
-#end
\ No newline at end of file
+require 'active_couch/callbacks'
\ No newline at end of file
Modified: trunk/lib/active_couch/associations/has_many_association.rb
==============================================================================
--- trunk/lib/active_couch/associations/has_many_association.rb (original)
+++ trunk/lib/active_couch/associations/has_many_association.rb Sat Feb
2 22:44:18 2008
@@ -12,7 +12,7 @@
# Use the inflector to get the correct class if it is not defined
# in the :class key in the options hash
# so has_many :contacts (will try to find the class Contact
and set it to @klass)
- @klass = Inflector.constantize(Inflector.classify(@name))
+ @klass = @name.classify.constantize #Inflector.constantize(Inflector.classify(@name))
end
end
Modified: trunk/lib/active_couch/base.rb
==============================================================================
--- trunk/lib/active_couch/base.rb (original)
+++ trunk/lib/active_couch/base.rb Sat Feb 2 22:44:18 2008
@@ -3,6 +3,21 @@
SPECIAL_MEMBERS = %w(attributes associations connection callbacks)
DEFAULT_ATTRIBUTES = %w(id rev)
+ # Initializes an ActiveCouch::Base object. The constructor accepts
both a hash, as well as
+ # a block to initialize attributes
+ #
+ # Examples:
+ # class Person < ActiveCouch::Base
+ # has :name
+ # end
+ #
+ # person1 = Person.new(:name => "McLovin")
+ # person1.name # => "McLovin"
+ #
+ # person2 = Person.new do |p|
+ # p.name = "Seth"
+ # end
+ # person2.name # => "Seth"
def initialize(params = {})
# Object instance variable
@attributes = {}; @associations = {}; @callbacks = Hash.new;
@connection = self.class.connection
@@ -24,7 +39,7 @@
self.instance_eval "def #{k}; associations[:#{k}].container; end"
# If you have has_many :people, this will add a method called
add_person to the object instantiated
# from the class
- self.instance_eval "def add_#{Inflector.singularize(k)}(val);
associations[:#{k}].push(val); end"
+ self.instance_eval "def add_#{k.singularize}(val);
associations[:#{k}].push(val); end"
end
klass_callbacks.each_key do |k|
@@ -38,6 +53,11 @@
# Set any instance variables if any, which are present in the
params hash
from_hash(params)
+ # Now you can do stuff like
+ # Person.new do |p|
+ # p.name = 'McLovin'
+ # end
+ yield self if block_given?
end
# Generates a JSON representation of an instance of a subclass of ActiveCouch::Base.
@@ -137,6 +157,18 @@
end
end
+ def marshal_dump # :nodoc:
+ self.to_json
+ end
+
+ def marshal_load(str) # :nodoc:
+ self.instance_eval do
+ hash = JSON.parse(str)
+ initialize(hash)
+ end
+ self
+ end
+
class << self # Class methods
# Returns the CouchDB database name that's backing this model.
The database name is guessed from the name of the
@@ -164,10 +196,10 @@
else
# Nested classes are prefixed with singular parent database name.
if parent < ActiveCouch::Base
- contained = Inflector.singularize(parent.database_name)
+ contained = parent.database_name.singularize
contained << '_'
end
- "#{contained}#{Inflector.underscore(Inflector.demodulize(Inflector.pluralize(base.name)))}"
+ "#{contained}#{base.name.pluralize.demodulize.underscore}"
end)
set_database_name(name)
name
@@ -289,7 +321,7 @@
case scope
when :all then find_every(options)
when :first then find_every(options).first
- else find_one(scope) #raise ArgumentError("find
must have the first parameter as either :all or :first")
+ else find_one(scope)
end
end
@@ -436,8 +468,9 @@
# So for example, if the params hash is :name => 'McLovin',
# the view associated with it will be /by_name/by_name?key="McLovin"
def query_string(params)
+ # TODO : Shouldn't check for multiple arguments
if params.is_a?(Hash)
- params.each { |k,v|
return "by_#{k}/by_#{k}?key=#{v.url_encode}" }
+ params.each { |k,v|
return "by_#{k}/by_#{k}?key=#{v.to_s.url_encode}" }
else
raise ArgumentError, "The value for the key 'params' must
be a Hash"
end
@@ -458,7 +491,6 @@
# Instantiates an ActiveCouch::Base object, based on the
result obtained from
# the GET URL
def instantiate_object(result)
- puts result
hash = JSON.parse(result)
self.new(hash)
end
@@ -476,7 +508,7 @@
name, child_klass = assoc.name, assoc.klass
v.each do |child|
child.is_a?(Hash) ? child_obj =
child_klass.new(child) : child_obj = child
- self.send "add_#{Inflector.singularize(name)}", child_obj
+ self.send "add_#{name.singularize}", child_obj
end
end
elsif v.is_a?(Hash) # This means this is a has_one
association (which we might add later)
Modified: trunk/lib/active_couch/callbacks.rb
==============================================================================
--- trunk/lib/active_couch/callbacks.rb (original)
+++ trunk/lib/active_couch/callbacks.rb Sat Feb 2 22:44:18 2008
@@ -4,11 +4,11 @@
def self.included(base)
# Alias methods which will have callbacks, (for now only save
and delete).
- # This creates 2 sets of methods: save_with_callbacks, save_without_callbacks,
+ # This creates 2 pairs of methods: save_with_callbacks, save_without_callbacks,
# delete_with_callbacks, delete_without_callbacks
#
- # save_without_callbacks and delete_without_callbacks have the
same behaviour
- # as the save and delete methods, respectively
+ # save_without_callbacks and delete_without_callbacks
+ # have the same behaviour as the save and delete methods, respectively
[:save, :delete].each do |method|
base.send :alias_method_chain, method, :callbacks
end
@@ -48,6 +48,14 @@
result
end
private :delete_with_callbacks
+
+ def find_with_callbacks
+ return false if callback(:before_find) == false
+ result = find_without_callbacks
+ callback(:after_find)
+ result
+ end
+ private :find_with_callbacks
private
def callback(method)
Modified: trunk/lib/active_couch/migrations/migration.rb
==============================================================================
--- trunk/lib/active_couch/migrations/migration.rb (original)
+++ trunk/lib/active_couch/migrations/migration.rb Sat Feb 2 22:44:18 2008
@@ -56,7 +56,7 @@
def get_view(view)
view_name = view
- view_name = Inflector.underscore("#{self}") if view.nil? ||
view.length == 0
+ view_name = "#{self}".underscore if view.nil? || view.length
== 0
view_name
end
Modified: trunk/lib/active_couch/support/extensions.rb
==============================================================================
--- trunk/lib/active_couch/support/extensions.rb (original)
+++ trunk/lib/active_couch/support/extensions.rb Sat Feb 2 22:44:18 2008
@@ -1,10 +1,19 @@
module ActiveCouch
+ Symbol.class_eval do
+ def singularize; Inflector.singularize(self); end
+ end
+
String.class_eval do
require 'cgi'
- def url_encode
- CGI.escape("\"#{self.to_s}\"")
- end
+ def url_encode; CGI.escape("\"#{self.to_s}\""); end
+ # Delegate to Inflector
+ def singularize; Inflector.singularize(self); end
+ def demodulize; Inflector.demodulize(self); end
+ def pluralize; Inflector.pluralize(self); end
+ def underscore; Inflector.underscore(self); end
+ def classify; Inflector.classify(self); end
+ def constantize; Inflector.constantize(self); end
end
Hash.class_eval do
@@ -47,27 +56,6 @@
parent_name.empty? ? Object : Inflector.constantize(parent_name)
end
- # Encapsulates the common pattern of:
- #
- # alias_method :foo_without_feature, :foo
- # alias_method :foo, :foo_with_feature
- #
- # With this, you simply do:
- #
- # alias_method_chain :foo, :feature
- #
- # And both aliases are set up for you.
- #
- # Query and bang methods (foo?, foo!) keep the same punctuation:
- #
- # alias_method_chain :foo?, :feature
- #
- # is equivalent to
- #
- # alias_method :foo_without_feature?, :foo?
- # alias_method :foo?, :foo_with_feature?
- #
- # so you can safely chain foo, foo?, and foo! with the same feature.
def alias_method_chain(target, feature)
# Strip out punctuation on predicates or bang methods since
# e.g. target?_without_feature is not a valid method name.
Modified: trunk/lib/active_couch/support/inflector.rb
==============================================================================
--- trunk/lib/active_couch/support/inflector.rb (original)
+++ trunk/lib/active_couch/support/inflector.rb Sat Feb 2 22:44:18 2008
@@ -1,8 +1,5 @@
# This file is copied from the ActiveSupport project, which
# is a part of the Ruby On Rails web-framework (http://rubyonrails.org).
-
-# TODO Consider a String extension that delegates to Inflector.
-
require 'singleton'
# The Inflector transforms words from singular to plural, class names
to table names, modularized class names to ones without,
@@ -277,4 +274,4 @@
end
end
-require File.dirname(__FILE__) + '/inflections'
+require File.dirname(__FILE__) + '/inflections'
\ No newline at end of file
Modified: trunk/spec/base/find_spec.rb
==============================================================================
--- trunk/spec/base/find_spec.rb (original)
+++ trunk/spec/base/find_spec.rb Sat Feb 2 22:44:18 2008
@@ -235,4 +235,36 @@
person = Person.find('321')
person.should == nil
end
+end
+
+describe "ActiveCouch::Base #find method with non-String params passed
as arguments" do
+ before(:each) do
+ class Person < ActiveCouch::Base
+ site 'http://localhost:5984/'
+ has :age
+ end
+ # Define the migration
+ class ByAge < ActiveCouch::Migration
+ define :for_db => 'people' do
+ with_key 'age'
+ end
+ end
+ # Create the database first
+ ActiveCouch::Migrator.create_database('http://localhost:5984', 'people')
+ # Create a view
+ ActiveCouch::Migrator.migrate('http://localhost:5984', ByAge)
+ # Save two objects
+ Person.create(:age => "21")
+ end
+
+ after(:each) do
+ # Delete the database last
+ ActiveCouch::Migrator.delete_database('http://localhost:5984', 'people')
+ Object.send(:remove_const, :Person)
+ end
+
+ it "should return an ActiveCouch::Base object" do
+ person = Person.find(:first, :params => {:age => 21})
+ person.age.should == "21"
+ end
end
Modified: trunk/spec/base/initialize_spec.rb
==============================================================================
--- trunk/spec/base/initialize_spec.rb (original)
+++ trunk/spec/base/initialize_spec.rb Sat Feb 2 22:44:18 2008
@@ -47,9 +47,45 @@
Object.send(:remove_const, :Person)
end
- it "should be able to initialize attributes correclty from the has,
including CouchDB reserved attributes" do
+ it "should be able to initialize attributes correctly, including
CouchDB reserved attributes" do
p = Person.new(:name => 'McLovin', :id => '123')
p.name.should == 'McLovin'
p.id.should == '123'
+ end
+end
+
+describe "ActiveCouch::Base #new method with a block (and self being
passed to the block)" do
+ before(:all) do
+ class Dog < ActiveCouch::Base
+ has :name
+ has :age, :which_is => :number
+ has :collar
+ end
+ end
+
+ after(:all) do
+ Object.send(:remove_const, :Dog)
+ end
+
+ it "should be able to initialize all the attributes correctly" do
+ dog = Dog.new do |d|
+ d.name = "Buster"
+ d.age = 2
+ d.collar = "Stray"
+ end
+
+ dog.name.should == "Buster"
+ dog.age.should == 2
+ dog.collar.should == "Stray"
+ end
+
+ it "should be able to initialize all attributes (including CouchDB
reserved attributes)" do
+ dog = Dog.new do |d|
+ d.name = "Spike"
+ d.id = 'underdog_1'
+ end
+
+ dog.name.should == "Spike"
+ dog.id.should == 'underdog_1'
end
end
Added: trunk/spec/base/marshal_dump_spec.rb
==============================================================================
--- (empty file)
+++ trunk/spec/base/marshal_dump_spec.rb Sat Feb 2 22:44:18 2008
@@ -0,0 +1,73 @@
+require File.dirname(__FILE__) + '/../spec_helper.rb'
+
+describe "ActiveCouch::Base #marshal_dump method with just simple
attributes" do
+ before(:each) do
+ class Hotel < ActiveCouch::Base
+ has :name, :which_is => :text, :with_default_value => "Swissotel
The Stamford"
+ has :star_rating, :which_is => :decimal, :with_default_value => 5.0
+ has :rooms, :which_is => :number, :with_default_value => 100
+ end
+
+ @h = Hotel.new
+ end
+
+ after(:each) do
+ Object.send(:remove_const, :Hotel)
+ end
+
+ it "should have to the marshal_dump method" do
+ @h.should respond_to(:marshal_dump)
+ end
+
+ it "should produce valid JSON output when sent the marshal_dump
method" do
+ marshal_dump = @h.marshal_dump
+ # Check for JSON regex, since attributes can appear in any order
+ (marshal_dump =~ /"name":"Swissotel The Stamford"/).should_not == nil
+ (marshal_dump =~ /"rooms":100/).should_not == nil
+ (marshal_dump =~ /"star_rating":5.0/).should_not == nil
+ end
+
+ it "should produce valid JSON output when an attribute has been
changed and the marshal_dump method is sent" do
+ @h.rooms = 200
+ marshal_dump = @h.marshal_dump
+ # Check for JSON regex, since attributes can appear in any order
+ (marshal_dump =~ /"name":"Swissotel The Stamford"/).should_not == nil
+ (marshal_dump =~ /"rooms":200/).should_not == nil
+ (marshal_dump =~ /"star_rating":5.0/).should_not == nil
+ end
+end
+
+describe "ActiveCouch::Base #marshal_dump with associations" do
+ before(:each) do
+ class Hospital < ActiveCouch::Base
+ has :name
+ end
+
+ class CrazyPerson < ActiveCouch::Base
+ has :name, :which_is => :text, :with_default_value => "Crazed McLovin"
+ has_many :hospitals
+ end
+
+ @c = CrazyPerson.new
+
+ @h1 = Hospital.new(:name => "Crazy Hospital 1")
+ @h2 = Hospital.new(:name => "Crazy Hospital 2")
+
+ @c.add_hospital(@h1)
+ @c.add_hospital(@h2)
+ end
+
+ after(:each) do
+ Object.send(:remove_const, :Hospital)
+ Object.send(:remove_const, :CrazyPerson)
+ end
+
+ it "should produce valid JSON when sent the marshal_dump method" do
+ marshal_dump = @c.marshal_dump
+ # Check for JSON regex, since attributes can appear in any order
+ (marshal_dump =~ /"name":"Crazed McLovin"/).should_not == nil
+ (marshal_dump =~ /"hospitals":\[.*?\]/).should_not == nil
+ (marshal_dump =~ /\{.*?"name":"Crazy Hospital 1".*?\}/).should_not
== nil
+ (marshal_dump =~ /\{.*?"name":"Crazy Hospital 2".*?\}/).should_not
== nil
+ end
+end
\ No newline at end of file
Added: trunk/spec/base/marshal_load_spec.rb
==============================================================================
--- (empty file)
+++ trunk/spec/base/marshal_load_spec.rb Sat Feb 2 22:44:18 2008
@@ -0,0 +1,56 @@
+require File.dirname(__FILE__) + '/../spec_helper.rb'
+
+describe "ActiveCouch::Base #marshal_load method, with many
attributes" do
+ before(:all) do
+ class Hotel < ActiveCouch::Base
+ has :name, :which_is => :text, :with_default_value => "Swissotel
The Stamford"
+ has :star_rating, :which_is => :decimal, :with_default_value => 5.0
+ has :rooms, :which_is => :number, :with_default_value => 100
+ end
+
+ class Hospital < ActiveCouch::Base
+ has :name
+ end
+
+ class CrazyPerson < ActiveCouch::Base
+ has :name, :which_is => :text, :with_default_value => "Crazed McLovin"
+ has_many :hospitals
+ end
+ end
+
+ after(:all) do
+ Object.send(:remove_const, :Hotel)
+ Object.send(:remove_const, :Hospital)
+ Object.send(:remove_const, :CrazyPerson)
+ end
+
+ it "should have the marshal_load method" do
+ Hotel.new.should respond_to(:marshal_load)
+ Hospital.new.should respond_to(:marshal_load)
+ CrazyPerson.new.should respond_to(:marshal_load)
+ end
+
+ it "should instantiate an object when sent the marshal_load method
with valid json as a parameter" do
+ h = Hotel.new
+ h = h.marshal_load("{\"name\":\"Swissotel The Stamford\",\"rooms\":200,\"star_rating\":4.0}")
+ h.class.should == Hotel
+ # Check whether all attributes are set correctly
+ h.name.should == "Swissotel The Stamford"
+ h.rooms.should == 200
+ h.star_rating.should == 4.0
+ end
+
+ it "should instantiate an object when sent the marshal_load method
with valid JSON (containing associations) as a parameter" do
+ crazy = CrazyPerson.new.marshal_load('{"name":"Crazed
McLovin","hospitals":[{"name":"Crazy Hospital 1"},{"name":"Crazy
Hospital 2"}]}')
+ crazy.class.should == CrazyPerson
+
+ crazy.name == "Crazed McLovin"
+ crazy.hospitals.size.should == 2
+
+ hospitals = crazy.hospitals.collect{|h| h.name }
+ hospitals.sort!
+
+ hospitals.first.should == 'Crazy Hospital 1'
+ hospitals.last.should == 'Crazy Hospital 2'
+ end
+end
\ No newline at end of file