Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

removed (incorrectly) vendored couchrest

  • Loading branch information...
commit 6682a8f6e515fa8c99afe74b46d82824aa83de9e 1 parent a819814
@benatkin authored
View
71 lib/couchrest/commands/generate.rb
@@ -1,71 +0,0 @@
-require 'fileutils'
-
-module CouchRest
- module Commands
- module Generate
-
- def self.run(options)
- directory = options[:directory]
- design_names = options[:trailing_args]
-
- FileUtils.mkdir_p(directory)
- filename = File.join(directory, "lib.js")
- self.write(filename, <<-FUNC)
- // Put global functions here.
- // Include in your views with
- //
- // //include-lib
- FUNC
-
- design_names.each do |design_name|
- subdirectory = File.join(directory, design_name)
- FileUtils.mkdir_p(subdirectory)
- filename = File.join(subdirectory, "sample-map.js")
- self.write(filename, <<-FUNC)
- function(doc) {
- // Keys is first letter of _id
- emit(doc._id[0], doc);
- }
- FUNC
-
- filename = File.join(subdirectory, "sample-reduce.js")
- self.write(filename, <<-FUNC)
- function(keys, values) {
- // Count the number of keys starting with this letter
- return values.length;
- }
- FUNC
-
- filename = File.join(subdirectory, "lib.js")
- self.write(filename, <<-FUNC)
- // Put functions specific to '#{design_name}' here.
- // Include in your views with
- //
- // //include-lib
- FUNC
- end
- end
-
- def self.help
- helpstring = <<-GEN
-
- Usage: couchview generate directory design1 design2 design3 ...
-
- Couchview will create directories and example views for the design documents you specify.
-
- GEN
- helpstring.gsub(/^ /, '')
- end
-
- def self.write(filename, contents)
- puts "Writing #{filename}"
- File.open(filename, "w") do |f|
- # Remove leading spaces
- contents.gsub!(/^ ( )?/, '')
- f.write contents
- end
- end
-
- end
- end
-end
View
103 lib/couchrest/commands/push.rb
@@ -1,103 +0,0 @@
-module CouchRest
-
- module Commands
-
- module Push
-
- def self.run(options)
- directory = options[:directory]
- database = options[:trailing_args].first
-
- fm = CouchRest::FileManager.new(database)
- fm.loud = options[:loud]
-
- if options[:loud]
- puts "Pushing views from directory #{directory} to database #{fm.db}"
- end
-
- fm.push_views(directory)
- end
-
- def self.help
- helpstring = <<-GEN
-
- == Pushing views with Couchview ==
-
- Usage: couchview push directory dbname
-
- Couchview expects a specific filesystem layout for your CouchDB views (see
- example below). It also supports advanced features like inlining of library
- code (so you can keep DRY) as well as avoiding unnecessary document
- modification.
-
- Couchview also solves a problem with CouchDB's view API, which only provides
- access to the final reduce side of any views which have both a map and a
- reduce function defined. The intermediate map results are often useful for
- development and production. CouchDB is smart enough to reuse map indexes for
- functions duplicated across views within the same design document.
-
- For views with a reduce function defined, Couchview creates both a reduce view
- and a map-only view, so that you can browse and query the map side as well as
- the reduction, with no performance penalty.
-
- == Example ==
-
- couchview push foo-project/bar-views baz-database
-
- This will push the views defined in foo-project/bar-views into a database
- called baz-database. Couchview expects the views to be defined in files with
- names like:
-
- foo-project/bar-views/my-design/viewname-map.js
- foo-project/bar-views/my-design/viewname-reduce.js
- foo-project/bar-views/my-design/noreduce-map.js
-
- Pushed to => http://127.0.0.1:5984/baz-database/_design/my-design
-
- And the design document:
- {
- "views" : {
- "viewname-map" : {
- "map" : "### contents of view-name-map.js ###"
- },
- "viewname-reduce" : {
- "map" : "### contents of view-name-map.js ###",
- "reduce" : "### contents of view-name-reduce.js ###"
- },
- "noreduce-map" : {
- "map" : "### contents of noreduce-map.js ###"
- }
- }
- }
-
- Couchview will create a design document for each subdirectory of the views
- directory specified on the command line.
-
- == Library Inlining ==
-
- Couchview can optionally inline library code into your views so you only have
- to maintain it in one place. It looks for any files named lib.* in your
- design-doc directory (for doc specific libs) and in the parent views directory
- (for project global libs). These libraries are only inserted into views which
- include the text
-
- // !include lib
-
- or
-
- # !include lib
-
- Couchview is a result of scratching my own itch. I'd be happy to make it more
- general, so please contact me at jchris@grabb.it if you'd like to see anything
- added or changed.
-
- GEN
- helpstring.gsub(/^ /, '')
- end
-
- end
-
-
- end
-
-end
View
315 lib/couchrest/core/database.rb
@@ -1,315 +0,0 @@
-require 'cgi'
-require "base64"
-
-module CouchRest
- class Database
- attr_reader :server, :host, :name, :root, :uri
- attr_accessor :bulk_save_cache_limit
-
- # Create a CouchRest::Database adapter for the supplied CouchRest::Server
- # and database name.
- #
- # ==== Parameters
- # server<CouchRest::Server>:: database host
- # name<String>:: database name
- #
- def initialize(server, name)
- @name = name
- @server = server
- @host = server.uri
- @uri = @root = "#{host}/#{name}"
- @streamer = Streamer.new(self)
- @bulk_save_cache = []
- @bulk_save_cache_limit = 50
- end
-
- # returns the database's uri
- def to_s
- @uri
- end
-
- # GET the database info from CouchDB
- def info
- CouchRest.get @uri
- end
-
- # Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
- def documents(params = {})
- keys = params.delete(:keys)
- url = CouchRest.paramify_url "#{@uri}/_all_docs", params
- if keys
- CouchRest.post(url, {:keys => keys})
- else
- CouchRest.get url
- end
- end
-
- # POST a temporary view function to CouchDB for querying. This is not
- # recommended, as you don't get any performance benefit from CouchDB's
- # materialized views. Can be quite slow on large databases.
- def slow_view(funcs, params = {})
- keys = params.delete(:keys)
- funcs = funcs.merge({:keys => keys}) if keys
- url = CouchRest.paramify_url "#{@uri}/_temp_view", params
- JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
- end
-
- # backwards compatibility is a plus
- alias :temp_view :slow_view
-
- # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
- # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
- def view(name, params = {}, &block)
- keys = params.delete(:keys)
- url = CouchRest.paramify_url "#{@uri}/_view/#{name}", params
- if keys
- CouchRest.post(url, {:keys => keys})
- else
- if block_given?
- @streamer.view(name, params, &block)
- else
- CouchRest.get url
- end
- end
- end
-
- # GET a document from CouchDB, by id. Returns a Ruby Hash.
- def get(id, rev=nil)
- slug = escape_docid(id)
- query = rev == true ? "?revs=true" : (rev ? "?rev=#{rev}" : "")
- hash = CouchRest.get("#{@uri}/#{slug}#{query}")
- doc = if /^_design/ =~ hash["_id"]
- Design.new(hash)
- else
- Document.new(hash)
- end
- doc.database = self
- doc
- end
-
- # GET an attachment directly from CouchDB
- def fetch_attachment(doc, name)
- # slug = escape_docid(docid)
- # name = CGI.escape(name)
-
- uri = uri_for_attachment(doc, name)
-
- RestClient.get uri
- # "#{@uri}/#{slug}/#{name}"
- end
-
- # PUT an attachment directly to CouchDB
- def put_attachment(doc, name, file, options = {})
- docid = escape_docid(doc['_id'])
- name = CGI.escape(name)
- uri = uri_for_attachment(doc, name)
- JSON.parse(RestClient.put(uri, file, options))
- end
-
- # DELETE an attachment directly from CouchDB
- def delete_attachment doc, name
- uri = uri_for_attachment(doc, name)
- # this needs a rev
- JSON.parse(RestClient.delete(uri))
- end
-
- # Save a document to CouchDB. This will use the <tt>_id</tt> field from
- # the document as the id for PUT, or request a new UUID from CouchDB, if
- # no <tt>_id</tt> is present on the document. IDs are attached to
- # documents on the client side because POST has the curious property of
- # being automatically retried by proxies in the event of network
- # segmentation and lost responses.
- #
- # If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
- # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
- def save_doc(doc, bulk = false)
- if doc['_attachments']
- doc['_attachments'] = encode_attachments(doc['_attachments'])
- end
- if bulk
- @bulk_save_cache << doc
- return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
- return {"ok" => true} # Compatibility with Document#save
- elsif !bulk && @bulk_save_cache.length > 0
- bulk_save
- end
- result = if doc['_id']
- slug = escape_docid(doc['_id'])
- CouchRest.put "#{@uri}/#{slug}", doc
- else
- begin
- slug = doc['_id'] = @server.next_uuid
- CouchRest.put "#{@uri}/#{slug}", doc
- rescue #old version of couchdb
- CouchRest.post @uri, doc
- end
- end
- if result['ok']
- doc['_id'] = result['id']
- doc['_rev'] = result['rev']
- doc.database = self if doc.respond_to?(:database=)
- end
- result
- end
-
- ### DEPRECATION NOTICE
- def save(doc, bulk=false)
- puts "CouchRest::Database's save method is being deprecated, please use save_doc instead"
- save_doc(doc, bulk)
- end
-
-
- # POST an array of documents to CouchDB. If any of the documents are
- # missing ids, supply one from the uuid cache.
- #
- # If called with no arguments, bulk saves the cache of documents to be bulk saved.
- def bulk_save(docs = nil, use_uuids = true)
- if docs.nil?
- docs = @bulk_save_cache
- @bulk_save_cache = []
- end
- if (use_uuids)
- ids, noids = docs.partition{|d|d['_id']}
- uuid_count = [noids.length, @server.uuid_batch_count].max
- noids.each do |doc|
- nextid = @server.next_uuid(uuid_count) rescue nil
- doc['_id'] = nextid if nextid
- end
- end
- CouchRest.post "#{@uri}/_bulk_docs", {:docs => docs}
- end
-
- # DELETE the document from CouchDB that has the given <tt>_id</tt> and
- # <tt>_rev</tt>.
- #
- # If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
- # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
- def delete_doc(doc, bulk = false)
- raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
- if bulk
- @bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
- return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
- return { "ok" => true } # Mimic the non-deferred version
- end
- slug = escape_docid(doc['_id'])
- CouchRest.delete "#{@uri}/#{slug}?rev=#{doc['_rev']}"
- end
-
- ### DEPRECATION NOTICE
- def delete(doc, bulk=false)
- puts "CouchRest::Database's delete method is being deprecated, please use delete_doc instead"
- delete_doc(doc, bulk)
- end
-
- # COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
- # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
- # hash with a '_rev' key
- def copy_doc(doc, dest)
- raise ArgumentError, "_id is required for copying" unless doc['_id']
- slug = escape_docid(doc['_id'])
- destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
- "#{dest['_id']}?rev=#{dest['_rev']}"
- else
- dest
- end
- CouchRest.copy "#{@uri}/#{slug}", destination
- end
-
- ### DEPRECATION NOTICE
- def copy(doc, dest)
- puts "CouchRest::Database's copy method is being deprecated, please use copy_doc instead"
- copy_doc(doc, dest)
- end
-
- # MOVE an existing document to a new id. If the destination id currently exists, a rev must be provided.
- # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
- # hash with a '_rev' key
- def move_doc(doc, dest)
- raise ArgumentError, "_id and _rev are required for moving" unless doc['_id'] && doc['_rev']
- slug = escape_docid(doc['_id'])
- destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
- "#{dest['_id']}?rev=#{dest['_rev']}"
- else
- dest
- end
- CouchRest.move "#{@uri}/#{slug}?rev=#{doc['_rev']}", destination
- end
-
- ### DEPRECATION NOTICE
- def move(doc, dest)
- puts "CouchRest::Database's move method is being deprecated, please use move_doc instead"
- move_doc(doc, dest)
- end
-
- # Compact the database, removing old document revisions and optimizing space use.
- def compact!
- CouchRest.post "#{@uri}/_compact"
- end
-
- # Create the database
- def create!
- bool = server.create_db(@name) rescue false
- bool && true
- end
-
- # Delete and re create the database
- def recreate!
- delete!
- create!
- rescue RestClient::ResourceNotFound
- ensure
- create!
- end
-
- # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
- def replicate_from other_db
- raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
- CouchRest.post "#{@host}/_replicate", :source => other_db.root, :target => name
- end
-
- # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
- def replicate_to other_db
- raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
- CouchRest.post "#{@host}/_replicate", :target => other_db.root, :source => name
- end
-
- # DELETE the database itself. This is not undoable and could be rather
- # catastrophic. Use with care!
- def delete!
- CouchRest.delete @uri
- end
-
- private
-
- def uri_for_attachment doc, name
- if doc.is_a?(String)
- puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
- docid = doc
- rev = nil
- else
- docid = doc['_id']
- rev = doc['_rev']
- end
- docid = escape_docid(docid)
- name = CGI.escape(name)
- rev = "?rev=#{doc['_rev']}" if rev
- "#{@root}/#{docid}/#{name}#{rev}"
- end
-
- def escape_docid id
- /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
- end
-
- def encode_attachments(attachments)
- attachments.each do |k,v|
- next if v['stub']
- v['data'] = base64(v['data'])
- end
- attachments
- end
-
- def base64(data)
- Base64.encode64(data).gsub(/\s/,'')
- end
- end
-end
View
89 lib/couchrest/core/design.rb
@@ -1,89 +0,0 @@
-module CouchRest
- class Design < Document
- def view_by *keys
- opts = keys.pop if keys.last.is_a?(Hash)
- opts ||= {}
- self['views'] ||= {}
- method_name = "by_#{keys.join('_and_')}"
-
- if opts[:map]
- view = {}
- view['map'] = opts.delete(:map)
- if opts[:reduce]
- view['reduce'] = opts.delete(:reduce)
- opts[:reduce] = false
- end
- self['views'][method_name] = view
- else
- doc_keys = keys.collect{|k|"doc['#{k}']"} # this is where :require => 'doc.x == true' would show up
- key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
- guards = opts.delete(:guards) || []
- guards.concat doc_keys
- map_function = <<-JAVASCRIPT
-function(doc) {
- if (#{guards.join(' && ')}) {
- emit(#{key_emit}, null);
- }
-}
-JAVASCRIPT
- self['views'][method_name] = {
- 'map' => map_function
- }
- end
- self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
- method_name
- end
-
- # Dispatches to any named view.
- def view view_name, query={}, &block
- view_name = view_name.to_s
- view_slug = "#{name}/#{view_name}"
- defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
- fetch_view(view_slug, defaults.merge(query), &block)
- end
-
- def name
- id.sub('_design/','') if id
- end
-
- def name= newname
- self['_id'] = "_design/#{newname}"
- end
-
- def save
- raise ArgumentError, "_design docs require a name" unless name && name.length > 0
- super
- end
-
- private
-
- # returns stored defaults if the there is a view named this in the design doc
- def has_view?(view)
- view = view.to_s
- self['views'][view] &&
- (self['views'][view]["couchrest-defaults"]||{})
- end
-
- # def fetch_view_with_docs name, opts, raw=false, &block
- # if raw
- # fetch_view name, opts, &block
- # else
- # begin
- # view = fetch_view name, opts.merge({:include_docs => true}), &block
- # view['rows'].collect{|r|new(r['doc'])} if view['rows']
- # rescue
- # # fallback for old versions of couchdb that don't
- # # have include_docs support
- # view = fetch_view name, opts, &block
- # view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
- # end
- # end
- # end
-
- def fetch_view view_name, opts, &block
- database.view(view_name, opts, &block)
- end
-
- end
-
-end
View
105 lib/couchrest/core/document.rb
@@ -1,105 +0,0 @@
-module CouchRest
- class Response < Hash
- def initialize(keys = {})
- keys.each do |k,v|
- self[k.to_s] = v
- end
- end
- def []= key, value
- super(key.to_s, value)
- end
- def [] key
- super(key.to_s)
- end
- end
-
- class Document < Response
- include CouchRest::Mixins::Views
-
- attr_accessor :database
- @@database = nil
-
- # override the CouchRest::Model-wide default_database
- # This is not a thread safe operation, do not change the model
- # database at runtime.
- def self.use_database(db)
- @@database = db
- end
-
- def self.database
- @@database
- end
-
- def id
- self['_id']
- end
-
- def rev
- self['_rev']
- end
-
- def revs
- self["_revs"]
- end
-
- # copies the document to a new id. If the destination id currently exists, a rev must be provided.
- # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
- # hash with a '_rev' key
- def copy(dest)
- raise ArgumentError, "doc.database required to copy" unless database
- result = database.copy_doc(self, dest)
- result['ok']
- end
-
- # moves the document to a new id. If the destination id currently exists, a rev must be provided.
- # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
- # hash with a '_rev' key
- def move(dest)
- raise ArgumentError, "doc.database required to copy" unless database
- result = database.move_doc(self, dest)
- result['ok']
- end
-
- # Returns the CouchDB uri for the document
- def uri(append_rev = false)
- return nil if new_document?
- couch_uri = "http://#{database.uri}/#{CGI.escape(id)}"
- if append_rev == true
- couch_uri << "?rev=#{rev}"
- elsif append_rev.kind_of?(Integer)
- couch_uri << "?rev=#{append_rev}"
- end
- couch_uri
- end
-
- # Returns the document's database
- def database
- @database || self.class.database
- end
-
- # saves an attachment directly to couchdb
- def put_attachment(name, file, options={})
- raise ArgumentError, "doc must be saved" unless self.rev
- raise ArgumentError, "doc.database required to put_attachment" unless database
- result = database.put_attachment(self, name, file, options)
- self['_rev'] = result['rev']
- result['ok']
- end
-
- # returns an attachment's data
- def fetch_attachment(name)
- raise ArgumentError, "doc must be saved" unless self.rev
- raise ArgumentError, "doc.database required to put_attachment" unless database
- database.fetch_attachment(self, name)
- end
-
- # deletes an attachment directly from couchdb
- def delete_attachment(name)
- raise ArgumentError, "doc.database required to delete_attachment" unless database
- result = database.delete_attachment(self, name)
- self['_rev'] = result['rev']
- result['ok']
- end
- end
-
-end
View
615 lib/couchrest/core/model.rb
@@ -1,615 +0,0 @@
-require 'rubygems'
-begin
- require 'extlib'
-rescue
- puts "CouchRest::Model requires extlib. This is left out of the gemspec on purpose."
- raise
-end
-require 'digest/md5'
-require File.dirname(__FILE__) + '/document'
-require 'mime/types'
-
-# = CouchRest::Model - Document modeling, the CouchDB way
-module CouchRest
- # = CouchRest::Model - Document modeling, the CouchDB way
- #
- # CouchRest::Model provides an ORM-like interface for CouchDB documents. It
- # avoids all usage of <tt>method_missing</tt>, and tries to strike a balance
- # between usability and magic. See CouchRest::Model#view_by for
- # documentation about the view-generation system.
- #
- # ==== Example
- #
- # This is an example class using CouchRest::Model. It is taken from the
- # spec/couchrest/core/model_spec.rb file, which may be even more up to date
- # than this example.
- #
- # class Article < CouchRest::Model
- # use_database CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
- # unique_id :slug
- #
- # view_by :date, :descending => true
- # view_by :user_id, :date
- #
- # view_by :tags,
- # :map =>
- # "function(doc) {
- # if (doc['couchrest-type'] == 'Article' && doc.tags) {
- # doc.tags.forEach(function(tag){
- # emit(tag, 1);
- # });
- # }
- # }",
- # :reduce =>
- # "function(keys, values, rereduce) {
- # return sum(values);
- # }"
- #
- # key_writer :date
- # key_reader :slug, :created_at, :updated_at
- # key_accessor :title, :tags
- #
- # timestamps!
- #
- # before(:create, :generate_slug_from_title)
- # def generate_slug_from_title
- # self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'')
- # end
- # end
- #
- # ==== Examples of finding articles with these views:
- #
- # * All the articles by Barney published in the last 24 hours. Note that we
- # use <tt>{}</tt> as a special value that sorts after all strings,
- # numbers, and arrays.
- #
- # Article.by_user_id_and_date :startkey => ["barney", Time.now - 24 * 3600], :endkey => ["barney", {}]
- #
- # * The most recent 20 articles. Remember that the <tt>view_by :date</tt>
- # has the default option <tt>:descending => true</tt>.
- #
- # Article.by_date :limit => 20
- #
- # * The raw CouchDB view reduce result for the custom <tt>:tags</tt> view.
- # In this case we'll get a count of the number of articles tagged "ruby".
- #
- # Article.by_tags :key => "ruby", :reduce => true
- #
- class Model < Document
-
- # instantiates the hash by converting all the keys to strings.
- def initialize keys = {}
- super(keys)
- apply_defaults
- cast_keys
- unless self['_id'] && self['_rev']
- self['couchrest-type'] = self.class.to_s
- end
- end
-
- # this is the CouchRest::Database that model classes will use unless
- # they override it with <tt>use_database</tt>
- cattr_accessor :default_database
-
- class_inheritable_accessor :casts
- class_inheritable_accessor :default_obj
- class_inheritable_accessor :class_database
- class_inheritable_accessor :design_doc
- class_inheritable_accessor :design_doc_slug_cache
- class_inheritable_accessor :design_doc_fresh
-
- class << self
- # override the CouchRest::Model-wide default_database
- def use_database db
- self.class_database = db
- end
-
- # returns the CouchRest::Database instance that this class uses
- def database
- self.class_database || CouchRest::Model.default_database
- end
-
- # Load a document from the database by id
- def get(id, rev=nil)
- doc = database.get(id, rev)
- new(doc)
- end
-
- # Load all documents that have the "couchrest-type" field equal to the
- # name of the current class. Take the standard set of
- # CouchRest::Database#view options.
- def all opts = {}, &block
- self.design_doc ||= Design.new(default_design_doc)
- unless design_doc_fresh
- refresh_design_doc
- end
- view :all, opts, &block
- end
-
- # Load the first document that have the "couchrest-type" field equal to
- # the name of the current class.
- #
- # ==== Returns
- # Object:: The first object instance available
- # or
- # Nil:: if no instances available
- #
- # ==== Parameters
- # opts<Hash>::
- # View options, see <tt>CouchRest::Database#view</tt> options for more info.
- def first opts = {}
- first_instance = self.all(opts.merge!(:limit => 1))
- first_instance.empty? ? nil : first_instance.first
- end
-
- # Cast a field as another class. The class must be happy to have the
- # field's primitive type as the argument to it's constuctur. Classes
- # which inherit from CouchRest::Model are happy to act as sub-objects
- # for any fields that are stored in JSON as object (and therefore are
- # parsed from the JSON as Ruby Hashes).
- #
- # Example:
- #
- # class Post < CouchRest::Model
- #
- # key_accessor :title, :body, :author
- #
- # cast :author, :as => 'Author'
- #
- # end
- #
- # post.author.class #=> Author
- #
- # Using the same example, if a Post should have many Comments, we
- # would declare it like this:
- #
- # class Post < CouchRest::Model
- #
- # key_accessor :title, :body, :author, comments
- #
- # cast :author, :as => 'Author'
- # cast :comments, :as => ['Comment']
- #
- # end
- #
- # post.author.class #=> Author
- # post.comments.class #=> Array
- # post.comments.first #=> Comment
- #
- def cast field, opts = {}
- self.casts ||= {}
- self.casts[field.to_s] = opts
- end
-
- # Defines methods for reading and writing from fields in the document.
- # Uses key_writer and key_reader internally.
- def key_accessor *keys
- key_writer *keys
- key_reader *keys
- end
-
- # For each argument key, define a method <tt>key=</tt> that sets the
- # corresponding field on the CouchDB document.
- def key_writer *keys
- keys.each do |method|
- key = method.to_s
- define_method "#{method}=" do |value|
- self[key] = value
- end
- end
- end
-
- # For each argument key, define a method <tt>key</tt> that reads the
- # corresponding field on the CouchDB document.
- def key_reader *keys
- keys.each do |method|
- key = method.to_s
- define_method method do
- self[key]
- end
- end
- end
-
- def default
- self.default_obj
- end
-
- def set_default hash
- self.default_obj = hash
- end
-
- # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
- # on the document whenever saving occurs. CouchRest uses a pretty
- # decent time format by default. See Time#to_json
- def timestamps!
- before(:save) do
- self['updated_at'] = Time.now
- self['created_at'] = self['updated_at'] if new_document?
- end
- end
-
- # Name a method that will be called before the document is first saved,
- # which returns a string to be used for the document's <tt>_id</tt>.
- # Because CouchDB enforces a constraint that each id must be unique,
- # this can be used to enforce eg: uniq usernames. Note that this id
- # must be globally unique across all document types which share a
- # database, so if you'd like to scope uniqueness to this class, you
- # should use the class name as part of the unique id.
- def unique_id method = nil, &block
- if method
- define_method :set_unique_id do
- self['_id'] ||= self.send(method)
- end
- elsif block
- define_method :set_unique_id do
- uniqid = block.call(self)
- raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
- self['_id'] ||= uniqid
- end
- end
- end
-
- # Define a CouchDB view. The name of the view will be the concatenation
- # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
- #
- # ==== Example views:
- #
- # class Post
- # # view with default options
- # # query with Post.by_date
- # view_by :date, :descending => true
- #
- # # view with compound sort-keys
- # # query with Post.by_user_id_and_date
- # view_by :user_id, :date
- #
- # # view with custom map/reduce functions
- # # query with Post.by_tags :reduce => true
- # view_by :tags,
- # :map =>
- # "function(doc) {
- # if (doc['couchrest-type'] == 'Post' && doc.tags) {
- # doc.tags.forEach(function(tag){
- # emit(doc.tag, 1);
- # });
- # }
- # }",
- # :reduce =>
- # "function(keys, values, rereduce) {
- # return sum(values);
- # }"
- # end
- #
- # <tt>view_by :date</tt> will create a view defined by this Javascript
- # function:
- #
- # function(doc) {
- # if (doc['couchrest-type'] == 'Post' && doc.date) {
- # emit(doc.date, null);
- # }
- # }
- #
- # It can be queried by calling <tt>Post.by_date</tt> which accepts all
- # valid options for CouchRest::Database#view. In addition, calling with
- # the <tt>:raw => true</tt> option will return the view rows
- # themselves. By default <tt>Post.by_date</tt> will return the
- # documents included in the generated view.
- #
- # CouchRest::Database#view options can be applied at view definition
- # time as defaults, and they will be curried and used at view query
- # time. Or they can be overridden at query time.
- #
- # Custom views can be queried with <tt>:reduce => true</tt> to return
- # reduce results. The default for custom views is to query with
- # <tt>:reduce => false</tt>.
- #
- # Views are generated (on a per-model basis) lazily on first-access.
- # This means that if you are deploying changes to a view, the views for
- # that model won't be available until generation is complete. This can
- # take some time with large databases. Strategies are in the works.
- #
- # To understand the capabilities of this view system more compeletly,
- # it is recommended that you read the RSpec file at
- # <tt>spec/core/model_spec.rb</tt>.
-
- def view_by *keys
- self.design_doc ||= Design.new(default_design_doc)
- opts = keys.pop if keys.last.is_a?(Hash)
- opts ||= {}
- ducktype = opts.delete(:ducktype)
- unless ducktype || opts[:map]
- opts[:guards] ||= []
- opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
- end
- keys.push opts
- self.design_doc.view_by(*keys)
- self.design_doc_fresh = false
- end
-
- def method_missing m, *args
- if has_view?(m)
- query = args.shift || {}
- view(m, query, *args)
- else
- super
- end
- end
-
- # returns stored defaults if the there is a view named this in the design doc
- def has_view?(view)
- view = view.to_s
- design_doc && design_doc['views'] && design_doc['views'][view]
- end
-
- # Dispatches to any named view.
- def view name, query={}, &block
- unless design_doc_fresh
- refresh_design_doc
- end
- query[:raw] = true if query[:reduce]
- raw = query.delete(:raw)
- fetch_view_with_docs(name, query, raw, &block)
- end
-
- def all_design_doc_versions
- database.documents :startkey => "_design/#{self.to_s}-",
- :endkey => "_design/#{self.to_s}-\u9999"
- end
-
- # Deletes any non-current design docs that were created by this class.
- # Running this when you're deployed version of your application is steadily
- # and consistently using the latest code, is the way to clear out old design
- # docs. Running it to early could mean that live code has to regenerate
- # potentially large indexes.
- def cleanup_design_docs!
- ddocs = all_design_doc_versions
- ddocs["rows"].each do |row|
- if (row['id'] != design_doc_id)
- database.delete_doc({
- "_id" => row['id'],
- "_rev" => row['value']['rev']
- })
- end
- end
- end
-
- private
-
- def fetch_view_with_docs name, opts, raw=false, &block
- if raw
- fetch_view name, opts, &block
- else
- begin
- view = fetch_view name, opts.merge({:include_docs => true}), &block
- view['rows'].collect{|r|new(r['doc'])} if view['rows']
- rescue
- # fallback for old versions of couchdb that don't
- # have include_docs support
- view = fetch_view name, opts, &block
- view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
- end
- end
- end
-
- def fetch_view view_name, opts, &block
- retryable = true
- begin
- design_doc.view(view_name, opts, &block)
- # the design doc could have been deleted by a rouge process
- rescue RestClient::ResourceNotFound => e
- if retryable
- refresh_design_doc
- retryable = false
- retry
- else
- raise e
- end
- end
- end
-
- def design_doc_id
- "_design/#{design_doc_slug}"
- end
-
- def design_doc_slug
- return design_doc_slug_cache if design_doc_slug_cache && design_doc_fresh
- funcs = []
- design_doc['views'].each do |name, view|
- funcs << "#{name}/#{view['map']}#{view['reduce']}"
- end
- md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
- self.design_doc_slug_cache = "#{self.to_s}-#{md5}"
- end
-
- def default_design_doc
- {
- "language" => "javascript",
- "views" => {
- 'all' => {
- 'map' => "function(doc) {
- if (doc['couchrest-type'] == '#{self.to_s}') {
- emit(null,null);
- }
- }"
- }
- }
- }
- end
-
- def refresh_design_doc
- did = design_doc_id
- saved = database.get(did) rescue nil
- if saved
- design_doc['views'].each do |name, view|
- saved['views'][name] = view
- end
- database.save(saved)
- self.design_doc = saved
- else
- design_doc['_id'] = did
- design_doc.delete('_rev')
- design_doc.database = database
- design_doc.save
- end
- self.design_doc_fresh = true
- end
-
- end # class << self
-
- # returns the database used by this model's class
- def database
- self.class.database
- end
-
- # Takes a hash as argument, and applies the values by using writer methods
- # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
- # missing. In case of error, no attributes are changed.
- def update_attributes_without_saving hash
- hash.each do |k, v|
- raise NoMethodError, "#{k}= method not available, use key_accessor or key_writer :#{k}" unless self.respond_to?("#{k}=")
- end
- hash.each do |k, v|
- self.send("#{k}=",v)
- end
- end
-
- # Takes a hash as argument, and applies the values by using writer methods
- # for each key. Raises a NoMethodError if the corresponding methods are
- # missing. In case of error, no attributes are changed.
- def update_attributes hash
- update_attributes_without_saving hash
- save
- end
-
- # for compatibility with old-school frameworks
- alias :new_record? :new_document?
-
- # Overridden to set the unique ID.
- # Returns a boolean value
- def save bulk = false
- set_unique_id if new_document? && self.respond_to?(:set_unique_id)
- result = database.save_doc(self, bulk)
- result["ok"] == true
- end
-
- # Saves the document to the db using create or update. Raises an exception
- # if the document is not saved properly.
- def save!
- raise "#{self.inspect} failed to save" unless self.save
- end
-
- # Deletes the document from the database. Runs the :destroy callbacks.
- # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
- # document to be saved to a new <tt>_id</tt>.
- def destroy
- result = database.delete_doc self
- if result['ok']
- self['_rev'] = nil
- self['_id'] = nil
- end
- result['ok']
- end
-
- # creates a file attachment to the current doc
- 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
-
- # reads the data from an attachment
- def read_attachment(attachment_name)
- Base64.decode64(database.fetch_attachment(self.id, attachment_name))
- end
-
- # modifies a file attachment on the current doc
- def update_attachment(args={})
- raise ArgumentError unless args[:file] && args[:name]
- return unless has_attachment?(args[:name])
- delete_attachment(args[:name])
- set_attachment_attr(args)
- rescue ArgumentError => e
- raise ArgumentError, 'You must specify :file and :name'
- end
-
- # deletes a file attachment from the current doc
- def delete_attachment(attachment_name)
- return unless self['_attachments']
- self['_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?)
- end
-
- # returns URL to fetch the attachment from
- def attachment_url(attachment_name)
- return unless has_attachment?(attachment_name)
- "#{database.root}/#{self.id}/#{attachment_name}"
- end
-
- private
-
- def apply_defaults
- return unless new_document?
- if self.class.default
- self.class.default.each do |k,v|
- unless self.key?(k.to_s)
- if v.class == Proc
- self[k.to_s] = v.call
- else
- self[k.to_s] = Marshal.load(Marshal.dump(v))
- end
- end
- end
- end
- end
-
- def cast_keys
- return unless self.class.casts
- # TODO move the argument checking to the cast method for early crashes
- self.class.casts.each do |k,v|
- next unless self[k]
- target = v[:as]
- v[:send] || 'new'
- if target.is_a?(Array)
- klass = ::Extlib::Inflection.constantize(target[0])
- self[k] = self[k].collect do |value|
- (!v[:send] && klass == Time) ? Time.parse(value) : klass.send((v[:send] || 'new'), value)
- end
- else
- self[k] = if (!v[:send] && target == 'Time')
- Time.parse(self[k])
- else
- ::Extlib::Inflection.constantize(target).send((v[:send] || 'new'), self[k])
- end
- end
- end
- end
-
- def encode_attachment(data)
- Base64.encode64(data).gsub(/\r|\n/,'')
- end
-
- def get_mime_type(file)
- MIME::Types.type_for(file.path).empty? ?
- 'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
- end
-
- def set_attachment_attr(args)
- content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
- self['_attachments'][args[:name]] = {
- 'content-type' => content_type,
- 'data' => encode_attachment(args[:file].read)
- }
- end
-
- include ::Extlib::Hook
- register_instance_hooks :save, :destroy
-
- end # class Model
-end # module CouchRest
View
88 lib/couchrest/core/server.rb
@@ -1,88 +0,0 @@
-module CouchRest
- class Server
- attr_accessor :uri, :uuid_batch_count, :available_databases
- def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
- @uri = server
- @uuid_batch_count = uuid_batch_count
- end
-
- # Lists all "available" databases.
- # An available database, is a database that was specified
- # as avaiable by your code.
- # It allows to define common databases to use and reuse in your code
- def available_databases
- @available_databases ||= {}
- end
-
- # Adds a new available database and create it unless it already exists
- #
- # Example:
- #
- # @couch = CouchRest::Server.new
- # @couch.define_available_database(:default, "tech-blog")
- #
- def define_available_database(reference, db_name, create_unless_exists = true)
- available_databases[reference.to_sym] = create_unless_exists ? database!(db_name) : database(db_name)
- end
-
- # Checks that a database is set as available
- #
- # Example:
- #
- # @couch.available_database?(:default)
- #
- def available_database?(ref_or_name)
- ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(ref_or_name)
- end
-
- def default_database=(name, create_unless_exists = true)
- define_available_database(:default, name, create_unless_exists = true)
- end
-
- def default_database
- available_databases[:default]
- end
-
- # Lists all databases on the server
- def databases
- CouchRest.get "#{@uri}/_all_dbs"
- end
-
- # Returns a CouchRest::Database for the given name
- def database(name)
- CouchRest::Database.new(self, name)
- end
-
- # Creates the database if it doesn't exist
- def database!(name)
- create_db(name) rescue nil
- database(name)
- end
-
- # GET the welcome message
- def info
- CouchRest.get "#{@uri}/"
- end
-
- # Create a database
- def create_db(name)
- CouchRest.put "#{@uri}/#{name}"
- database(name)
- end
-
- # Restart the CouchDB instance
- def restart!
- CouchRest.post "#{@uri}/_restart"
- end
-
- # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
- def next_uuid(count = @uuid_batch_count)
- @uuids ||= []
- if @uuids.empty?
- @uuids = CouchRest.post("#{@uri}/_uuids?count=#{count}")["uuids"]
- end
- @uuids.pop
- end
-
- end
-end
View
4 lib/couchrest/core/view.rb
@@ -1,4 +0,0 @@
-module CouchRest
- class View
- end
-end
View
103 lib/couchrest/helper/pager.rb
@@ -1,103 +0,0 @@
-module CouchRest
- class Pager
- attr_accessor :db
- def initialize db
- @db = db
- end
-
- def all_docs(limit=100, &block)
- startkey = nil
- oldend = nil
-
- while docrows = request_all_docs(limit+1, startkey)
- startkey = docrows.last['key']
- docrows.pop if docrows.length > limit
- if oldend == startkey
- break
- end
- yield(docrows)
- oldend = startkey
- end
- end
-
- def key_reduce(view, limit=2000, firstkey = nil, lastkey = nil, &block)
- # start with no keys
- startkey = firstkey
- # lastprocessedkey = nil
- keepgoing = true
-
- while keepgoing && viewrows = request_view(view, limit, startkey)
- startkey = viewrows.first['key']
- endkey = viewrows.last['key']
-
- if (startkey == endkey)
- # we need to rerequest to get a bigger page
- # so we know we have all the rows for that key
- viewrows = @db.view(view, :key => startkey)['rows']
- # we need to do an offset thing to find the next startkey
- # otherwise we just get stuck
- lastdocid = viewrows.last['id']
- fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :limit => 2)['rows']
-
- newendkey = fornextloop.last['key']
- if (newendkey == endkey)
- keepgoing = false
- else
- startkey = newendkey
- end
- rows = viewrows
- else
- rows = []
- for r in viewrows
- if (lastkey && r['key'] == lastkey)
- keepgoing = false
- break
- end
- break if (r['key'] == endkey)
- rows << r
- end
- startkey = endkey
- end
-
- key = :begin
- values = []
-
- rows.each do |r|
- if key != r['key']
- # we're on a new key, yield the old first and then reset
- yield(key, values) if key != :begin
- key = r['key']
- values = []
- end
- # keep accumulating
- values << r['value']
- end
- yield(key, values)
-
- end
- end
-
- private
-
- def request_all_docs limit, startkey = nil
- opts = {}
- opts[:limit] = limit if limit
- opts[:startkey] = startkey if startkey
- results = @db.documents(opts)
- rows = results['rows']
- rows unless rows.length == 0
- end
-
- def request_view view, limit = nil, startkey = nil, endkey = nil
- opts = {}
- opts[:limit] = limit if limit
- opts[:startkey] = startkey if startkey
- opts[:endkey] = endkey if endkey
-
- results = @db.view(view, opts)
- rows = results['rows']
- rows unless rows.length == 0
- end
-
- end
-end
View
44 lib/couchrest/helper/streamer.rb
@@ -1,44 +0,0 @@
-module CouchRest
- class Streamer
- attr_accessor :db
- def initialize db
- @db = db
- end
-
- # Stream a view, yielding one row at a time. Shells out to <tt>curl</tt> to keep RAM usage low when you have millions of rows.
- def view name, params = nil, &block
- urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}"
- url = CouchRest.paramify_url urlst, params
- # puts "stream #{url}"
- first = nil
- IO.popen("curl --silent #{url}") do |view|
- first = view.gets # discard header
- while line = view.gets
- row = parse_line(line)
- block.call row
- end
- end
- parse_first(first)
- end
-
- private
-
- def parse_line line
- return nil unless line
- if /(\{.*\}),?/.match(line.chomp)
- JSON.parse($1)
- end
- end
-
- def parse_first first
- return nil unless first
- parts = first.split(',')
- parts.pop
- line = parts.join(',')
- JSON.parse("#{line}}")
- rescue
- nil
- end
-
- end
-end
View
3  lib/couchrest/mixins.rb
@@ -1,3 +0,0 @@
-mixins_dir = File.join(File.dirname(__FILE__), 'mixins')
-
-require File.join(mixins_dir, 'views')
View
63 lib/couchrest/mixins/design_doc.rb
@@ -1,63 +0,0 @@
-require 'digest/md5'
-
-module CouchRest
- module Mixins
- module DesignDoc
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- module ClassMethods
- def design_doc_id
- "_design/#{design_doc_slug}"
- end
-
- def design_doc_slug
- return design_doc_slug_cache if design_doc_slug_cache && design_doc_fresh
- funcs = []
- design_doc['views'].each do |name, view|
- funcs << "#{name}/#{view['map']}#{view['reduce']}"
- end
- md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
- self.design_doc_slug_cache = "#{self.to_s}-#{md5}"
- end
-
- def default_design_doc
- {
- "language" => "javascript",
- "views" => {
- 'all' => {
- 'map' => "function(doc) {
- if (doc['couchrest-type'] == '#{self.to_s}') {
- emit(null,null);
- }
- }"
- }
- }
- }
- end
-
- def refresh_design_doc
- did = design_doc_id
- saved = database.get(did) rescue nil
- if saved
- design_doc['views'].each do |name, view|
- saved['views'][name] = view
- end
- database.save_doc(saved)
- self.design_doc = saved
- else
- design_doc['_id'] = did
- design_doc.delete('_rev')
- design_doc.database = database
- design_doc.save
- end
- self.design_doc_fresh = true
- end
-
- end # module ClassMethods
-
- end
- end
-end
View
48 lib/couchrest/mixins/document_queries.rb
@@ -1,48 +0,0 @@
-module CouchRest
- module Mixins
- module DocumentQueries
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- module ClassMethods
-
- # Load all documents that have the "couchrest-type" field equal to the
- # name of the current class. Take the standard set of
- # CouchRest::Database#view options.
- def all(opts = {}, &block)
- self.design_doc ||= Design.new(default_design_doc)
- unless design_doc_fresh
- refresh_design_doc
- end
- view :all, opts, &block
- end
-
- # Load the first document that have the "couchrest-type" field equal to
- # the name of the current class.
- #
- # ==== Returns
- # Object:: The first object instance available
- # or
- # Nil:: if no instances available
- #
- # ==== Parameters
- # opts<Hash>::
- # View options, see <tt>CouchRest::Database#view</tt> options for more info.
- def first(opts = {})
- first_instance = self.all(opts.merge!(:limit => 1))
- first_instance.empty? ? nil : first_instance.first
- end
-
- # Load a document from the database by id
- def get(id)
- doc = database.get id
- new(doc)
- end
-
- end
-
- end
- end
-end
View
4 lib/couchrest/mixins/extended_document_mixins.rb
@@ -1,4 +0,0 @@
-require File.join(File.dirname(__FILE__), 'properties')
-require File.join(File.dirname(__FILE__), 'document_queries')
-require File.join(File.dirname(__FILE__), 'extended_views')
-require File.join(File.dirname(__FILE__), 'design_doc')
View
169 lib/couchrest/mixins/extended_views.rb
@@ -1,169 +0,0 @@
-module CouchRest
- module Mixins
- module ExtendedViews
-
- def self.included(base)
- base.extend(ClassMethods)
- # extlib is required for the following code
- base.send(:class_inheritable_accessor, :design_doc)
- base.send(:class_inheritable_accessor, :design_doc_slug_cache)
- base.send(:class_inheritable_accessor, :design_doc_fresh)
- end
-
- module ClassMethods
-
- # Define a CouchDB view. The name of the view will be the concatenation
- # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
- #
- # ==== Example views:
- #
- # class Post
- # # view with default options
- # # query with Post.by_date
- # view_by :date, :descending => true
- #
- # # view with compound sort-keys
- # # query with Post.by_user_id_and_date
- # view_by :user_id, :date
- #
- # # view with custom map/reduce functions
- # # query with Post.by_tags :reduce => true
- # view_by :tags,
- # :map =>
- # "function(doc) {
- # if (doc['couchrest-type'] == 'Post' && doc.tags) {
- # doc.tags.forEach(function(tag){
- # emit(doc.tag, 1);
- # });
- # }
- # }",
- # :reduce =>
- # "function(keys, values, rereduce) {
- # return sum(values);
- # }"
- # end
- #
- # <tt>view_by :date</tt> will create a view defined by this Javascript
- # function:
- #
- # function(doc) {
- # if (doc['couchrest-type'] == 'Post' && doc.date) {
- # emit(doc.date, null);
- # }
- # }
- #
- # It can be queried by calling <tt>Post.by_date</tt> which accepts all
- # valid options for CouchRest::Database#view. In addition, calling with
- # the <tt>:raw => true</tt> option will return the view rows
- # themselves. By default <tt>Post.by_date</tt> will return the
- # documents included in the generated view.
- #
- # CouchRest::Database#view options can be applied at view definition
- # time as defaults, and they will be curried and used at view query
- # time. Or they can be overridden at query time.
- #
- # Custom views can be queried with <tt>:reduce => true</tt> to return
- # reduce results. The default for custom views is to query with
- # <tt>:reduce => false</tt>.
- #
- # Views are generated (on a per-model basis) lazily on first-access.
- # This means that if you are deploying changes to a view, the views for
- # that model won't be available until generation is complete. This can
- # take some time with large databases. Strategies are in the works.
- #
- # To understand the capabilities of this view system more compeletly,
- # it is recommended that you read the RSpec file at
- # <tt>spec/core/model_spec.rb</tt>.
-
- def view_by(*keys)
- self.design_doc ||= Design.new(default_design_doc)
- opts = keys.pop if keys.last.is_a?(Hash)
- opts ||= {}
- ducktype = opts.delete(:ducktype)
- unless ducktype || opts[:map]
- opts[:guards] ||= []
- opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
- end
- keys.push opts
- self.design_doc.view_by(*keys)
- self.design_doc_fresh = false
- end
-
- # returns stored defaults if the there is a view named this in the design doc
- def has_view?(view)
- view = view.to_s
- design_doc && design_doc['views'] && design_doc['views'][view]
- end
-
- # Dispatches to any named view.
- def view name, query={}, &block
- unless design_doc_fresh
- refresh_design_doc
- end
- query[:raw] = true if query[:reduce]
- raw = query.delete(:raw)
- fetch_view_with_docs(name, query, raw, &block)
- end
-
- def all_design_doc_versions
- database.documents :startkey => "_design/#{self.to_s}-",
- :endkey => "_design/#{self.to_s}-\u9999"
- end
-
- # Deletes any non-current design docs that were created by this class.
- # Running this when you're deployed version of your application is steadily
- # and consistently using the latest code, is the way to clear out old design
- # docs. Running it to early could mean that live code has to regenerate
- # potentially large indexes.
- def cleanup_design_docs!
- ddocs = all_design_doc_versions
- ddocs["rows"].each do |row|
- if (row['id'] != design_doc_id)
- database.delete_doc({
- "_id" => row['id'],
- "_rev" => row['value']['rev']
- })
- end
- end
- end
-
- private
-
- def fetch_view_with_docs name, opts, raw=false, &block
- if raw
- fetch_view name, opts, &block
- else
- begin
- view = fetch_view name, opts.merge({:include_docs => true}), &block
- view['rows'].collect{|r|new(r['doc'])} if view['rows']
- rescue
- # fallback for old versions of couchdb that don't
- # have include_docs support
- view = fetch_view name, opts, &block
- view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
- end
- end
- end
-
- def fetch_view view_name, opts, &block
- retryable = true
- begin
- design_doc.view(view_name, opts, &block)
- # the design doc could have been deleted by a rouge process
- rescue RestClient::ResourceNotFound => e
- if retryable
- refresh_design_doc
- retryable = false
- retry
- else
- raise e
- end
- end
- end
-
- end # module ClassMethods
-
-
- end
- end
-end
View
63 lib/couchrest/mixins/properties.rb
@@ -1,63 +0,0 @@
-module CouchRest
- module Mixins
- module DocumentProperties
-
- def self.included(base)
- base.extend(ClassMethods)
- end
-
- module ClassMethods
- # Stores the class properties
- def properties
- @@properties ||= []
- end
-
- # This is not a thread safe operation, if you have to set new properties at runtime
- # make sure to use a mutex.
- def property(name, options={})
- unless properties.map{|p| p.name}.include?(name.to_s)
- property = CouchRest::Property.new(name, options.delete(:type), options)
- create_property_getter(property)
- create_property_setter(property) unless property.read_only == true
- properties << property
- end
- end
-
- protected
- # defines the getter for the property
- def create_property_getter(property)
- meth = property.name
- class_eval <<-EOS
- def #{meth}
- self['#{meth}']
- end
- EOS
-
- if property.alias
- class_eval <<-EOS
- alias #{property.alias.to_sym} #{meth.to_sym}
- EOS
- end
- end
-
- # defines the setter for the property
- def create_property_setter(property)
- meth = property.name
- class_eval <<-EOS
- def #{meth}=(value)
- self['#{meth}'] = value
- end
- EOS
-
- if property.alias
- class_eval <<-EOS
- alias #{property.alias.to_sym}= #{meth.to_sym}=
- EOS
- end
- end
-
- end # module ClassMethods
-
- end
- end
-end
View
59 lib/couchrest/mixins/views.rb
@@ -1,59 +0,0 @@
-module CouchRest
- module Mixins
- module Views
-
- # alias for self['_id']
- def id
- self['_id']
- end
-
- # alias for self['_rev']
- def rev
- self['_rev']
- end
-
- # returns true if the document has never been saved
- def new_document?
- !rev
- end
-
- # Saves the document to the db using create or update. Also runs the :save
- # callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
- # CouchDB's response.
- # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
- def save(bulk = false)
- raise ArgumentError, "doc.database required for saving" unless database
- result = database.save_doc self, bulk
- result['ok']
- end
-
- # Deletes the document from the database. Runs the :delete callbacks.
- # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
- # document to be saved to a new <tt>_id</tt>.
- # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
- # actually be deleted from the db until bulk save.
- def destroy(bulk = false)
- raise ArgumentError, "doc.database required to destroy" unless database
- result = database.delete_doc(self, bulk)
- if result['ok']
- self['_rev'] = nil
- self['_id'] = nil
- end
- result['ok']
- end
-
- def copy(dest)
- raise ArgumentError, "doc.database required to copy" unless database
- result = database.copy_doc(self, dest)
- result['ok']
- end
-
- def move(dest)
- raise ArgumentError, "doc.database required to copy" unless database
- result = database.move_doc(self, dest)
- result['ok']
- end
-
- end
- end
-end
View
99 lib/couchrest/monkeypatches.rb
@@ -1,99 +0,0 @@
-# This file must be loaded after the JSON gem and any other library that beats up the Time class.
-class Time
- # This date format sorts lexicographically
- # and is compatible with Javascript's <tt>new Date(time_string)</tt> constructor.
- # Note this this format stores all dates in UTC so that collation
- # order is preserved. (There's no longer a need to set <tt>ENV['TZ'] = 'UTC'</tt>
- # in your application.)
-
- def to_json(options = nil)
- u = self.utc
- %("#{u.strftime("%Y/%m/%d %H:%M:%S +0000")}")
- end
-
- # Decodes the JSON time format to a UTC time.
- # Based on Time.parse from ActiveSupport. ActiveSupport's version
- # is more complete, returning a time in your current timezone,
- # rather than keeping the time in UTC. YMMV.
- # def self.parse string, fallback=nil
- # d = DateTime.parse(string).new_offset
- # self.utc(d.year, d.month, d.day, d.hour, d.min, d.sec)
- # rescue
- # fallback
- # end
-end
-
-# Monkey patch for faster net/http io
-if RUBY_VERSION.to_f < 1.9
- class Net::BufferedIO #:nodoc:
- alias :old_rbuf_fill :rbuf_fill
- def rbuf_fill
- begin
- @rbuf << @io.read_nonblock(65536)
- rescue Errno::EWOULDBLOCK
- if IO.select([@io], nil, nil, @read_timeout)
- @rbuf << @io.read_nonblock(65536)
- else
- raise Timeout::TimeoutError
- end
- end
- end
- end
-end
-
-module RestClient
- def self.copy(url, headers={})
- Request.execute(:method => :copy,
- :url => url,
- :headers => headers)
- end
-
- def self.move(url, headers={})
- Request.execute(:method => :move,
- :url => url,
- :headers => headers)
- end
-
- class Request
- def transmit(uri, req, payload)
- setup_credentials(req)
-
- Thread.current[:host] ||= uri.host
- Thread.current[:port] ||= uri.port
-
- net = net_http_class.new(uri.host, uri.port)
-
- if Thread.current[:connection].nil? || Thread.current[:host] != uri.host
- Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
- net.use_ssl = uri.is_a?(URI::HTTPS)
- net.verify_mode = OpenSSL::SSL::VERIFY_NONE
- Thread.current[:connection] = net
- Thread.current[:connection].start
- end
-
- display_log request_log
- http = Thread.current[:connection]
-
- http.read_timeout = @timeout if @timeout
- begin
- res = http.request(req, payload)
- rescue
- # p "Net::HTTP connection failed, reconnecting"
- Thread.current[:connection].finish
- http = Thread.current[:connection] = net
- Thread.current[:connection].start
- res = http.request(req, payload)
- display_log response_log(res)
- process_result res
- else
- display_log response_log(res)
- process_result res
- end
-
- rescue EOFError
- raise RestClient::ServerBrokeConnection
- rescue Timeout::Error
- raise RestClient::RequestTimeout
- end
- end
-end
View
114 lib/couchrest/more/extended_document.rb
@@ -1,114 +0,0 @@
-require 'rubygems'
-begin
- gem 'extlib'
- require 'extlib'
-rescue
- puts "CouchRest::Model requires extlib. This is left out of the gemspec on purpose."
- raise
-end
-require 'mime/types'
-require File.join(File.dirname(__FILE__), "property")
-require File.join(File.dirname(__FILE__), '..', 'mixins', 'extended_document_mixins')
-
-module CouchRest
-
- # Same as CouchRest::Document but with properties and validations
- class ExtendedDocument < Document
- include CouchRest::Mixins::DocumentQueries
- include CouchRest::Mixins::DocumentProperties
- include CouchRest::Mixins::ExtendedViews
- include CouchRest::Mixins::DesignDoc
-
-
- # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
- # on the document whenever saving occurs. CouchRest uses a pretty
- # decent time format by default. See Time#to_json
- def self.timestamps!
- before(:save) do
- self['updated_at'] = Time.now
- self['created_at'] = self['updated_at'] if new_document?
- end
- end
-
- # Name a method that will be called before the document is first saved,
- # which returns a string to be used for the document's <tt>_id</tt>.
- # Because CouchDB enforces a constraint that each id must be unique,
- # this can be used to enforce eg: uniq usernames. Note that this id
- # must be globally unique across all document types which share a
- # database, so if you'd like to scope uniqueness to this class, you
- # should use the class name as part of the unique id.
- def self.unique_id method = nil, &block
- if method
- define_method :set_unique_id do
- self['_id'] ||= self.send(method)
- end
- elsif block
- define_method :set_unique_id do
- uniqid = block.call(self)
- raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
- self['_id'] ||= uniqid
- end
- end
- end
-
- ### instance methods
-
- # Returns the Class properties
- #
- # ==== Returns
- # Array:: the list of properties for the instance
- def properties
- self.class.properties
- end
-
- # Takes a hash as argument, and applies the values by using writer methods
- # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
- # missing. In case of error, no attributes are changed.
- def update_attributes_without_saving(hash)
- hash.each do |k, v|
- raise NoMethodError, "#{k}= method not available, use key_accessor or key_writer :#{k}" unless self.respond_to?("#{k}=")
- end
- hash.each do |k, v|
- self.send("#{k}=",v)
- end
- end
-
- # Takes a hash as argument, and applies the values by using writer methods
- # for each key. Raises a NoMethodError if the corresponding methods are
- # missing. In case of error, no attributes are changed.
- def update_attributes(hash)
- update_attributes_without_saving hash
- save
- end
-
- # for compatibility with old-school frameworks
- alias :new_record? :new_document?
-
- # Overridden to set the unique ID.
- # Returns a boolean value
- def save(bulk = false)
- set_unique_id if new_document? && self.respond_to?(:set_unique_id)
- result = database.save_doc(self, bulk)
- result["ok"] == true
- end
-
- # Saves the document to the db using create or update. Raises an exception
- # if the document is not saved properly.
- def save!
- raise "#{self.inspect} failed to save" unless self.save
- end
-
- # Deletes the document from the database. Runs the :destroy callbacks.
- # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
- # document to be saved to a new <tt>_id</tt>.
- def destroy
- result = database.delete_doc self
- if result['ok']
- self['_rev'] = nil
- self['_id'] = nil
- end
- result['ok']
- end
-
- end
-end
View
26 lib/couchrest/more/property.rb
@@ -1,26 +0,0 @@
-module CouchRest
-
- # Basic attribute support adding getter/setter + validation
- class Property
- attr_reader :name, :type, :validation_format, :required, :read_only, :alias
-
- # attribute to define
- def initialize(name, type = String, options = {})
- @name = name.to_s
- @type = type
- parse_options(options)
- self
- end
-
-
- private
- def parse_options(options)
- return if options.empty?
- @required = true if (options[:required] && (options[:required] == true))
- @validation_format = options[:format] if options[:format]
- @read_only = options[:read_only] if options[:read_only]
- @alias = options[:alias] if options
- end
-
- end
-end
Please sign in to comment.
Something went wrong with that request. Please try again.