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.
- data/.gitignore +7 -0
- data/.travis.yml +5 -0
- data/CHANGES.md +148 -0
- data/CREDITS +6 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.md +450 -0
- data/Rakefile +82 -0
- data/couch_potato.gemspec +27 -0
- data/init.rb +3 -0
- data/lib/core_ext/date.rb +14 -0
- data/lib/core_ext/object.rb +5 -0
- data/lib/core_ext/string.rb +12 -0
- data/lib/core_ext/symbol.rb +15 -0
- data/lib/core_ext/time.rb +23 -0
- data/lib/couch_potato.rb +48 -0
- data/lib/couch_potato/database.rb +179 -0
- data/lib/couch_potato/persistence.rb +124 -0
- data/lib/couch_potato/persistence/active_model_compliance.rb +44 -0
- data/lib/couch_potato/persistence/attachments.rb +31 -0
- data/lib/couch_potato/persistence/callbacks.rb +29 -0
- data/lib/couch_potato/persistence/dirty_attributes.rb +56 -0
- data/lib/couch_potato/persistence/ghost_attributes.rb +12 -0
- data/lib/couch_potato/persistence/json.rb +47 -0
- data/lib/couch_potato/persistence/magic_timestamps.rb +23 -0
- data/lib/couch_potato/persistence/properties.rb +79 -0
- data/lib/couch_potato/persistence/simple_property.rb +82 -0
- data/lib/couch_potato/persistence/type_caster.rb +40 -0
- data/lib/couch_potato/railtie.rb +25 -0
- data/lib/couch_potato/rspec.rb +2 -0
- data/lib/couch_potato/rspec/matchers.rb +39 -0
- data/lib/couch_potato/rspec/matchers/json2.js +482 -0
- data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +54 -0
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +49 -0
- data/lib/couch_potato/rspec/matchers/print_r.js +60 -0
- data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +50 -0
- data/lib/couch_potato/rspec/stub_db.rb +46 -0
- data/lib/couch_potato/validation.rb +16 -0
- data/lib/couch_potato/validation/with_active_model.rb +27 -0
- data/lib/couch_potato/validation/with_validatable.rb +41 -0
- data/lib/couch_potato/version.rb +3 -0
- data/lib/couch_potato/view/base_view_spec.rb +84 -0
- data/lib/couch_potato/view/custom_view_spec.rb +42 -0
- data/lib/couch_potato/view/custom_views.rb +52 -0
- data/lib/couch_potato/view/lists.rb +23 -0
- data/lib/couch_potato/view/model_view_spec.rb +75 -0
- data/lib/couch_potato/view/properties_view_spec.rb +47 -0
- data/lib/couch_potato/view/raw_view_spec.rb +25 -0
- data/lib/couch_potato/view/view_query.rb +82 -0
- data/rails/init.rb +4 -0
- data/rails/reload_classes.rb +47 -0
- data/spec/attachments_spec.rb +23 -0
- data/spec/callbacks_spec.rb +297 -0
- data/spec/create_spec.rb +35 -0
- data/spec/custom_view_spec.rb +239 -0
- data/spec/default_property_spec.rb +38 -0
- data/spec/destroy_spec.rb +29 -0
- data/spec/fixtures/address.rb +10 -0
- data/spec/fixtures/person.rb +6 -0
- data/spec/property_spec.rb +323 -0
- data/spec/rails_spec.rb +50 -0
- data/spec/railtie_spec.rb +65 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/unit/active_model_compliance_spec.rb +98 -0
- data/spec/unit/attributes_spec.rb +135 -0
- data/spec/unit/base_view_spec_spec.rb +106 -0
- data/spec/unit/callbacks_spec.rb +46 -0
- data/spec/unit/couch_potato_spec.rb +39 -0
- data/spec/unit/create_spec.rb +69 -0
- data/spec/unit/custom_views_spec.rb +15 -0
- data/spec/unit/database_spec.rb +317 -0
- data/spec/unit/date_spec.rb +22 -0
- data/spec/unit/dirty_attributes_spec.rb +136 -0
- data/spec/unit/initialize_spec.rb +38 -0
- data/spec/unit/json_spec.rb +30 -0
- data/spec/unit/lists_spec.rb +20 -0
- data/spec/unit/model_view_spec_spec.rb +13 -0
- data/spec/unit/properties_view_spec_spec.rb +31 -0
- data/spec/unit/rspec_matchers_spec.rb +124 -0
- data/spec/unit/rspec_stub_db_spec.rb +35 -0
- data/spec/unit/string_spec.rb +7 -0
- data/spec/unit/time_spec.rb +15 -0
- data/spec/unit/validation_spec.rb +67 -0
- data/spec/unit/view_query_spec.rb +86 -0
- data/spec/update_spec.rb +40 -0
- data/spec/view_updates_spec.rb +28 -0
- metadata +243 -0
data/Rakefile
ADDED
@@ -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,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
|
data/lib/couch_potato.rb
ADDED
@@ -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
|