couchbase-model 0.0.1 → 0.1.0
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/.travis.yml +1 -1
- data/README.markdown +91 -2
- data/couchbase-model.gemspec +4 -4
- data/lib/couchbase-model.rb +5 -0
- data/lib/couchbase/model.rb +313 -30
- data/lib/couchbase/model/configuration.rb +30 -0
- data/lib/couchbase/model/uuid.rb +10 -0
- data/lib/couchbase/model/version.rb +1 -1
- data/lib/couchbase/railtie.rb +136 -0
- data/lib/rails/generators/couchbase/config/config_generator.rb +43 -0
- data/lib/rails/generators/couchbase/config/templates/couchbase.yml +23 -0
- data/lib/rails/generators/couchbase/view/templates/map.js +40 -0
- data/lib/rails/generators/couchbase/view/templates/reduce.js +61 -0
- data/lib/rails/generators/couchbase/view/view_generator.rb +43 -0
- data/lib/rails/generators/couchbase_generator.rb +42 -0
- data/tasks/package.rake +27 -0
- data/test/test_model.rb +42 -1
- metadata +59 -24
data/.travis.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
before_install:
|
2
2
|
- wget -O- http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add -
|
3
|
-
- echo deb http://packages.couchbase.com/ubuntu lucid lucid/main | sudo tee /etc/apt/sources.list.d/couchbase.list
|
3
|
+
- echo deb http://packages.couchbase.com/preview/ubuntu lucid lucid/main | sudo tee /etc/apt/sources.list.d/couchbase.list
|
4
4
|
- sudo apt-get update
|
5
5
|
- sudo apt-get -y install libevent-dev libvbucket-dev libcouchbase-dev
|
6
6
|
|
data/README.markdown
CHANGED
@@ -1,6 +1,41 @@
|
|
1
|
-
# Couchbase Model
|
1
|
+
# Couchbase Model
|
2
2
|
|
3
|
-
This library allows to declare models for [couchbase gem][1].
|
3
|
+
This library allows to declare models for [couchbase gem][1].
|
4
|
+
|
5
|
+
## Rails integration
|
6
|
+
|
7
|
+
To generate config you can use `rails generate couchbase:config`:
|
8
|
+
|
9
|
+
$ rails generate couchbase:config
|
10
|
+
create config/couchbase.yml
|
11
|
+
|
12
|
+
It will generate this `config/couchbase.yml` for you:
|
13
|
+
|
14
|
+
common: &common
|
15
|
+
hostname: localhost
|
16
|
+
port: 8091
|
17
|
+
username:
|
18
|
+
password:
|
19
|
+
pool: default
|
20
|
+
|
21
|
+
development:
|
22
|
+
<<: *common
|
23
|
+
bucket: couchbase_tinyurl_development
|
24
|
+
|
25
|
+
test:
|
26
|
+
<<: *common
|
27
|
+
bucket: couchbase_tinyurl_test
|
28
|
+
|
29
|
+
# set these environment variables on your production server
|
30
|
+
production:
|
31
|
+
hostname: <%= ENV['COUCHBASE_HOST'] %>
|
32
|
+
port: <%= ENV['COUCHBASE_PORT'] %>
|
33
|
+
username: <%= ENV['COUCHBASE_USERNAME'] %>
|
34
|
+
password: <%= ENV['COUCHBASE_PASSWORD'] %>
|
35
|
+
pool: <%= ENV['COUCHBASE_POOL'] %>
|
36
|
+
bucket: <%= ENV['COUCHBASE_BUCKET'] %>
|
37
|
+
|
38
|
+
## Examples
|
4
39
|
|
5
40
|
require 'couchbase/model'
|
6
41
|
|
@@ -48,4 +83,58 @@ You can define connection options on per model basis:
|
|
48
83
|
connect :port => 80, :bucket => 'blog'
|
49
84
|
end
|
50
85
|
|
86
|
+
## Views (aka Map/Reduce queries to Couchbase)
|
87
|
+
|
88
|
+
Views are stored in models directory in subdirectory named after the
|
89
|
+
model (to be precious `design_document` attribute of the model class).
|
90
|
+
Here is an example of directory layout for `Link` model with three
|
91
|
+
views.
|
92
|
+
|
93
|
+
.
|
94
|
+
└── app
|
95
|
+
└── models
|
96
|
+
├── link
|
97
|
+
│ ├── total_count
|
98
|
+
│ │ ├── map.js
|
99
|
+
│ │ └── reduce.js
|
100
|
+
│ ├── by_created_at
|
101
|
+
│ │ └── map.js
|
102
|
+
│ └── by_view_count
|
103
|
+
│ └── map.js
|
104
|
+
└── link.rb
|
105
|
+
|
106
|
+
To generate view you can use yet another generator `rails generate
|
107
|
+
couchbase:view DESIGNDOCNAME VIEWNAME`. For example how `total_count`
|
108
|
+
view could be generated:
|
109
|
+
|
110
|
+
$ rails generate link total_count
|
111
|
+
|
112
|
+
The generated files contains useful info and links about how to write
|
113
|
+
map and reduce functions, you can take a look at them in the [templates
|
114
|
+
directory][2].
|
115
|
+
|
116
|
+
In the model class you should declare accessible views:
|
117
|
+
|
118
|
+
class Post < Couchbase::Model
|
119
|
+
attribute :title
|
120
|
+
attribute :body
|
121
|
+
attribute :draft
|
122
|
+
attribute :view_count
|
123
|
+
attribute :created_at, :default => lambda { Time.now }
|
124
|
+
|
125
|
+
view :total_count, :by_created_at, :by_view_count
|
126
|
+
end
|
127
|
+
|
128
|
+
And request them later:
|
129
|
+
|
130
|
+
Post.by_created_at(:include_docs => true).each do |post|
|
131
|
+
puts post.title
|
132
|
+
end
|
133
|
+
|
134
|
+
Post.by_view_count(:include_docs => true).group_by(&:view_count) do |count, posts|
|
135
|
+
p "#{count} -> #{posts.map{|pp| pp.inspect}.join(', ')}"
|
136
|
+
end
|
137
|
+
|
138
|
+
|
51
139
|
[1]: https://github.com/couchbase/couchbase-ruby-client/
|
140
|
+
[2]: https://github.com/couchbaselabs/ruby-couchbase-model/tree/master/lib/rails/generators/couchbase/view/templates/
|
data/couchbase-model.gemspec
CHANGED
@@ -5,9 +5,9 @@ require "couchbase/model/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "couchbase-model"
|
7
7
|
s.version = Couchbase::Model::VERSION
|
8
|
-
s.
|
9
|
-
s.email =
|
10
|
-
s.homepage = ""
|
8
|
+
s.author = "Couchbase"
|
9
|
+
s.email = "support@couchbase.com"
|
10
|
+
s.homepage = "https://github.com/couchbaselabs/ruby-couchbase-model"
|
11
11
|
s.summary = %q{Declarative interface to Couchbase}
|
12
12
|
s.description = %q{ORM-like interface allows you to persist your models to Couchbase}
|
13
13
|
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
17
|
s.require_paths = ["lib"]
|
18
18
|
|
19
|
-
s.add_runtime_dependency 'couchbase', '~> 1.
|
19
|
+
s.add_runtime_dependency 'couchbase', '~> 1.2.0.dp'
|
20
20
|
|
21
21
|
s.add_development_dependency 'rake', '~> 0.8.7'
|
22
22
|
s.add_development_dependency 'minitest'
|
data/lib/couchbase-model.rb
CHANGED
data/lib/couchbase/model.rb
CHANGED
@@ -15,16 +15,22 @@
|
|
15
15
|
# limitations under the License.
|
16
16
|
#
|
17
17
|
|
18
|
+
require 'digest/md5'
|
19
|
+
|
18
20
|
require 'couchbase'
|
19
21
|
require 'couchbase/model/version'
|
20
22
|
require 'couchbase/model/uuid'
|
23
|
+
require 'couchbase/model/configuration'
|
21
24
|
|
22
25
|
module Couchbase
|
23
26
|
|
27
|
+
# @since 0.0.1
|
24
28
|
class Error::MissingId < Error::Base; end
|
25
29
|
|
26
30
|
# Declarative layer for Couchbase gem
|
27
31
|
#
|
32
|
+
# @since 0.0.1
|
33
|
+
#
|
28
34
|
# require 'couchbase/model'
|
29
35
|
#
|
30
36
|
# class Post < Couchbase::Model
|
@@ -72,14 +78,35 @@ module Couchbase
|
|
72
78
|
# end
|
73
79
|
class Model
|
74
80
|
# Each model must have identifier
|
81
|
+
#
|
82
|
+
# @since 0.0.1
|
75
83
|
attr_accessor :id
|
76
84
|
|
77
|
-
# @
|
78
|
-
|
85
|
+
# @since 0.1.0
|
86
|
+
attr_reader :_key
|
87
|
+
|
88
|
+
# @since 0.1.0
|
89
|
+
attr_reader :_value
|
90
|
+
|
91
|
+
# @since 0.1.0
|
92
|
+
attr_reader :_doc
|
93
|
+
|
94
|
+
# @since 0.1.0
|
95
|
+
attr_reader :_meta
|
96
|
+
|
97
|
+
# @private Container for all attributes with defaults of all subclasses
|
98
|
+
@@attributes = ::Hash.new {|hash, key| hash[key] = {}}
|
99
|
+
|
100
|
+
# @private Container for all view names of all subclasses
|
101
|
+
@@views = ::Hash.new {|hash, key| hash[key] = []}
|
79
102
|
|
80
103
|
# Use custom connection options
|
81
104
|
#
|
82
|
-
# @
|
105
|
+
# @since 0.0.1
|
106
|
+
#
|
107
|
+
# @param [String, Hash, Array] options options for establishing
|
108
|
+
# connection.
|
109
|
+
# @return [Couchbase::Bucket]
|
83
110
|
#
|
84
111
|
# @see Couchbase::Bucket#initialize
|
85
112
|
#
|
@@ -92,8 +119,108 @@ module Couchbase
|
|
92
119
|
self.bucket = Couchbase.connect(*options)
|
93
120
|
end
|
94
121
|
|
122
|
+
# Associate custom design document with the model
|
123
|
+
#
|
124
|
+
# Design document is the special document which contains views, the
|
125
|
+
# chunks of code for building map/reduce indexes. When this method
|
126
|
+
# called without argument, it just returns the effective design document
|
127
|
+
# name.
|
128
|
+
#
|
129
|
+
# @since 0.1.0
|
130
|
+
#
|
131
|
+
# @see http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views.html
|
132
|
+
#
|
133
|
+
# @param [String, Symbol] name the name for the design document. By
|
134
|
+
# default underscored model name is used.
|
135
|
+
# @return [String] the effective design document
|
136
|
+
#
|
137
|
+
# @example Choose specific design document name
|
138
|
+
# class Post < Couchbase::Model
|
139
|
+
# design_document :my_posts
|
140
|
+
# ...
|
141
|
+
# end
|
142
|
+
def self.design_document(name = nil)
|
143
|
+
if name
|
144
|
+
@_design_doc = name.to_s
|
145
|
+
else
|
146
|
+
@_design_doc ||= begin
|
147
|
+
name = self.name.dup
|
148
|
+
name.gsub!(/::/, '_')
|
149
|
+
name.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
150
|
+
name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
151
|
+
name.downcase!
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Ensure that design document is up to date.
|
157
|
+
#
|
158
|
+
# @since 0.1.0
|
159
|
+
#
|
160
|
+
# This method also cares about organizing view in separate javascript
|
161
|
+
# files. The general structure is the following (+[root]+ is the
|
162
|
+
# directory, one of the {Model::Configuration.design_documents_paths}):
|
163
|
+
#
|
164
|
+
# [root]
|
165
|
+
# |
|
166
|
+
# `- link
|
167
|
+
# | |
|
168
|
+
# | `- by_created_at
|
169
|
+
# | | |
|
170
|
+
# | | `- map.js
|
171
|
+
# | |
|
172
|
+
# | `- by_session_id
|
173
|
+
# | | |
|
174
|
+
# | | `- map.js
|
175
|
+
# | |
|
176
|
+
# | `- total_views
|
177
|
+
# | | |
|
178
|
+
# | | `- map.js
|
179
|
+
# | | |
|
180
|
+
# | | `- reduce.js
|
181
|
+
#
|
182
|
+
# The directory structure above demonstrate layout for design document
|
183
|
+
# with id +_design/link+ and three views: +by_create_at+,
|
184
|
+
# +by_session_id` and `total_views`.
|
185
|
+
def self.ensure_design_document!
|
186
|
+
unless Configuration.design_documents_paths
|
187
|
+
raise "Configuration.design_documents_path must be directory"
|
188
|
+
end
|
189
|
+
|
190
|
+
doc = {'_id' => "_design/#{design_document}", 'views' => {}}
|
191
|
+
digest = Digest::MD5.new
|
192
|
+
mtime = 0
|
193
|
+
views.each do |name|
|
194
|
+
doc['views'][name] = view = {}
|
195
|
+
['map', 'reduce'].each do |type|
|
196
|
+
Configuration.design_documents_paths.each do |path|
|
197
|
+
ff = File.join(path, design_document.to_s, name.to_s, "#{type}.js")
|
198
|
+
if File.file?(ff)
|
199
|
+
view[type] = File.read(ff)
|
200
|
+
mtime = [mtime, File.mtime(ff).to_i].max
|
201
|
+
digest << view[type]
|
202
|
+
break # pick first matching file
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
doc['signature'] = digest.to_s
|
208
|
+
doc['timestamp'] = mtime
|
209
|
+
if doc['signature'] != thread_storage[:signature] && doc['timestamp'] > thread_storage[:timestamp].to_i
|
210
|
+
current_doc = bucket.design_docs[design_document.to_s]
|
211
|
+
if current_doc.nil? || (current_doc['signature'] != doc['signature'] && doc['timestamp'] > current_doc[:timestamp].to_i)
|
212
|
+
bucket.save_design_doc(doc)
|
213
|
+
current_doc = doc
|
214
|
+
end
|
215
|
+
thread_storage[:signature] = current_doc['signature']
|
216
|
+
thread_storage[:timestamp] = current_doc['timestamp'].to_i
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
95
220
|
# Choose the UUID generation algorithms
|
96
221
|
#
|
222
|
+
# @since 0.0.1
|
223
|
+
#
|
97
224
|
# @param [Symbol] algorithm (:sequential) one of the available
|
98
225
|
# algorithms.
|
99
226
|
#
|
@@ -104,12 +231,16 @@ module Couchbase
|
|
104
231
|
# uuid_algorithm :random
|
105
232
|
# ...
|
106
233
|
# end
|
234
|
+
#
|
235
|
+
# @return [Symbol]
|
107
236
|
def self.uuid_algorithm(algorithm)
|
108
237
|
self.thread_storage[:uuid_algorithm] = algorithm
|
109
238
|
end
|
110
239
|
|
111
240
|
# Defines an attribute for the model
|
112
241
|
#
|
242
|
+
# @since 0.0.1
|
243
|
+
#
|
113
244
|
# @param [Symbol, String] name name of the attribute
|
114
245
|
#
|
115
246
|
# @example Define some attributes for a model
|
@@ -122,49 +253,114 @@ module Couchbase
|
|
122
253
|
# post = Post.new(:title => 'Hello world',
|
123
254
|
# :body => 'This is the first example...',
|
124
255
|
# :published_at => Time.now)
|
125
|
-
def self.attribute(
|
126
|
-
|
127
|
-
|
256
|
+
def self.attribute(*names)
|
257
|
+
options = {}
|
258
|
+
if names.last.is_a?(Hash)
|
259
|
+
options = names.pop
|
128
260
|
end
|
129
|
-
|
130
|
-
|
261
|
+
names.each do |name|
|
262
|
+
name = name.to_sym
|
263
|
+
define_method(name) do
|
264
|
+
@_attributes[name]
|
265
|
+
end
|
266
|
+
define_method(:"#{name}=") do |value|
|
267
|
+
@_attributes[name] = value
|
268
|
+
end
|
269
|
+
attributes[name] = options[:default]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.view(*names)
|
274
|
+
options = {}
|
275
|
+
if names.last.is_a?(Hash)
|
276
|
+
options = names.pop
|
277
|
+
end
|
278
|
+
names.each do |name|
|
279
|
+
views << name
|
280
|
+
self.instance_eval <<-EOV, __FILE__, __LINE__ + 1
|
281
|
+
def #{name}(params = {})
|
282
|
+
View.new(bucket, "_design/\#{design_document}/_view/#{name}",
|
283
|
+
params.merge(:wrapper_class => self, :include_docs => true))
|
284
|
+
end
|
285
|
+
EOV
|
131
286
|
end
|
132
|
-
attributes << name unless attributes.include?(name)
|
133
287
|
end
|
134
288
|
|
135
289
|
# Find the model using +id+ attribute
|
136
290
|
#
|
291
|
+
# @since 0.0.1
|
292
|
+
#
|
137
293
|
# @param [String, Symbol] id model identificator
|
138
294
|
# @return [Couchbase::Model] an instance of the model
|
295
|
+
# @raise [Couchbase::Error::NotFound] when given key isn't exist
|
139
296
|
#
|
140
297
|
# @example Find model using +id+
|
141
298
|
# post = Post.find('the-id')
|
142
299
|
def self.find(id)
|
143
|
-
if id && (
|
144
|
-
|
300
|
+
if id && (res = bucket.get(id, :quiet => false, :extended => true))
|
301
|
+
obj, flags, cas = res
|
302
|
+
new({:id => id, :_meta => {'flags' => flags, 'cas' => cas}}.merge(obj))
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Find the model using +id+ attribute
|
307
|
+
#
|
308
|
+
# @since 0.1.0
|
309
|
+
#
|
310
|
+
# @param [String, Symbol] id model identificator
|
311
|
+
# @return [Couchbase::Model, nil] an instance of the model or +nil+ if
|
312
|
+
# given key isn't exist
|
313
|
+
#
|
314
|
+
# @example Find model using +id+
|
315
|
+
# post = Post.find_by_id('the-id')
|
316
|
+
def self.find_by_id(id)
|
317
|
+
if id && (res = bucket.get(id, :quiet => true))
|
318
|
+
obj, flags, cas = res
|
319
|
+
new({:id => id, :_meta => {'flags' => flags, 'cas' => cas}}.merge(obj))
|
145
320
|
end
|
146
321
|
end
|
147
322
|
|
148
323
|
# Create the model with given attributes
|
149
324
|
#
|
325
|
+
# @since 0.0.1
|
326
|
+
#
|
150
327
|
# @param [Hash] args attribute-value pairs for the object
|
151
328
|
# @return [Couchbase::Model] an instance of the model
|
152
329
|
def self.create(*args)
|
153
330
|
new(*args).create
|
154
331
|
end
|
155
332
|
|
156
|
-
# Constructor for all subclasses of Couchbase::Model
|
157
|
-
#
|
333
|
+
# Constructor for all subclasses of Couchbase::Model
|
334
|
+
#
|
335
|
+
# @since 0.0.1
|
336
|
+
#
|
337
|
+
# Optionally takes a Hash of attribute value pairs.
|
158
338
|
#
|
159
339
|
# @param [Hash] attrs attribute-value pairs
|
160
340
|
def initialize(attrs = {})
|
161
|
-
|
162
|
-
|
163
|
-
|
341
|
+
if attrs.respond_to?(:with_indifferent_access)
|
342
|
+
attrs = attrs.with_indifferent_access
|
343
|
+
end
|
344
|
+
@id = attrs.delete(:id)
|
345
|
+
@_key = attrs.delete(:_key)
|
346
|
+
@_value = attrs.delete(:_value)
|
347
|
+
@_doc = attrs.delete(:_doc)
|
348
|
+
@_meta = attrs.delete(:_meta)
|
349
|
+
@_attributes = ::Hash.new do |h, k|
|
350
|
+
default = self.class.attributes[k]
|
351
|
+
h[k] = if default.respond_to?(:call)
|
352
|
+
default.call
|
353
|
+
else
|
354
|
+
default
|
355
|
+
end
|
356
|
+
end
|
357
|
+
update_attributes(@_doc || attrs)
|
164
358
|
end
|
165
359
|
|
166
360
|
# Create this model and assign new id if necessary
|
167
361
|
#
|
362
|
+
# @since 0.0.1
|
363
|
+
#
|
168
364
|
# @return [Couchbase::Model] newly created object
|
169
365
|
#
|
170
366
|
# @raise [Couchbase::Error::KeyExists] if model with the same +id+
|
@@ -175,12 +371,14 @@ module Couchbase
|
|
175
371
|
# p.create
|
176
372
|
def create
|
177
373
|
@id ||= Couchbase::Model::UUID.generator.next(1, model.thread_storage[:uuid_algorithm])
|
178
|
-
model.bucket.add(@id,
|
374
|
+
model.bucket.add(@id, attributes_with_values)
|
179
375
|
self
|
180
376
|
end
|
181
377
|
|
182
378
|
# Create or update this object based on the state of #new?.
|
183
379
|
#
|
380
|
+
# @since 0.0.1
|
381
|
+
#
|
184
382
|
# @return [Couchbase::Model] The saved object
|
185
383
|
#
|
186
384
|
# @example Update the Post model
|
@@ -189,12 +387,14 @@ module Couchbase
|
|
189
387
|
# p.save
|
190
388
|
def save
|
191
389
|
return create if new?
|
192
|
-
model.bucket.set(@id,
|
390
|
+
model.bucket.set(@id, attributes_with_values)
|
193
391
|
self
|
194
392
|
end
|
195
393
|
|
196
394
|
# Update this object, optionally accepting new attributes.
|
197
395
|
#
|
396
|
+
# @since 0.0.1
|
397
|
+
#
|
198
398
|
# @param [Hash] attrs Attribute value pairs to use for the updated
|
199
399
|
# version
|
200
400
|
# @return [Couchbase::Model] The updated object
|
@@ -205,6 +405,8 @@ module Couchbase
|
|
205
405
|
|
206
406
|
# Delete this object from the bucket
|
207
407
|
#
|
408
|
+
# @since 0.0.1
|
409
|
+
#
|
208
410
|
# @note This method will reset +id+ attribute
|
209
411
|
#
|
210
412
|
# @return [Couchbase::Model] Returns a reference of itself.
|
@@ -221,6 +423,8 @@ module Couchbase
|
|
221
423
|
|
222
424
|
# Check if the record have +id+ attribute
|
223
425
|
#
|
426
|
+
# @since 0.0.1
|
427
|
+
#
|
224
428
|
# @return [true, false] Whether or not this object has an id.
|
225
429
|
#
|
226
430
|
# @note +true+ doesn't mean that record exists in the database
|
@@ -232,6 +436,8 @@ module Couchbase
|
|
232
436
|
|
233
437
|
# Check if the key exists in the bucket
|
234
438
|
#
|
439
|
+
# @since 0.0.1
|
440
|
+
#
|
235
441
|
# @param [String, Symbol] id the record identifier
|
236
442
|
# @return [true, false] Whether or not the object with given +id+
|
237
443
|
# presented in the bucket.
|
@@ -241,21 +447,40 @@ module Couchbase
|
|
241
447
|
|
242
448
|
# Check if this model exists in the bucket.
|
243
449
|
#
|
450
|
+
# @since 0.0.1
|
451
|
+
#
|
244
452
|
# @return [true, false] Whether or not this object presented in the
|
245
453
|
# bucket.
|
246
454
|
def exists?
|
247
455
|
model.exists?(@id)
|
248
456
|
end
|
249
457
|
|
250
|
-
# All
|
458
|
+
# All defined attributes within a class.
|
459
|
+
#
|
460
|
+
# @since 0.0.1
|
251
461
|
#
|
252
462
|
# @see Model.attribute
|
463
|
+
#
|
464
|
+
# @return [Hash]
|
253
465
|
def self.attributes
|
254
466
|
@@attributes[self]
|
255
467
|
end
|
256
468
|
|
469
|
+
# All defined views within a class.
|
470
|
+
#
|
471
|
+
# @since 0.1.0
|
472
|
+
#
|
473
|
+
# @see Model.view
|
474
|
+
#
|
475
|
+
# @return [Array]
|
476
|
+
def self.views
|
477
|
+
@@views[self]
|
478
|
+
end
|
479
|
+
|
257
480
|
# All the attributes of the current instance
|
258
481
|
#
|
482
|
+
# @since 0.0.1
|
483
|
+
#
|
259
484
|
# @return [Hash]
|
260
485
|
def attributes
|
261
486
|
@_attributes
|
@@ -263,18 +488,23 @@ module Couchbase
|
|
263
488
|
|
264
489
|
# Update all attributes without persisting the changes.
|
265
490
|
#
|
491
|
+
# @since 0.0.1
|
492
|
+
#
|
266
493
|
# @param [Hash] attrs attribute-value pairs.
|
267
494
|
def update_attributes(attrs)
|
268
495
|
if id = attrs.delete(:id)
|
269
496
|
@id = id
|
270
497
|
end
|
271
498
|
attrs.each do |key, value|
|
272
|
-
|
499
|
+
setter = :"#{key}="
|
500
|
+
send(setter, value) if respond_to?(setter)
|
273
501
|
end
|
274
502
|
end
|
275
503
|
|
276
504
|
# Reload all the model attributes from the bucket
|
277
505
|
#
|
506
|
+
# @since 0.0.1
|
507
|
+
#
|
278
508
|
# @return [Model] the latest model state
|
279
509
|
#
|
280
510
|
# @raise [Error::MissingId] for records without +id+
|
@@ -287,46 +517,99 @@ module Couchbase
|
|
287
517
|
end
|
288
518
|
|
289
519
|
# @private The thread local storage for model specific stuff
|
520
|
+
#
|
521
|
+
# @since 0.0.1
|
290
522
|
def self.thread_storage
|
291
523
|
Couchbase.thread_storage[self] ||= {:uuid_algorithm => :sequential}
|
292
524
|
end
|
293
525
|
|
294
526
|
# @private Fetch the current connection
|
527
|
+
#
|
528
|
+
# @since 0.0.1
|
295
529
|
def self.bucket
|
296
530
|
self.thread_storage[:bucket] ||= Couchbase.bucket
|
297
531
|
end
|
298
532
|
|
299
533
|
# @private Set the current connection
|
300
534
|
#
|
535
|
+
# @since 0.0.1
|
536
|
+
#
|
301
537
|
# @param [Bucket] connection the connection instance
|
302
538
|
def self.bucket=(connection)
|
303
539
|
self.thread_storage[:bucket] = connection
|
304
540
|
end
|
305
541
|
|
306
542
|
# @private Get model class
|
543
|
+
#
|
544
|
+
# @since 0.0.1
|
307
545
|
def model
|
308
546
|
self.class
|
309
547
|
end
|
310
548
|
|
311
|
-
# @private Wrap the hash to the model class
|
312
|
-
#
|
313
|
-
# @
|
314
|
-
#
|
315
|
-
|
316
|
-
|
549
|
+
# @private Wrap the hash to the model class.
|
550
|
+
#
|
551
|
+
# @since 0.0.1
|
552
|
+
#
|
553
|
+
# @param [Bucket] bucket the reference to Bucket instance
|
554
|
+
# @param [Hash] data the Hash fetched by View, it should have at least
|
555
|
+
# +"id"+, +"key"+ and +"value"+ keys, also it could have optional
|
556
|
+
# +"doc"+ key.
|
557
|
+
#
|
558
|
+
# @return [Model]
|
559
|
+
def self.wrap(bucket, data)
|
560
|
+
doc = {
|
561
|
+
:_key => data['key'],
|
562
|
+
:_value => data['value'],
|
563
|
+
:_meta => {},
|
564
|
+
:id => data['id']
|
565
|
+
}
|
566
|
+
if doc[:_value].is_a?(Hash) && (_id = doc[:_value]['_id'])
|
567
|
+
doc[:id] = _id
|
568
|
+
end
|
569
|
+
if data['doc']
|
570
|
+
data['doc'].keys.each do |key|
|
571
|
+
if key.start_with?("$")
|
572
|
+
doc[:_meta][key.sub(/^\$/, '')] = data['doc'].delete(key)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
doc.update(data['doc'])
|
576
|
+
end
|
577
|
+
new(doc)
|
317
578
|
end
|
318
579
|
|
319
580
|
# @private Returns a string containing a human-readable representation
|
320
581
|
# of the record.
|
582
|
+
#
|
583
|
+
# @since 0.0.1
|
321
584
|
def inspect
|
322
|
-
attrs = model.attributes.
|
323
|
-
[attr, @_attributes[attr].inspect]
|
324
|
-
end
|
585
|
+
attrs = model.attributes.map do |attr, default|
|
586
|
+
[attr.to_s, @_attributes[attr].inspect]
|
587
|
+
end.sort
|
325
588
|
sprintf("#<%s:%s %s>",
|
326
589
|
model, new? ? "?" : id,
|
327
|
-
attrs.map{|a| a.join("=")}.join(" "))
|
590
|
+
attrs.map{|a| a.join("=")}.join(", "))
|
591
|
+
end
|
592
|
+
|
593
|
+
def self.inspect
|
594
|
+
buf = "#{name}"
|
595
|
+
if self != Couchbase::Model
|
596
|
+
buf << "(#{['id', attributes.map(&:first)].flatten.join(', ')})"
|
597
|
+
end
|
598
|
+
buf
|
328
599
|
end
|
329
600
|
|
601
|
+
protected
|
602
|
+
|
603
|
+
# @private Returns a hash with model attributes
|
604
|
+
#
|
605
|
+
# @since 0.1.0
|
606
|
+
def attributes_with_values
|
607
|
+
ret = {:type => model.design_document}
|
608
|
+
model.attributes.keys.each do |attr|
|
609
|
+
ret[attr] = @_attributes[attr]
|
610
|
+
end
|
611
|
+
ret
|
612
|
+
end
|
330
613
|
end
|
331
614
|
|
332
615
|
end
|