Merge branch 'master' of github.com:couchrest/couchrest_model into improve_associations

improve_associations
Sam Lown 2010-08-11 17:36:02 +02:00
commit ee31946e07
8 changed files with 48 additions and 217 deletions

View File

@ -24,7 +24,7 @@ begin
gemspec.homepage = "http://github.com/couchrest/couchrest_model"
gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
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.add_dependency("couchrest", ">= 1.0.0")
gemspec.add_dependency("mime-types", ">= 1.15")

View File

@ -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."

View File

@ -39,7 +39,7 @@ module CouchRest
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.
#
# Options supported:

View File

@ -7,11 +7,15 @@ module CouchRest
def create_attachment(args={})
raise ArgumentError unless args[:file] && args[:name]
return if has_attachment?(args[:name])
self['_attachments'] ||= {}
set_attachment_attr(args)
rescue ArgumentError => e
raise ArgumentError, 'You must specify :file and :name'
end
# return all attachments
def attachments
self['_attachments'] ||= {}
end
# reads the data from an attachment
def read_attachment(attachment_name)
@ -30,13 +34,13 @@ module CouchRest
# deletes a file attachment from the current doc
def delete_attachment(attachment_name)
return unless self['_attachments']
self['_attachments'].delete attachment_name
return unless attachments
attachments.delete attachment_name
end
# returns true if attachment_name exists
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
# returns URL to fetch the attachment from
@ -62,7 +66,7 @@ module CouchRest
def set_attachment_attr(args)
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
content_type ||= (get_mime_type(args[:name]) || 'text/plain')
self['_attachments'][args[:name]] = {
attachments[args[:name]] = {
'content_type' => content_type,
'data' => args[:file].read
}

View File

@ -30,6 +30,13 @@ describe "Model attachments" do
@obj.delete_attachment(@attachment_name)
@obj.has_attachment?(@attachment_name).should be_false
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
describe "creating an attachment" do
@ -46,14 +53,14 @@ describe "Model attachments" do
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
@obj.save.should be_true
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
it "should create an attachment from file without an extension" do
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
@obj.save.should be_true
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
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
@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
it "should detect the content-type automatically" do
@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
it "should use name to detect the content-type automatically if no file" do
file = File.open(FIXTURE_PATH + '/attachments/couchdb.png')
file.stub!(:path).and_return("badfilname")
@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
@ -113,7 +120,7 @@ describe "Model attachments" do
file = File.open(FIXTURE_PATH + '/attachments/README')
@file.should_not == file
@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
it 'should delete an attachment that exists' do
@ -143,6 +150,27 @@ describe "Model attachments" do
it 'should return the attachment URI' do
@obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
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

View File

@ -21,14 +21,14 @@ begin
class ExtendedChild < ExtendedParent
end
describe "Using chained inheritance without CouchRest::ExtendedDocument" do
describe "Using chained inheritance without CouchRest::Model::Base" do
it "should preserve inheritable attributes" do
PlainParent.foo.should == :bar
PlainChild.foo.should == :bar
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
ExtendedParent.foo.should == :bar
ExtendedChild.foo.should == :bar

View File

@ -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

View File

@ -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