rufus-doric 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,300 @@
1
+ #--
2
+ # Copyright (c) 2010, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ #require 'cgi'
26
+
27
+
28
+ module Rufus
29
+ module Doric
30
+
31
+ DORIC_DESIGN_DOC = {
32
+ '_id' => '_design/doric',
33
+ 'views' => {
34
+ 'by_doric_type' => {
35
+ 'map' => %{
36
+ function(doc) {
37
+ if (doc.doric_type) emit(doc.doric_type, null);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ #
45
+ # Classes extending that class have 1 Couch document per instance
46
+ #
47
+ class Model
48
+
49
+ #extend ActiveModel::Naming
50
+ include ActiveModel::Validations
51
+
52
+ #
53
+ # class 'helpers'
54
+
55
+ def self.doric_type (rt=nil)
56
+
57
+ @doric_type = rt.to_s if rt
58
+ @doric_type
59
+ end
60
+
61
+ def self._id_field (field_name=nil, &block)
62
+
63
+ @_id_field = field_name.to_s if field_name
64
+ @_id_field = block if block
65
+
66
+ @_id_field
67
+ end
68
+
69
+ def self.view_by (key)
70
+
71
+ @keys ||= []
72
+ @keys << key.to_s
73
+
74
+ instance_eval %{
75
+ def by_#{key} (val, opts={})
76
+ by('#{key}', val, opts)
77
+ end
78
+ }
79
+ end
80
+
81
+ include WithH
82
+ include WithDb
83
+
84
+ #--
85
+ # constructor and instance methods
86
+ #++
87
+
88
+ attr_reader :h
89
+
90
+ def initialize (doc={})
91
+
92
+ @h = doc.inject({}) { |h, (k, v)| h[k.to_s] = v; h }
93
+ @h['doric_type'] = self.class.doric_type
94
+ end
95
+
96
+ def _id
97
+ @h['_id']
98
+ end
99
+
100
+ def _rev
101
+ @h['_rev']
102
+ end
103
+
104
+ def id
105
+ @h['_id']
106
+ end
107
+
108
+ def attachments
109
+ (@h['_attachments'] || {}).keys.sort
110
+ end
111
+
112
+ def copy
113
+
114
+ h = Rufus::Json.dup(@h)
115
+ h.delete('_id')
116
+ h.delete('_rev')
117
+
118
+ self.class.new(h)
119
+ end
120
+
121
+ def save!
122
+
123
+ raise ActiveRecord::RecordInvalid.new(self) unless valid?
124
+
125
+ if @h['_id'].nil? && self.class._id_field
126
+
127
+ i = if self.class._id_field.is_a?(String)
128
+ self.send(self.class._id_field)
129
+ else
130
+ self.instance_eval &self.class._id_field
131
+ end
132
+
133
+ @h['_id'] = Rufus::Doric.neutralize_id(i)
134
+ end
135
+
136
+ raise ActiveRecord::RecordInvalid.new(self) if @h['_id'].nil?
137
+
138
+ r = db.put(@h)
139
+
140
+ raise(SaveFailed.new(self.class.doric_type, _id)) unless r.nil?
141
+ end
142
+
143
+ #--
144
+ # methods required by ActiveModel (see test/unit/lint_mdmodel_test.rb
145
+ #++
146
+
147
+ def to_model
148
+
149
+ self
150
+ end
151
+
152
+ def destroyed?
153
+
154
+ @h['_destroyed'] == true
155
+ end
156
+
157
+ def new_record?
158
+
159
+ @h['_id'].nil?
160
+ end
161
+
162
+ # Is used by <resource>_path and <resource>_url
163
+ #
164
+ def to_param
165
+
166
+ @h['_id']
167
+ end
168
+
169
+ def delete
170
+
171
+ @h['_destroyed'] = true
172
+ db.delete(@h)
173
+
174
+ # TODO : raise when the delete fails
175
+ end
176
+
177
+ def destroy
178
+
179
+ delete
180
+ end
181
+
182
+ #--
183
+ # class methods
184
+ #++
185
+
186
+ def self.destroy_all
187
+
188
+ get_all({}).each { |d| db.delete(d) }
189
+ end
190
+
191
+ def self.all (opts={})
192
+
193
+ get_all(opts).collect { |d| self.new(d) }
194
+ end
195
+
196
+ def self.find (_id)
197
+
198
+ doc = db.get(_id)
199
+
200
+ raise Rufus::Doric::NotFound.new(@doric_type, _id) unless doc
201
+
202
+ self.new(doc)
203
+ end
204
+
205
+ def self.design_path
206
+
207
+ name = self.to_s.downcase
208
+ name = name.gsub(/::/, '__')
209
+
210
+ "_design/doric_#{name}"
211
+ end
212
+
213
+ protected
214
+
215
+ def self.put_design_doc (key=nil)
216
+
217
+ # the 'all' view
218
+
219
+ unless key
220
+ db.put(DORIC_DESIGN_DOC)
221
+ return
222
+ end
223
+
224
+ # by_{key} views
225
+
226
+ x = {
227
+ '_id' => '_design/doric',
228
+ 'views' => {
229
+ 'by_doric_type' => {
230
+ 'map' => %{
231
+ function(doc) {
232
+ if (doc.doric_type) emit(doc.doric_type, null);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ ddoc = db.get(design_path) || {
240
+ '_id' => design_path,
241
+ 'views' => {}
242
+ }
243
+
244
+ ddoc['views']["by_#{key}"] = {
245
+ 'map' => %{
246
+ function(doc) {
247
+ if (doc.doric_type == '#{@doric_type}') {
248
+ emit(doc['#{key}'], null);
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ db.put(ddoc)
255
+ end
256
+
257
+ def self.get_all (opts)
258
+
259
+ # TODO : limit, skip (opts)
260
+
261
+ path =
262
+ "_design/doric/_view/by_doric_type?key=%22#{@doric_type}%22" +
263
+ "&include_docs=true"
264
+
265
+ result = db.get(path)
266
+
267
+ unless result
268
+
269
+ # insert design doc
270
+
271
+ put_design_doc
272
+ return get_all(opts)
273
+ end
274
+
275
+ result['rows'].collect { |r| r['doc'] }
276
+ end
277
+
278
+ def self.by (key, val, opts)
279
+
280
+ # TODO : limit, skip (opts)
281
+
282
+ #v = Rufus::Json.encode(val)
283
+ #v = CGI.escape(v)
284
+ v = "%22#{val}%22"
285
+
286
+ path = "#{design_path}/_view/by_#{key}?key=#{v}&include_docs=true"
287
+
288
+ result = db.get(path)
289
+
290
+ unless result
291
+ put_design_doc(key)
292
+ return by(key, val, opts)
293
+ end
294
+
295
+ result['rows'].collect { |r| r['doc'] }
296
+ end
297
+ end
298
+ end
299
+ end
300
+
@@ -0,0 +1,122 @@
1
+ #--
2
+ # Copyright (c) 2010, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ require 'active_model'
26
+ require 'active_record'
27
+
28
+ # there are more 'require' at the end of this file
29
+
30
+
31
+ module Rufus
32
+ module Doric
33
+
34
+ def self.neutralize_id (s)
35
+
36
+ s.to_s.strip.gsub(/[\s\/:;\*\\\+\?]/, '_')
37
+ end
38
+
39
+ #
40
+ # The including classes get two new class methods : h_reader and h_accessor
41
+ #
42
+ module WithH
43
+
44
+ def self.included (target)
45
+
46
+ def target.known_fields
47
+ @known_fields
48
+ end
49
+
50
+ def target.h_reader (*names)
51
+ names.each do |name|
52
+ name = name.to_s
53
+ (@known_fields ||= []) << name
54
+ define_method(name) do
55
+ @h[name]
56
+ end
57
+ end
58
+ end
59
+ def target.h_accessor (*names)
60
+ h_reader(*names)
61
+ names.each do |name|
62
+ define_method("#{name}=") do |v|
63
+ @h[name.to_s] = v
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ #
71
+ # The .db 'xyz' and #db methods
72
+ #
73
+ module WithDb
74
+
75
+ def self.included (target)
76
+
77
+ def target.db (dbname=nil, opts=nil)
78
+
79
+ if dbname
80
+ @db = dbname.to_s
81
+ @db_opts = opts || {}
82
+ return @db
83
+ end
84
+
85
+ Rufus::Doric::Couch.db(@db, @db_opts)
86
+ end
87
+ end
88
+
89
+ def db
90
+
91
+ self.class.db
92
+ end
93
+ end
94
+
95
+ #--
96
+ # ERRORS
97
+ #++
98
+
99
+ # A common error class
100
+ #
101
+ class ModelError < StandardError
102
+
103
+ attr_accessor :model_class, :_id
104
+
105
+ def initialize (model_class, _id)
106
+ @model_class = model_class
107
+ @_id = _id
108
+ end
109
+
110
+ def to_s
111
+ "#{@model_class}/#{@_id}"
112
+ end
113
+ end
114
+
115
+ class NotFound < ModelError; end
116
+ class SaveFailed < ModelError; end
117
+ end
118
+ end
119
+
120
+ require 'rufus/doric/model'
121
+ require 'rufus/doric/one_doc_model'
122
+
@@ -0,0 +1,136 @@
1
+ #--
2
+ # Copyright (c) 2010, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Rufus
27
+ module Doric
28
+
29
+ #
30
+ # The models built on top of this class store all their instances
31
+ # in one Couch document.
32
+ #
33
+ class OneDocModel
34
+
35
+ include ActiveModel::Validations
36
+
37
+ #
38
+ # class 'helpers'
39
+
40
+ def self.doc_id (_id=nil)
41
+
42
+ @doc_id = _id.to_s if _id
43
+ @doc_id
44
+ end
45
+
46
+ include WithH
47
+ include WithDb
48
+
49
+ #
50
+ # constructor and instance methods
51
+
52
+ attr_reader :h
53
+ attr_reader :attachment
54
+
55
+ def initialize (h, doc=nil)
56
+
57
+ @h = h.inject({}) { |hh, (k, v)| hh[k.to_s] = v; hh }
58
+
59
+ if doc && atts = doc['_attachments']
60
+
61
+ @attachment, details = atts.find { |k, v|
62
+ File.basename(k, File.extname(k)) == @h['_id']
63
+ }
64
+ end
65
+ end
66
+
67
+ def _id
68
+
69
+ @h['_id']
70
+ end
71
+
72
+ def save!
73
+
74
+ raise ActiveRecord::RecordInvalid.new(self) unless valid?
75
+
76
+ doc = self.class.do_get(self.class.doc_id)
77
+ doc[self.class.doc_id][@h['_id']] = @h
78
+
79
+ db.put(doc)
80
+ end
81
+
82
+ def delete
83
+
84
+ doc = self.class.do_get(self.class.doc_id)
85
+ doc[self.class.doc_id].delete(@h['_id'])
86
+
87
+ # TODO : raise when the put fails
88
+
89
+ db.put(doc)
90
+ end
91
+
92
+ def destroy
93
+
94
+ delete
95
+ end
96
+
97
+ #--
98
+ # class methods
99
+ #++
100
+
101
+ def self.create! (h)
102
+
103
+ self.new(h).save!
104
+ end
105
+
106
+ def self.all
107
+
108
+ doc = do_get(@doc_id)
109
+
110
+ doc[@doc_id].values.collect { |h| self.new(h, doc) }
111
+ end
112
+
113
+ def self.destroy_all
114
+
115
+ doc = db.get(@doc_id)
116
+ db.delete(doc) if doc
117
+ end
118
+
119
+ def self.find (_id)
120
+
121
+ doc = do_get(@doc_id)
122
+
123
+ h = doc[@doc_id][_id]
124
+ h ? self.new(h, doc) : nil
125
+ end
126
+
127
+ protected
128
+
129
+ def self.do_get (doc_id)
130
+
131
+ db.get(doc_id) || { '_id' => doc_id, doc_id => {} }
132
+ end
133
+ end
134
+ end
135
+ end
136
+
@@ -0,0 +1,7 @@
1
+
2
+ module Rufus
3
+ module Doric
4
+ VERSION = '0.1.0'
5
+ end
6
+ end
7
+
@@ -0,0 +1,4 @@
1
+
2
+ require 'rufus/doric/couch'
3
+ require 'rufus/doric/models'
4
+
@@ -0,0 +1,3 @@
1
+
2
+ require 'rufus/doric'
3
+
@@ -0,0 +1,90 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rufus-doric}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["John Mettraux"]
12
+ s.date = %q{2010-03-17}
13
+ s.description = %q{
14
+ something at the intersection of Rails3, CouchDB and rufus-jig
15
+ }
16
+ s.email = %q{jmettraux@gmail.com}
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ "CHANGELOG.txt",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "TODO.txt",
27
+ "lib/rufus-doric.rb",
28
+ "lib/rufus/doric.rb",
29
+ "lib/rufus/doric/couch.rb",
30
+ "lib/rufus/doric/fixtures.rb",
31
+ "lib/rufus/doric/model.rb",
32
+ "lib/rufus/doric/models.rb",
33
+ "lib/rufus/doric/one_doc_model.rb",
34
+ "lib/rufus/doric/version.rb",
35
+ "rufus-doric.gemspec",
36
+ "test/base.rb",
37
+ "test/fixtures/test/doric/69247b__picture.jpg",
38
+ "test/fixtures/test/doric/69249__picture.jpg",
39
+ "test/fixtures/test/doric/product0.json",
40
+ "test/fixtures/test/doric/product1.json",
41
+ "test/fixtures/test/doric/users.json",
42
+ "test/fixtures/test/doric/users__jami.png",
43
+ "test/fixtures/test/doric/users__john.jpg",
44
+ "test/fixtures/test/doric_ENV_workitems/workitem0.json",
45
+ "test/test.rb",
46
+ "test/ut_0_fixtures.rb",
47
+ "test/ut_1_model.rb",
48
+ "test/ut_2_model_view.rb",
49
+ "test/ut_3_model_lint.rb",
50
+ "test/ut_4_one_doc_model.rb"
51
+ ]
52
+ s.homepage = %q{http://github.com/jmettraux/rufus-doric/}
53
+ s.rdoc_options = ["--charset=UTF-8"]
54
+ s.require_paths = ["lib"]
55
+ s.rubyforge_project = %q{rufus}
56
+ s.rubygems_version = %q{1.3.6}
57
+ s.summary = %q{something at the intersection of Rails3, CouchDB and rufus-jig}
58
+ s.test_files = [
59
+ "test/test.rb"
60
+ ]
61
+
62
+ if s.respond_to? :specification_version then
63
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
67
+ s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.0.beta"])
68
+ s.add_runtime_dependency(%q<rufus-jig>, [">= 0.1.16"])
69
+ s.add_runtime_dependency(%q<mime-types>, [">= 1.16"])
70
+ s.add_development_dependency(%q<rake>, [">= 0"])
71
+ s.add_development_dependency(%q<yard>, [">= 0"])
72
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
73
+ else
74
+ s.add_dependency(%q<activerecord>, ["~> 3.0.0.beta"])
75
+ s.add_dependency(%q<rufus-jig>, [">= 0.1.16"])
76
+ s.add_dependency(%q<mime-types>, [">= 1.16"])
77
+ s.add_dependency(%q<rake>, [">= 0"])
78
+ s.add_dependency(%q<yard>, [">= 0"])
79
+ s.add_dependency(%q<jeweler>, [">= 0"])
80
+ end
81
+ else
82
+ s.add_dependency(%q<activerecord>, ["~> 3.0.0.beta"])
83
+ s.add_dependency(%q<rufus-jig>, [">= 0.1.16"])
84
+ s.add_dependency(%q<mime-types>, [">= 1.16"])
85
+ s.add_dependency(%q<rake>, [">= 0"])
86
+ s.add_dependency(%q<yard>, [">= 0"])
87
+ s.add_dependency(%q<jeweler>, [">= 0"])
88
+ end
89
+ end
90
+
data/test/base.rb ADDED
@@ -0,0 +1,16 @@
1
+
2
+ lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $:.unshift(lib) unless $:.include?(lib)
4
+
5
+ require 'test/unit'
6
+
7
+ require 'rubygems'
8
+ require 'yajl'
9
+ require 'patron'
10
+
11
+ require 'rufus/jig'
12
+
13
+ ENV['RAILS_ENV'] = 'test'
14
+ require 'active_support'
15
+ require 'rufus/doric'
16
+
@@ -0,0 +1,8 @@
1
+ {
2
+ "_id": "69249",
3
+ "serial_number": "69249",
4
+ "type": "AH0305 MAC Board",
5
+ "sub_type": "ATM",
6
+ "barcode": "76076P05936",
7
+ "roma_type": "products"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "_id": "69247b",
3
+ "serial_number": "69247b",
4
+ "type": "AH0305 Shaun Smith SnowBoard",
5
+ "sub_type": "STM",
6
+ "barcode": "7607SF6P05936",
7
+ "roma_type": "products"
8
+ }