Merge branch 'master' of github.com:couchrest/couchrest_model into improve_associations
This commit is contained in:
commit
ee31946e07
2
Rakefile
2
Rakefile
|
@ -24,7 +24,7 @@ begin
|
||||||
gemspec.homepage = "http://github.com/couchrest/couchrest_model"
|
gemspec.homepage = "http://github.com/couchrest/couchrest_model"
|
||||||
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
|
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
|
||||||
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
||||||
gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt couchrest.gemspec) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
|
gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt couchrest.gemspec) + Dir["{examples,lib,spec}/**/*"] - Dir["spec/tmp"]
|
||||||
gemspec.has_rdoc = true
|
gemspec.has_rdoc = true
|
||||||
gemspec.add_dependency("couchrest", ">= 1.0.0")
|
gemspec.add_dependency("couchrest", ">= 1.0.0")
|
||||||
gemspec.add_dependency("mime-types", ">= 1.15")
|
gemspec.add_dependency("mime-types", ">= 1.15")
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'couchrest')
|
|
||||||
|
|
||||||
def show obj
|
|
||||||
puts obj.inspect
|
|
||||||
puts
|
|
||||||
end
|
|
||||||
|
|
||||||
SERVER = CouchRest.new
|
|
||||||
SERVER.default_database = 'couchrest-extendeddoc-example'
|
|
||||||
|
|
||||||
class Author < CouchRest::ExtendedDocument
|
|
||||||
use_database SERVER.default_database
|
|
||||||
property :name
|
|
||||||
|
|
||||||
def drink_scotch
|
|
||||||
puts "... glug type glug ... I'm #{name} ... type glug glug ..."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Post < CouchRest::ExtendedDocument
|
|
||||||
use_database SERVER.default_database
|
|
||||||
|
|
||||||
property :title
|
|
||||||
property :body
|
|
||||||
property :author, :cast_as => 'Author'
|
|
||||||
|
|
||||||
timestamps!
|
|
||||||
end
|
|
||||||
|
|
||||||
class Comment < CouchRest::ExtendedDocument
|
|
||||||
use_database SERVER.default_database
|
|
||||||
|
|
||||||
property :commenter, :cast_as => 'Author'
|
|
||||||
timestamps!
|
|
||||||
|
|
||||||
def post= post
|
|
||||||
self["post_id"] = post.id
|
|
||||||
end
|
|
||||||
def post
|
|
||||||
Post.get(self['post_id']) if self['post_id']
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "Act I: CRUD"
|
|
||||||
puts
|
|
||||||
puts "(pause for dramatic effect)"
|
|
||||||
puts
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
puts "Create an author."
|
|
||||||
quentin = Author.new("name" => "Quentin Hazel")
|
|
||||||
show quentin
|
|
||||||
|
|
||||||
puts "Create a new post."
|
|
||||||
post = Post.new(:title => "First Post", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit...")
|
|
||||||
show post
|
|
||||||
|
|
||||||
puts "Add the author to the post."
|
|
||||||
post.author = quentin
|
|
||||||
show post
|
|
||||||
|
|
||||||
puts "Save the post."
|
|
||||||
post.save
|
|
||||||
show post
|
|
||||||
|
|
||||||
puts "Load the post."
|
|
||||||
reloaded = Post.get(post.id)
|
|
||||||
show reloaded
|
|
||||||
|
|
||||||
puts "The author of the post is an instance of Author."
|
|
||||||
reloaded.author.drink_scotch
|
|
||||||
|
|
||||||
puts "\nAdd some comments to the post."
|
|
||||||
comment_one = Comment.new :text => "Blah blah blah", :commenter => {:name => "Joe Sixpack"}
|
|
||||||
comment_two = Comment.new :text => "Yeah yeah yeah", :commenter => {:name => "Jane Doe"}
|
|
||||||
comment_three = Comment.new :text => "Whatever...", :commenter => {:name => "John Stewart"}
|
|
||||||
|
|
||||||
# TODO - maybe add some magic here?
|
|
||||||
comment_one.post = post
|
|
||||||
comment_two.post = post
|
|
||||||
comment_three.post = post
|
|
||||||
comment_one.save
|
|
||||||
comment_two.save
|
|
||||||
comment_three.save
|
|
||||||
|
|
||||||
show comment_one
|
|
||||||
show comment_two
|
|
||||||
show comment_three
|
|
||||||
|
|
||||||
puts "We can load a post through its comment (no magic here)."
|
|
||||||
show post = comment_one.post
|
|
||||||
|
|
||||||
puts "Commenters are also authors."
|
|
||||||
comment_two['commenter'].drink_scotch
|
|
||||||
comment_one['commenter'].drink_scotch
|
|
||||||
comment_three['commenter'].drink_scotch
|
|
||||||
|
|
||||||
puts "\nLet's save an author to her own document."
|
|
||||||
jane = comment_two['commenter']
|
|
||||||
jane.save
|
|
||||||
show jane
|
|
||||||
|
|
||||||
puts "Oh, that's neat! Because Ruby passes hash valuee by reference, Jane's new id has been added to the comment she left."
|
|
||||||
show comment_two
|
|
||||||
|
|
||||||
puts "Of course, we'd better remember to save it."
|
|
||||||
comment_two.save
|
|
||||||
show comment_two
|
|
||||||
|
|
||||||
puts "Oooh, denormalized... feel the burn!"
|
|
||||||
puts
|
|
||||||
puts
|
|
||||||
puts
|
|
||||||
puts "Act II: Views"
|
|
||||||
puts
|
|
||||||
puts
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
puts "Let's find all the comments that go with our post."
|
|
||||||
puts "Our post has id #{post.id}, so lets find all the comments with that post_id."
|
|
||||||
puts
|
|
||||||
|
|
||||||
class Comment
|
|
||||||
view_by :post_id
|
|
||||||
end
|
|
||||||
|
|
||||||
comments = Comment.by_post_id :key => post.id
|
|
||||||
show comments
|
|
||||||
|
|
||||||
puts "That was too easy."
|
|
||||||
puts "We can even wrap it up in a finder on the Post class."
|
|
||||||
puts
|
|
||||||
|
|
||||||
class Post
|
|
||||||
def comments
|
|
||||||
Comment.by_post_id :key => id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
show post.comments
|
|
||||||
puts "Gimme 5 minutes and I'll roll this into the framework. ;)"
|
|
||||||
puts
|
|
||||||
puts "There is a lot more that can be done with views, but a lot of the interesting stuff is joins, which of course range across types. We'll pick up where we left off, next time."
|
|
|
@ -39,7 +39,7 @@ module CouchRest
|
||||||
attr_accessor :casted_by
|
attr_accessor :casted_by
|
||||||
|
|
||||||
|
|
||||||
# Instantiate a new ExtendedDocument by preparing all properties
|
# Instantiate a new CouchRest::Model::Base by preparing all properties
|
||||||
# using the provided document hash.
|
# using the provided document hash.
|
||||||
#
|
#
|
||||||
# Options supported:
|
# Options supported:
|
||||||
|
|
|
@ -7,11 +7,15 @@ module CouchRest
|
||||||
def create_attachment(args={})
|
def create_attachment(args={})
|
||||||
raise ArgumentError unless args[:file] && args[:name]
|
raise ArgumentError unless args[:file] && args[:name]
|
||||||
return if has_attachment?(args[:name])
|
return if has_attachment?(args[:name])
|
||||||
self['_attachments'] ||= {}
|
|
||||||
set_attachment_attr(args)
|
set_attachment_attr(args)
|
||||||
rescue ArgumentError => e
|
rescue ArgumentError => e
|
||||||
raise ArgumentError, 'You must specify :file and :name'
|
raise ArgumentError, 'You must specify :file and :name'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# return all attachments
|
||||||
|
def attachments
|
||||||
|
self['_attachments'] ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
# reads the data from an attachment
|
# reads the data from an attachment
|
||||||
def read_attachment(attachment_name)
|
def read_attachment(attachment_name)
|
||||||
|
@ -30,13 +34,13 @@ module CouchRest
|
||||||
|
|
||||||
# deletes a file attachment from the current doc
|
# deletes a file attachment from the current doc
|
||||||
def delete_attachment(attachment_name)
|
def delete_attachment(attachment_name)
|
||||||
return unless self['_attachments']
|
return unless attachments
|
||||||
self['_attachments'].delete attachment_name
|
attachments.delete attachment_name
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns true if attachment_name exists
|
# returns true if attachment_name exists
|
||||||
def has_attachment?(attachment_name)
|
def has_attachment?(attachment_name)
|
||||||
!!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
|
!!(attachments && attachments[attachment_name] && !attachments[attachment_name].empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns URL to fetch the attachment from
|
# returns URL to fetch the attachment from
|
||||||
|
@ -62,7 +66,7 @@ module CouchRest
|
||||||
def set_attachment_attr(args)
|
def set_attachment_attr(args)
|
||||||
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
|
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
|
||||||
content_type ||= (get_mime_type(args[:name]) || 'text/plain')
|
content_type ||= (get_mime_type(args[:name]) || 'text/plain')
|
||||||
self['_attachments'][args[:name]] = {
|
attachments[args[:name]] = {
|
||||||
'content_type' => content_type,
|
'content_type' => content_type,
|
||||||
'data' => args[:file].read
|
'data' => args[:file].read
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,13 @@ describe "Model attachments" do
|
||||||
@obj.delete_attachment(@attachment_name)
|
@obj.delete_attachment(@attachment_name)
|
||||||
@obj.has_attachment?(@attachment_name).should be_false
|
@obj.has_attachment?(@attachment_name).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should return false if an attachment has been removed and reloaded' do
|
||||||
|
@obj.delete_attachment(@attachment_name)
|
||||||
|
reloaded_obj = Basic.get(@obj.id)
|
||||||
|
reloaded_obj.has_attachment?(@attachment_name).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "creating an attachment" do
|
describe "creating an attachment" do
|
||||||
|
@ -46,14 +53,14 @@ describe "Model attachments" do
|
||||||
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
||||||
@obj.save.should be_true
|
@obj.save.should be_true
|
||||||
reloaded_obj = Basic.get(@obj.id)
|
reloaded_obj = Basic.get(@obj.id)
|
||||||
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
reloaded_obj.attachments[@attachment_name].should_not be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should create an attachment from file without an extension" do
|
it "should create an attachment from file without an extension" do
|
||||||
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
||||||
@obj.save.should be_true
|
@obj.save.should be_true
|
||||||
reloaded_obj = Basic.get(@obj.id)
|
reloaded_obj = Basic.get(@obj.id)
|
||||||
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
reloaded_obj.attachments[@attachment_name].should_not be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should raise ArgumentError if :file is missing' do
|
it 'should raise ArgumentError if :file is missing' do
|
||||||
|
@ -66,19 +73,19 @@ describe "Model attachments" do
|
||||||
|
|
||||||
it 'should set the content-type if passed' do
|
it 'should set the content-type if passed' do
|
||||||
@obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
|
@obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
|
||||||
@obj['_attachments'][@attachment_name]['content_type'].should == @content_type
|
@obj.attachments[@attachment_name]['content_type'].should == @content_type
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should detect the content-type automatically" do
|
it "should detect the content-type automatically" do
|
||||||
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
||||||
@obj['_attachments']['couchdb.png']['content_type'].should == "image/png"
|
@obj.attachments['couchdb.png']['content_type'].should == "image/png"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should use name to detect the content-type automatically if no file" do
|
it "should use name to detect the content-type automatically if no file" do
|
||||||
file = File.open(FIXTURE_PATH + '/attachments/couchdb.png')
|
file = File.open(FIXTURE_PATH + '/attachments/couchdb.png')
|
||||||
file.stub!(:path).and_return("badfilname")
|
file.stub!(:path).and_return("badfilname")
|
||||||
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
||||||
@obj['_attachments']['couchdb.png']['content_type'].should == "image/png"
|
@obj.attachments['couchdb.png']['content_type'].should == "image/png"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -113,7 +120,7 @@ describe "Model attachments" do
|
||||||
file = File.open(FIXTURE_PATH + '/attachments/README')
|
file = File.open(FIXTURE_PATH + '/attachments/README')
|
||||||
@file.should_not == file
|
@file.should_not == file
|
||||||
@obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
|
@obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
|
||||||
@obj['_attachments'][@attachment_name]['content_type'].should == @content_type
|
@obj.attachments[@attachment_name]['content_type'].should == @content_type
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should delete an attachment that exists' do
|
it 'should delete an attachment that exists' do
|
||||||
|
@ -143,6 +150,27 @@ describe "Model attachments" do
|
||||||
it 'should return the attachment URI' do
|
it 'should return the attachment URI' do
|
||||||
@obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
|
@obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#attachments" do
|
||||||
|
before(:each) do
|
||||||
|
@obj = Basic.new
|
||||||
|
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||||
|
@attachment_name = 'my_attachment'
|
||||||
|
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||||
|
@obj.save.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return an empty Hash when document does not have any attachment' do
|
||||||
|
new_obj = Basic.new
|
||||||
|
new_obj.save.should be_true
|
||||||
|
new_obj.attachments.should == {}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return a Hash with all attachments' do
|
||||||
|
@file.rewind
|
||||||
|
@obj.attachments.should == { @attachment_name =>{ "data" => "PCFET0NUWVBFIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPHRpdGxlPlRlc3Q8L3RpdGxlPgogIDwvaGVhZD4KICA8Ym9keT4KICAgIDxwPgogICAgICBUZXN0CiAgICA8L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==", "content_type" => "text/html"}}
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,14 +21,14 @@ begin
|
||||||
class ExtendedChild < ExtendedParent
|
class ExtendedChild < ExtendedParent
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Using chained inheritance without CouchRest::ExtendedDocument" do
|
describe "Using chained inheritance without CouchRest::Model::Base" do
|
||||||
it "should preserve inheritable attributes" do
|
it "should preserve inheritable attributes" do
|
||||||
PlainParent.foo.should == :bar
|
PlainParent.foo.should == :bar
|
||||||
PlainChild.foo.should == :bar
|
PlainChild.foo.should == :bar
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Using chained inheritance with CouchRest::ExtendedDocument" do
|
describe "Using chained inheritance with CouchRest::Model::Base" do
|
||||||
it "should preserve inheritable attributes" do
|
it "should preserve inheritable attributes" do
|
||||||
ExtendedParent.foo.should == :bar
|
ExtendedParent.foo.should == :bar
|
||||||
ExtendedChild.foo.should == :bar
|
ExtendedChild.foo.should == :bar
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'couchrest'
|
|
||||||
|
|
||||||
# set the source db and map view
|
|
||||||
source = CouchRest.new("http://127.0.0.1:5984").database('source-db')
|
|
||||||
source_view = 'mydesign/view-map'
|
|
||||||
|
|
||||||
# set the target db
|
|
||||||
target = CouchRest.new("http://127.0.0.1:5984").database('target-db')
|
|
||||||
|
|
||||||
|
|
||||||
pager = CouchRest::Pager.new(source)
|
|
||||||
|
|
||||||
# pager will yield once per uniq key in the source view
|
|
||||||
|
|
||||||
pager.key_reduce(source_view, 10000) do |key, values|
|
|
||||||
# create a doc from the key and the values
|
|
||||||
example_doc = {
|
|
||||||
:key => key,
|
|
||||||
:values => values.uniq
|
|
||||||
}
|
|
||||||
|
|
||||||
target.save(example_doc)
|
|
||||||
|
|
||||||
# keep us up to date with progress
|
|
||||||
puts k if (rand > 0.9)
|
|
||||||
end
|
|
|
@ -1,30 +0,0 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'couchrest'
|
|
||||||
|
|
||||||
# subset.rb replicates a percentage of a database to a fresh database.
|
|
||||||
# use it to create a smaller dataset on which to prototype views.
|
|
||||||
|
|
||||||
# specify the source database
|
|
||||||
source = CouchRest.new("http://127.0.0.1:5984").database('source-db')
|
|
||||||
|
|
||||||
# specify the target database
|
|
||||||
target = CouchRest.new("http://127.0.0.1:5984").database('target-db')
|
|
||||||
|
|
||||||
# pager efficiently yields all view rows
|
|
||||||
pager = CouchRest::Pager.new(source)
|
|
||||||
|
|
||||||
pager.all_docs(1000) do |rows|
|
|
||||||
docs = rows.collect do |r|
|
|
||||||
# the percentage of docs to clone
|
|
||||||
next if rand > 0.1
|
|
||||||
doc = source.get(r['id'])
|
|
||||||
doc.delete('_rev')
|
|
||||||
doc
|
|
||||||
end.compact
|
|
||||||
puts docs.length
|
|
||||||
next if docs.empty?
|
|
||||||
|
|
||||||
puts docs.first['_id']
|
|
||||||
target.bulk_save(docs)
|
|
||||||
end
|
|
||||||
|
|
Loading…
Reference in a new issue