couch_potato-rails2 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGES.md +148 -0
  4. data/CREDITS +6 -0
  5. data/Gemfile +4 -0
  6. data/MIT-LICENSE.txt +19 -0
  7. data/README.md +450 -0
  8. data/Rakefile +82 -0
  9. data/couch_potato.gemspec +27 -0
  10. data/init.rb +3 -0
  11. data/lib/core_ext/date.rb +14 -0
  12. data/lib/core_ext/object.rb +5 -0
  13. data/lib/core_ext/string.rb +12 -0
  14. data/lib/core_ext/symbol.rb +15 -0
  15. data/lib/core_ext/time.rb +23 -0
  16. data/lib/couch_potato.rb +48 -0
  17. data/lib/couch_potato/database.rb +179 -0
  18. data/lib/couch_potato/persistence.rb +124 -0
  19. data/lib/couch_potato/persistence/active_model_compliance.rb +44 -0
  20. data/lib/couch_potato/persistence/attachments.rb +31 -0
  21. data/lib/couch_potato/persistence/callbacks.rb +29 -0
  22. data/lib/couch_potato/persistence/dirty_attributes.rb +56 -0
  23. data/lib/couch_potato/persistence/ghost_attributes.rb +12 -0
  24. data/lib/couch_potato/persistence/json.rb +47 -0
  25. data/lib/couch_potato/persistence/magic_timestamps.rb +23 -0
  26. data/lib/couch_potato/persistence/properties.rb +79 -0
  27. data/lib/couch_potato/persistence/simple_property.rb +82 -0
  28. data/lib/couch_potato/persistence/type_caster.rb +40 -0
  29. data/lib/couch_potato/railtie.rb +25 -0
  30. data/lib/couch_potato/rspec.rb +2 -0
  31. data/lib/couch_potato/rspec/matchers.rb +39 -0
  32. data/lib/couch_potato/rspec/matchers/json2.js +482 -0
  33. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +54 -0
  34. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +49 -0
  35. data/lib/couch_potato/rspec/matchers/print_r.js +60 -0
  36. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +50 -0
  37. data/lib/couch_potato/rspec/stub_db.rb +46 -0
  38. data/lib/couch_potato/validation.rb +16 -0
  39. data/lib/couch_potato/validation/with_active_model.rb +27 -0
  40. data/lib/couch_potato/validation/with_validatable.rb +41 -0
  41. data/lib/couch_potato/version.rb +3 -0
  42. data/lib/couch_potato/view/base_view_spec.rb +84 -0
  43. data/lib/couch_potato/view/custom_view_spec.rb +42 -0
  44. data/lib/couch_potato/view/custom_views.rb +52 -0
  45. data/lib/couch_potato/view/lists.rb +23 -0
  46. data/lib/couch_potato/view/model_view_spec.rb +75 -0
  47. data/lib/couch_potato/view/properties_view_spec.rb +47 -0
  48. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  49. data/lib/couch_potato/view/view_query.rb +82 -0
  50. data/rails/init.rb +4 -0
  51. data/rails/reload_classes.rb +47 -0
  52. data/spec/attachments_spec.rb +23 -0
  53. data/spec/callbacks_spec.rb +297 -0
  54. data/spec/create_spec.rb +35 -0
  55. data/spec/custom_view_spec.rb +239 -0
  56. data/spec/default_property_spec.rb +38 -0
  57. data/spec/destroy_spec.rb +29 -0
  58. data/spec/fixtures/address.rb +10 -0
  59. data/spec/fixtures/person.rb +6 -0
  60. data/spec/property_spec.rb +323 -0
  61. data/spec/rails_spec.rb +50 -0
  62. data/spec/railtie_spec.rb +65 -0
  63. data/spec/spec.opts +2 -0
  64. data/spec/spec_helper.rb +44 -0
  65. data/spec/unit/active_model_compliance_spec.rb +98 -0
  66. data/spec/unit/attributes_spec.rb +135 -0
  67. data/spec/unit/base_view_spec_spec.rb +106 -0
  68. data/spec/unit/callbacks_spec.rb +46 -0
  69. data/spec/unit/couch_potato_spec.rb +39 -0
  70. data/spec/unit/create_spec.rb +69 -0
  71. data/spec/unit/custom_views_spec.rb +15 -0
  72. data/spec/unit/database_spec.rb +317 -0
  73. data/spec/unit/date_spec.rb +22 -0
  74. data/spec/unit/dirty_attributes_spec.rb +136 -0
  75. data/spec/unit/initialize_spec.rb +38 -0
  76. data/spec/unit/json_spec.rb +30 -0
  77. data/spec/unit/lists_spec.rb +20 -0
  78. data/spec/unit/model_view_spec_spec.rb +13 -0
  79. data/spec/unit/properties_view_spec_spec.rb +31 -0
  80. data/spec/unit/rspec_matchers_spec.rb +124 -0
  81. data/spec/unit/rspec_stub_db_spec.rb +35 -0
  82. data/spec/unit/string_spec.rb +7 -0
  83. data/spec/unit/time_spec.rb +15 -0
  84. data/spec/unit/validation_spec.rb +67 -0
  85. data/spec/unit/view_query_spec.rb +86 -0
  86. data/spec/update_spec.rb +40 -0
  87. data/spec/view_updates_spec.rb +28 -0
  88. metadata +243 -0
@@ -0,0 +1,82 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require "rspec/core/rake_task"
6
+ require 'rake/rdoctask'
7
+
8
+ def with_validatable(&block)
9
+ begin
10
+ require 'validatable'
11
+
12
+ ENV['VALIDATION_FRAMEWORK'] = 'validatable'
13
+ puts "Running task with Validatable validation framework."
14
+ yield block
15
+ rescue LoadError
16
+ STDERR.puts "WARNING: Validatable not available, skipping task."
17
+ end
18
+ end
19
+
20
+ def with_active_model(&block)
21
+ begin
22
+ require 'active_model'
23
+
24
+ ENV['VALIDATION_FRAMEWORK'] = 'active_model'
25
+ puts "Running task with ActiveModel validation framework."
26
+ yield block
27
+ rescue LoadError
28
+ STDERR.puts "WARNING: ActiveModel not available, skipping task."
29
+ end
30
+ end
31
+
32
+ task :default => :spec
33
+
34
+ task :spec_functional_validatable do
35
+ with_validatable { Rake::Task['spec_functional_default'].execute }
36
+ end
37
+
38
+ task :spec_functional_active_model do
39
+ with_active_model { Rake::Task['spec_functional_default'].execute }
40
+ end
41
+
42
+ task :spec_unit_validatable do
43
+ with_validatable { Rake::Task['spec_unit_default'].execute }
44
+ end
45
+
46
+ task :spec_unit_active_model do
47
+ with_active_model { Rake::Task['spec_unit_default'].execute }
48
+ end
49
+
50
+ desc "Run functional specs with default validation framework, override with VALIDATION_FRAMEWORK"
51
+ RSpec::Core::RakeTask.new(:spec_functional_default) do |spec|
52
+ spec.pattern = 'spec/*_spec.rb'
53
+ spec.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
54
+ end
55
+
56
+ desc "Run unit specs with default validation framework, override with VALIDATION_FRAMEWORK"
57
+ RSpec::Core::RakeTask.new(:spec_unit_default) do |spec|
58
+ spec.pattern = 'spec/unit/*_spec.rb'
59
+ spec.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
60
+ end
61
+
62
+ desc "Run functional specs with all validation frameworks"
63
+ task :spec_functional => [:spec_functional_validatable, :spec_functional_active_model] do
64
+ end
65
+
66
+ desc "Run unit specs with all validation frameworks"
67
+ task :spec_unit => [:spec_unit_validatable, :spec_unit_active_model] do
68
+ end
69
+
70
+ desc "Run all specs"
71
+ task :spec => [:spec_unit, :spec_functional] do
72
+ end
73
+
74
+ desc 'Generate documentation'
75
+ Rake::RDocTask.new(:rdoc) do |rdoc|
76
+ rdoc.rdoc_dir = 'rdoc'
77
+ rdoc.title = 'Couch Potato'
78
+ rdoc.options << '--line-numbers' << '--inline-source'
79
+ rdoc.rdoc_files.include('README.md')
80
+ rdoc.rdoc_files.include('lib/couch_potato.rb')
81
+ rdoc.rdoc_files.include('lib/couch_potato/**/*.rb')
82
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "couch_potato/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "couch_potato-rails2"
7
+ s.summary = %Q{Ruby persistence layer for CouchDB}
8
+ s.email = "alex@upstre.am"
9
+ s.homepage = "http://github.com/langalex/couch_potato"
10
+ s.description = "Ruby persistence layer for CouchDB"
11
+ s.authors = ["Alexander Lang"]
12
+ s.version = CouchPotato::VERSION
13
+ s.platform = Gem::Platform::RUBY
14
+
15
+ s.add_dependency 'json'
16
+ s.add_dependency 'couchrest', '>=1.0.1'
17
+
18
+ s.add_development_dependency 'rspec', '>=2.0'
19
+ s.add_development_dependency 'timecop'
20
+ s.add_development_dependency 'tzinfo'
21
+ s.add_development_dependency 'rake'
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # this is for rails only
2
+
3
+ require File.dirname(__FILE__) + '/rails/init'
@@ -0,0 +1,14 @@
1
+ class Date
2
+ def to_json(*a)
3
+ %("#{as_json}")
4
+ end
5
+
6
+ def as_json(*args)
7
+ strftime("%Y/%m/%d")
8
+ end
9
+
10
+ def self.json_create string
11
+ return nil if string.nil?
12
+ Date.parse(string)
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ Object.class_eval do
2
+ def try(method, *args)
3
+ self.send method, *args if self.respond_to?(method)
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveSupportMethods
2
+ def camelize
3
+ sub(/^([a-z])/) {$1.upcase}.gsub(/_([a-z])/) do
4
+ $1.upcase
5
+ end
6
+ end
7
+
8
+ def blank?
9
+ empty?
10
+ end
11
+ end
12
+ String.send :include, ActiveSupportMethods unless String.new.respond_to?(:underscore)
@@ -0,0 +1,15 @@
1
+ # taken from ActiveSupport 2.3.2
2
+ unless :to_proc.respond_to?(:to_proc)
3
+ class Symbol
4
+ # Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
5
+ #
6
+ # # The same as people.collect { |p| p.name }
7
+ # people.collect(&:name)
8
+ #
9
+ # # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
10
+ # people.select(&:manager?).collect(&:salary)
11
+ def to_proc
12
+ Proc.new { |*args| args.shift.__send__(self, *args) }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_support/time'
2
+
3
+ class Time
4
+ def to_json(*a)
5
+ %("#{as_json}")
6
+ end
7
+
8
+ def as_json(*args)
9
+ getutc.strftime("%Y/%m/%d %H:%M:%S +0000")
10
+ end
11
+
12
+ def self.json_create string
13
+ return nil if string.nil?
14
+ d = DateTime.parse(string.to_s).new_offset
15
+ self.utc(d.year, d.month, d.day, d.hour, d.min, d.sec).in_time_zone
16
+ end
17
+ end
18
+
19
+ ActiveSupport::TimeWithZone.class_eval do
20
+ def as_json(*args)
21
+ utc.as_json
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ require 'couchrest'
2
+ require 'json'
3
+ require 'json/add/core'
4
+ require 'json/add/rails'
5
+
6
+ require 'ostruct'
7
+
8
+ JSON.create_id = 'ruby_class'
9
+
10
+ module CouchPotato
11
+ Config = Struct.new(:database_name, :validation_framework, :split_design_documents_per_view).new
12
+ Config.validation_framework = :active_model
13
+ Config.split_design_documents_per_view = false
14
+
15
+ class NotFound < StandardError; end
16
+
17
+ # Returns a database instance which you can then use to create objects and query views. You have to set the CouchPotato::Config.database_name before this works.
18
+ def self.database
19
+ @@__database ||= Database.new(self.couchrest_database)
20
+ end
21
+
22
+ # Returns the underlying CouchRest database object if you want low level access to your CouchDB. You have to set the CouchPotato::Config.database_name before this works.
23
+ def self.couchrest_database
24
+ @@__couchrest_database ||= CouchRest.database(full_url_to_database)
25
+ end
26
+
27
+ private
28
+
29
+ def self.full_url_to_database
30
+ raise('No Database configured. Set CouchPotato::Config.database_name') unless CouchPotato::Config.database_name
31
+ if CouchPotato::Config.database_name.match(%r{https?://})
32
+ CouchPotato::Config.database_name
33
+ else
34
+ "http://127.0.0.1:5984/#{CouchPotato::Config.database_name}"
35
+ end
36
+ end
37
+ end
38
+
39
+ $LOAD_PATH << File.dirname(__FILE__)
40
+
41
+ require 'core_ext/object'
42
+ require 'core_ext/time'
43
+ require 'core_ext/date'
44
+ require 'core_ext/string'
45
+ require 'core_ext/symbol'
46
+ require 'couch_potato/validation'
47
+ require 'couch_potato/persistence'
48
+ require 'couch_potato/railtie' if defined?(Rails)
@@ -0,0 +1,179 @@
1
+ module CouchPotato
2
+ class Database
3
+
4
+ class ValidationsFailedError < ::StandardError; end
5
+
6
+ def initialize(couchrest_database)
7
+ @couchrest_database = couchrest_database
8
+ begin
9
+ couchrest_database.info
10
+ rescue RestClient::ResourceNotFound
11
+ raise "Database '#{couchrest_database.name}' does not exist."
12
+ end
13
+ end
14
+
15
+ # executes a view and return the results. you pass in a view spec
16
+ # which is usually a result of a SomePersistentClass.some_view call.
17
+ # also return the total_rows returned by CouchDB as an accessor on the results.
18
+ #
19
+ # Example:
20
+ #
21
+ # class User
22
+ # include CouchPotato::Persistence
23
+ # property :age
24
+ # view :all, key: :age
25
+ # end
26
+ # db = CouchPotato.database
27
+ #
28
+ # db.view(User.all) # => [user1, user2]
29
+ # db.view(User.all).total_rows # => 2
30
+ #
31
+ # You can pass the usual parameters you can pass to a couchdb view to the view:
32
+ #
33
+ # db.view(User.all(limit: 5, startkey: 2, reduce: false))
34
+ #
35
+ # For your convenience when passing a hash with only a key parameter you can just pass in the value
36
+ #
37
+ # db.view(User.all(key: 1)) == db.view(User.all(1))
38
+ #
39
+ # Instead of passing a startkey and endkey you can pass in a key with a range:
40
+ #
41
+ # db.view(User.all(key: 1..20)) == db.view(startkey: 1, endkey: 20) == db.view(User.all(1..20))
42
+ #
43
+ # You can also pass in multiple keys:
44
+ #
45
+ # db.view(User.all(keys: [1, 2, 3]))
46
+ def view(spec)
47
+ results = CouchPotato::View::ViewQuery.new(
48
+ couchrest_database,
49
+ spec.design_document,
50
+ {spec.view_name => {
51
+ :map => spec.map_function,
52
+ :reduce => spec.reduce_function}
53
+ },
54
+ ({spec.list_name => spec.list_function} unless spec.list_name.nil?)
55
+ ).query_view!(spec.view_parameters)
56
+ processed_results = spec.process_results results
57
+ processed_results.instance_eval "def total_rows; #{results['total_rows']}; end" if results['total_rows']
58
+ processed_results.each do |document|
59
+ document.database = self if document.respond_to?(:database=)
60
+ end if processed_results.respond_to?(:each)
61
+ processed_results
62
+ end
63
+
64
+ # returns the first result from a #view query or nil
65
+ def first(spec)
66
+ view(spec).first
67
+ end
68
+
69
+ # returns th first result from a #view or raises CouchPotato::NotFound
70
+ def first!(spec)
71
+ first(spec) || raise(CouchPotato::NotFound)
72
+ end
73
+
74
+ # saves a document. returns true on success, false on failure
75
+ def save_document(document, validate = true)
76
+ return true unless document.dirty? || document.new?
77
+ if document.new?
78
+ create_document(document, validate)
79
+ else
80
+ update_document(document, validate)
81
+ end
82
+ end
83
+ alias_method :save, :save_document
84
+
85
+ # saves a document, raises a CouchPotato::Database::ValidationsFailedError on failure
86
+ def save_document!(document)
87
+ save_document(document) || raise(ValidationsFailedError.new(document.errors.full_messages))
88
+ end
89
+ alias_method :save!, :save_document!
90
+
91
+ def destroy_document(document)
92
+ document.run_callbacks :destroy do
93
+ document._deleted = true
94
+ couchrest_database.delete_doc document.to_hash
95
+ end
96
+ document._id = nil
97
+ document._rev = nil
98
+ end
99
+ alias_method :destroy, :destroy_document
100
+
101
+ # loads a document by its id
102
+ def load_document(id)
103
+ raise "Can't load a document without an id (got nil)" if id.nil?
104
+ begin
105
+ instance = couchrest_database.get(id)
106
+ instance.database = self
107
+ instance
108
+ rescue(RestClient::ResourceNotFound)
109
+ nil
110
+ end
111
+ end
112
+ alias_method :load, :load_document
113
+
114
+ def load!(id)
115
+ load(id) || raise(CouchPotato::NotFound)
116
+ end
117
+
118
+ def inspect #:nodoc:
119
+ "#<CouchPotato::Database @root=\"#{couchrest_database.root}\">"
120
+ end
121
+
122
+ # returns the underlying CouchRest::Database instance
123
+ def couchrest_database
124
+ @couchrest_database
125
+ end
126
+
127
+ private
128
+
129
+ def create_document(document, validate)
130
+ document.database = self
131
+
132
+ if validate
133
+ document.errors.clear
134
+ document.run_callbacks :validation_on_save do
135
+ document.run_callbacks :validation_on_create do
136
+ return false unless valid_document?(document)
137
+ end
138
+ end
139
+ end
140
+
141
+ document.run_callbacks :save do
142
+ document.run_callbacks :create do
143
+ res = couchrest_database.save_doc document.to_hash
144
+ document._rev = res['rev']
145
+ document._id = res['id']
146
+ end
147
+ end
148
+ true
149
+ end
150
+
151
+ def update_document(document, validate)
152
+ if validate
153
+ document.errors.clear
154
+ document.run_callbacks :validation_on_save do
155
+ document.run_callbacks :validation_on_update do
156
+ return false unless valid_document?(document)
157
+ end
158
+ end
159
+ end
160
+
161
+ document.run_callbacks :save do
162
+ document.run_callbacks :update do
163
+ res = couchrest_database.save_doc document.to_hash
164
+ document._rev = res['rev']
165
+ end
166
+ end
167
+ true
168
+ end
169
+
170
+ def valid_document?(document)
171
+ errors = document.errors.errors.dup
172
+ document.valid?
173
+ errors.each_pair do |k, v|
174
+ v.each {|message| document.errors.add(k, message)}
175
+ end
176
+ document.errors.empty?
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,124 @@
1
+ require 'digest/md5'
2
+ require File.dirname(__FILE__) + '/database'
3
+ require File.dirname(__FILE__) + '/persistence/active_model_compliance'
4
+ require File.dirname(__FILE__) + '/persistence/properties'
5
+ require File.dirname(__FILE__) + '/persistence/magic_timestamps'
6
+ require File.dirname(__FILE__) + '/persistence/callbacks'
7
+ require File.dirname(__FILE__) + '/persistence/json'
8
+ require File.dirname(__FILE__) + '/persistence/dirty_attributes'
9
+ require File.dirname(__FILE__) + '/persistence/ghost_attributes'
10
+ require File.dirname(__FILE__) + '/persistence/attachments'
11
+ require File.dirname(__FILE__) + '/persistence/type_caster'
12
+ require File.dirname(__FILE__) + '/view/custom_views'
13
+ require File.dirname(__FILE__) + '/view/lists'
14
+ require File.dirname(__FILE__) + '/view/view_query'
15
+
16
+
17
+ module CouchPotato
18
+ module Persistence
19
+
20
+ def self.included(base) #:nodoc:
21
+ base.send :include, Properties, Callbacks, Json, CouchPotato::View::CustomViews, CouchPotato::View::Lists
22
+ base.send :include, DirtyAttributes, GhostAttributes, Attachments
23
+ base.send :include, MagicTimestamps, ActiveModelCompliance
24
+ base.send :include, Validation
25
+ base.class_eval do
26
+ attr_accessor :_id, :_rev, :_deleted, :database
27
+ alias_method :id, :_id
28
+ alias_method :id=, :_id=
29
+ end
30
+ end
31
+
32
+ # initialize a new instance of the model optionally passing it a hash of attributes.
33
+ # the attributes have to be declared using the #property method.
34
+ # the new model will be yielded to an optionally given block.
35
+ #
36
+ # example:
37
+ # class Book
38
+ # include CouchPotato::Persistence
39
+ # property :title
40
+ # end
41
+ # book = Book.new :title => 'Time to Relax'
42
+ #
43
+ # OR
44
+ #
45
+ # book = Book.new do |b|
46
+ # b.title = 'Time to Relax'
47
+ # end
48
+ # book.title # => 'Time to Relax'
49
+ def initialize(attributes = {})
50
+ if attributes
51
+ attributes.each do |name, value|
52
+ self.send("#{name}=", value)
53
+ end
54
+ end
55
+ yield self if block_given?
56
+ end
57
+
58
+ # assign multiple attributes at once.
59
+ # the attributes have to be declared using the #property method
60
+ #
61
+ # example:
62
+ # class Book
63
+ # include CouchPotato::Persistence
64
+ # property :title
65
+ # property :year
66
+ # end
67
+ # book = Book.new
68
+ # book.attributes = {:title => 'Time to Relax', :year => 2009}
69
+ # book.title # => 'Time to Relax'
70
+ # book.year # => 2009
71
+ def attributes=(hash)
72
+ hash.each do |attribute, value|
73
+ self.send "#{attribute}=", value
74
+ end
75
+ end
76
+
77
+ # returns all of a model's attributes that have been defined using the #property method as a Hash
78
+ #
79
+ # example:
80
+ # class Book
81
+ # include CouchPotato::Persistence
82
+ # property :title
83
+ # property :year
84
+ # end
85
+ # book = Book.new :year => 2009
86
+ # book.attributes # => {:title => nil, :year => 2009}
87
+ def attributes
88
+ self.class.properties.inject({}) do |res, property|
89
+ property.value(res, self)
90
+ res
91
+ end
92
+ end
93
+
94
+ # returns true if a model hasn't been saved yet, false otherwise
95
+ def new?
96
+ _rev.nil?
97
+ end
98
+ alias_method :new_record?, :new?
99
+
100
+ # returns the document id
101
+ # this is used by rails to construct URLs
102
+ # can be overridden to for example use slugs for URLs instead if ids
103
+ def to_param
104
+ _id
105
+ end
106
+
107
+ def ==(other) #:nodoc:
108
+ other.class == self.class && self.to_json == other.to_json
109
+ end
110
+
111
+ def eql?(other)
112
+ self == other
113
+ end
114
+
115
+ def hash
116
+ _id.hash * (_id.hash.to_s.size ** 10) + _rev.hash
117
+ end
118
+
119
+ def inspect
120
+ attributes_as_string = attributes.map {|attribute, value| "#{attribute}: #{value.inspect}"}.join(", ")
121
+ %Q{#<#{self.class} _id: "#{_id}", _rev: "#{_rev}", #{attributes_as_string}>}
122
+ end
123
+ end
124
+ end