rufus-doric 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.txt CHANGED
@@ -2,6 +2,13 @@
2
2
  = rufus-doric CHANGELOG.txt
3
3
 
4
4
 
5
+ == rufus-doric - 0.1.3 released 2010/04/06
6
+
7
+ - Rufus::Doric::Value now has h_shortcut
8
+ - some kind of full-text indexing (.text_index / .texts) for Model
9
+ - preventing infinite loop when doing Model.by_x and db is not created
10
+
11
+
5
12
  == rufus-doric - 0.1.2 released 2010/03/23
6
13
 
7
14
  - person.belongings() returns all the object whose person_id is person._id
data/README.rdoc CHANGED
@@ -1,16 +1,162 @@
1
1
 
2
2
  = rufus-doric
3
3
 
4
- something at the intersection of Rails3, CouchDB and rufus-jig.
4
+ some Ruby lib at the intersection of Rails3, CouchDB and rufus-jig.
5
+
6
+
7
+ == how does rufus-doric determine which Couch database to use ?
8
+
9
+ At first it determines which CouchDB server to use via this code :
10
+
11
+ def self.couch_url
12
+
13
+ if defined?(Rails) # rails config/couch_url.txt
14
+ return File.read(Rails.root.join('config', 'couch_url.txt')).strip
15
+ end
16
+ if File.exist?('couch_url.txt') # current working directory
17
+ return File.read('couch_url.txt').strip
18
+ end
19
+
20
+ 'http://127.0.0.1:5984' # the default
21
+ end
22
+
23
+ Then the database is determined by calling Rufus::Doric.db(name)
24
+
25
+ p Rufus::Doric.db('mydb', :url_only => true)
26
+ # => "http://127.0.0.1:5984/mydb_test"
27
+
28
+ p Rufus::Doric.db('mydb', :env => 'camelia', :url_only => true)
29
+ # => "http://127.0.0.1:5984/mydb_camelia"
30
+
31
+ p Rufus::Doric.db('mydb', :env => 'camelia')
32
+ # => #<Rufus::Jig::Couch:0x00000102325120 @http=#<Rufus::Jig::Http:0x00000102324908 @host="127.0.0.1", @port=5984, @path="/toot_test", ...>...>
33
+
34
+ In summary, you only have to create a couch_url.txt file that contains the http:://{host}:{port} of your couch server. The rest is taken care of.
5
35
 
6
36
 
7
37
  == Rufus::Doric::Model usage
8
38
 
9
- see tests
39
+ One document per instance.
40
+
41
+ class Item < Rufus::Doric::Model
42
+
43
+ db :doric
44
+ # in which db it goes (remember that an _env suffix is added)
45
+
46
+ doric_type :items
47
+ # 'doric_type' field
48
+
49
+ _id_field :name
50
+ # _id field is determined from field 'name'
51
+
52
+ h_accessor :name
53
+ h_accessor :supplier
54
+
55
+ validates :supplier, :presence => true
56
+ end
57
+
58
+ There is more, please look at the test/ directory to discover Model.
59
+
10
60
 
11
61
  == Rufus::Doric::OneDocModel usage
12
62
 
13
- see tests
63
+ I use this for 'users' models.
64
+
65
+ class User < Rufus::Doric::OneDocModel
66
+
67
+ db 'doric'
68
+ doc_id :users
69
+
70
+ h_accessor :locale
71
+ h_accessor :email
72
+ h_reader :password
73
+
74
+ validates :password, :presence => true
75
+ end
76
+
77
+ It places all the users in one document (whereas Rufus::Doric::Model has 1 document per instance/record).
78
+
79
+ Look at the test/ directory to learn more about OneDocModel.
80
+
81
+
82
+ == Rufus::Doric::Value usage
83
+
84
+ Value is about Couch documents containing a single 'value' field (apart from _id and _rev)
85
+
86
+ Given
87
+
88
+ class Tuples < Rufus::Doric::Value
89
+
90
+ doc_id :tuples
91
+ db :doric
92
+
93
+ def to_s
94
+ value.sort.join(' ')
95
+ end
96
+ end
97
+
98
+ and a document 'tuples' in the database 'doric' :
99
+
100
+ {
101
+ "_id": "tuples",
102
+ "value": [ "alpha", "bravo", "charly" ]
103
+ }
104
+
105
+ this can be done :
106
+
107
+ p Tuples.load.to_s
108
+ # => "alpha bravo charly"
109
+
110
+ tuples = Tuples.load
111
+ tuples.value << 'borneo'
112
+ tuples.save!
113
+
114
+ p Tuples.load.to_s
115
+ # => "alpha borneo bravo charly"
116
+
117
+ Also :
118
+
119
+ tuples = Tuples.new(
120
+ '_id' => 'tuples', 'value' => %w[ alpha beta delta gamma ]).save!
121
+
122
+ p Tuples.load.to_s
123
+ # => "alpha beta delta gamma"
124
+
125
+
126
+ == Rufus::Doric::Value and h_shortcut
127
+
128
+ Most of the time, I use Rufus::Doric::Value to store a hash of things :
129
+
130
+ class Misc < Rufus::Doric::Value
131
+
132
+ doc_id :misc
133
+ db :doric
134
+
135
+ h_shortcut :product_lines
136
+ h_shortcut :purposes
137
+ end
138
+
139
+ with :
140
+
141
+ {
142
+ "_id": "misc",
143
+ "value": {
144
+ "product_lines" : [
145
+ "blue_coat", "ulticom", "znyx"
146
+ ],
147
+ "purposes": [
148
+ "stock", "non_stock", "replace", "rma", "loan"
149
+ ]
150
+ }
151
+ }
152
+
153
+ then in my app, I just do
154
+
155
+ p Misc.product_lines
156
+ # => [ "blue_coat", "ulticom", "znyx" ]
157
+
158
+ p Misc.purposes
159
+ # => [ "stock", "non_stock", "replace", "rma", "loan" ]
14
160
 
15
161
 
16
162
  == 'fixtures' usage
data/TODO.txt CHANGED
@@ -2,8 +2,13 @@
2
2
  [o] one_doc_model : validation
3
3
  [o] model : validation
4
4
  [o] destroy/delete
5
+ [o] fix Amedeo's infinite loop (model.rb l438)
5
6
 
6
7
  [x] lsof check (jig / patron)
7
8
 
8
9
  [ ] all, by : limit and skip (pagination)
9
10
 
11
+ [ ] eventually : cache the text index
12
+ [ ] eventually : pagination for the text index
13
+ [ ] pagination for everybody in Model (DRY)
14
+
@@ -115,6 +115,11 @@ module Doric
115
115
  }
116
116
  end
117
117
 
118
+ def self.text_index (*keys)
119
+
120
+ @text_index = keys
121
+ end
122
+
118
123
  include WithH
119
124
  include WithDb
120
125
 
@@ -369,6 +374,22 @@ module Doric
369
374
  "_design/doric_#{name}"
370
375
  end
371
376
 
377
+ # Well... Returns a map { 'word' => [ docid0, docid1 ] }
378
+ #
379
+ def self.texts (key=nil)
380
+
381
+ return nil unless @text_index
382
+
383
+ path = "#{design_path}/_view/text_index"
384
+ path = "#{path}?key=%22#{key}%22" if key
385
+
386
+ m = get_result(path, :text_index)
387
+
388
+ m = m['rows'].inject({}) { |h, r| (h[r['key']] ||= []) << r['id']; h }
389
+
390
+ key ? m[key] : m
391
+ end
392
+
372
393
  protected
373
394
 
374
395
  def self.put_design_doc (key=nil)
@@ -384,15 +405,42 @@ module Doric
384
405
  'views' => {}
385
406
  }
386
407
 
387
- ddoc['views']["by_#{key}"] = {
388
- 'map' => %{
389
- function(doc) {
390
- if (doc.doric_type == '#{@doric_type}') {
391
- emit(doc['#{key}'], null);
408
+ if key == :text_index
409
+
410
+ # I wish I could write keys.forEach(...) directly
411
+
412
+ # do no word removing, it depends on languages, and can be
413
+ # done on the client side
414
+
415
+ ddoc['views']['text_index'] = {
416
+ 'map' => %{
417
+ function(doc) {
418
+ if (doc.doric_type == '#{@doric_type}') {
419
+ var keys = #{Rufus::Json.encode(@text_index)};
420
+ for (var key in doc) {
421
+ if (keys.indexOf(key) < 0) continue;
422
+ if (doc[key] == undefined) continue;
423
+ var words = doc[key].split(/[\s,;\.]/);
424
+ words.forEach(function (word) {
425
+ if (word != '') emit(word, null);
426
+ });
427
+ }
428
+ }
392
429
  }
393
430
  }
394
431
  }
395
- }
432
+ else
433
+
434
+ ddoc['views']["by_#{key}"] = {
435
+ 'map' => %{
436
+ function(doc) {
437
+ if (doc.doric_type == '#{@doric_type}') {
438
+ emit(doc['#{key}'], null);
439
+ }
440
+ }
441
+ }
442
+ }
443
+ end
396
444
 
397
445
  db.put(ddoc)
398
446
  end
@@ -405,18 +453,7 @@ module Doric
405
453
  "_design/doric/_view/by_doric_type?key=%22#{@doric_type}%22" +
406
454
  "&include_docs=true"
407
455
 
408
- result = db.get(path)
409
-
410
- unless result
411
-
412
- # insert design doc
413
-
414
- r = put_design_doc
415
- raise(
416
- "failed to insert design_doc in db '#{db.name}'"
417
- ) if r == true
418
- return get_all(opts)
419
- end
456
+ result = get_result(path)
420
457
 
421
458
  result['rows'].collect { |r| r['doc'] }
422
459
  end
@@ -431,14 +468,34 @@ module Doric
431
468
 
432
469
  path = "#{design_path}/_view/by_#{key}?key=#{v}&include_docs=true"
433
470
 
471
+ result = get_result(path, key)
472
+
473
+ result['rows'].collect { |r| self.new(r['doc']) }
474
+ end
475
+
476
+ # Ensures the necessary design_doc is loaded (if first query failed)
477
+ # and then returns the raw result.
478
+ #
479
+ # Will raise if the design_doc can't be inserted (probably the underlying
480
+ # db is missing).
481
+ #
482
+ def self.get_result (path, design_doc_key=nil)
483
+
434
484
  result = db.get(path)
435
485
 
436
- unless result
437
- put_design_doc(key)
438
- return by(key, val, opts)
439
- end
486
+ return result if result
440
487
 
441
- result['rows'].collect { |r| self.new(r['doc']) }
488
+ # insert design doc
489
+
490
+ r = put_design_doc(design_doc_key)
491
+
492
+ raise(
493
+ "failed to insert 'any' design_doc in db '#{db.name}'"
494
+ ) if r == true
495
+
496
+ # re-get
497
+
498
+ get_result(path, design_doc_key)
442
499
  end
443
500
  end
444
501
  end
@@ -40,6 +40,16 @@ module Doric
40
40
  @doc_id
41
41
  end
42
42
 
43
+ def self.h_shortcut (*keys)
44
+ keys.each do |k|
45
+ self.instance_eval %{
46
+ def #{k}
47
+ load.value[#{k.to_s.inspect}]
48
+ end
49
+ }
50
+ end
51
+ end
52
+
43
53
  include WithDb
44
54
 
45
55
  #
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Rufus
3
3
  module Doric
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.3'
5
5
  end
6
6
  end
7
7
 
data/rufus-doric.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rufus-doric}
8
- s.version = "0.1.2"
8
+ s.version = "0.1.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Mettraux"]
12
- s.date = %q{2010-03-23}
12
+ s.date = %q{2010-04-06}
13
13
  s.description = %q{
14
14
  something at the intersection of Rails3, CouchDB and rufus-jig
15
15
  }
@@ -53,7 +53,8 @@ something at the intersection of Rails3, CouchDB and rufus-jig
53
53
  "test/ut_5_value.rb",
54
54
  "test/ut_6_model_associations.rb",
55
55
  "test/ut_7_looser_associations.rb",
56
- "test/ut_8_belongings.rb"
56
+ "test/ut_8_belongings.rb",
57
+ "test/ut_9_any.rb"
57
58
  ]
58
59
  s.homepage = %q{http://github.com/jmettraux/rufus-doric/}
59
60
  s.rdoc_options = ["--charset=UTF-8"]
@@ -64,5 +64,14 @@ class UtModelTest < Test::Unit::TestCase
64
64
 
65
65
  assert_not_nil Nada::Thing.db.get('_design/doric_nada__thing')
66
66
  end
67
+
68
+ def test_no_infinite_loop_when_missing_db
69
+
70
+ Rufus::Doric.db('doric').delete('.')
71
+
72
+ assert_raise RuntimeError do
73
+ Nada::Thing.by_colour('blue').size
74
+ end
75
+ end
67
76
  end
68
77
 
data/test/ut_5_value.rb CHANGED
@@ -21,6 +21,14 @@ class Tuples < Rufus::Doric::Value
21
21
  end
22
22
  end
23
23
 
24
+ class Misc < Rufus::Doric::Value
25
+
26
+ doc_id :misc
27
+ db :doric
28
+
29
+ h_shortcut :product_lines
30
+ end
31
+
24
32
 
25
33
  class UtValueTest < Test::Unit::TestCase
26
34
 
@@ -65,5 +73,13 @@ class UtValueTest < Test::Unit::TestCase
65
73
 
66
74
  assert_equal 'alpha beta delta gamma', Tuples.load.to_s
67
75
  end
76
+
77
+ def test_h_shortcut
78
+
79
+ Misc.new(
80
+ '_id' => 'misc', 'value' => { 'product_lines' => %w[ a b c ]}).save!
81
+
82
+ assert_equal %w[ a b c ], Misc.product_lines
83
+ end
68
84
  end
69
85
 
data/test/ut_9_any.rb ADDED
@@ -0,0 +1,115 @@
1
+
2
+ #
3
+ # testing rufus-doric
4
+ #
5
+ # Thu Apr 1 10:32:57 JST 2010
6
+ #
7
+
8
+ require File.join(File.dirname(__FILE__), 'base')
9
+
10
+ require 'rufus/doric'
11
+
12
+
13
+ class Product < Rufus::Doric::Model
14
+
15
+ db :doric
16
+ doric_type :products
17
+
18
+ _id_field :serial_number
19
+
20
+ property :serial_number
21
+ property :name
22
+ property :brand
23
+ property :category
24
+ property :comment
25
+
26
+ text_index :serial_number, :name, :brand
27
+ end
28
+
29
+ class Whatever < Rufus::Doric::Model
30
+
31
+ db :doric
32
+ doric_type :whatevers
33
+
34
+ _id_field :name
35
+
36
+ property :name
37
+ end
38
+
39
+
40
+ class UtAnyTest < Test::Unit::TestCase
41
+
42
+ def setup
43
+
44
+ Rufus::Doric.db('doric').delete('.')
45
+ Rufus::Doric.db('doric').put('.')
46
+
47
+ Rufus::Doric.db('doric').http.cache.clear
48
+ # CouchDB feeds the same etags for views, even after a db has
49
+ # been deleted and put back, so have to do that 'forgetting'
50
+
51
+ Product.new(
52
+ :serial_number => 'h2o',
53
+ :name => 'water',
54
+ :brand => 'earth',
55
+ :category => 'drink',
56
+ :comment => nil
57
+ ).save!
58
+ Product.new(
59
+ :serial_number => '951',
60
+ :name => 'seamaster professional',
61
+ :brand => 'omega',
62
+ :category => 'watches',
63
+ :comment => 'want as well'
64
+ ).save!
65
+ Product.new(
66
+ :serial_number => 'lv52',
67
+ :name => 'leather bag, for men',
68
+ :brand => 'lv',
69
+ :category => 'bags',
70
+ :comment => nil
71
+ ).save!
72
+ Product.new(
73
+ :serial_number => '0kcal',
74
+ :name => 'zero',
75
+ :brand => 'coca-cola',
76
+ :category => 'drink',
77
+ :comment => nil
78
+ ).save!
79
+ Product.new(
80
+ :serial_number => 'tell2',
81
+ :name => 'tell bag',
82
+ :brand => 'victorinox',
83
+ :category => 'bags',
84
+ :comment => nil
85
+ ).save!
86
+
87
+ Whatever.new(
88
+ :name => 'whatever'
89
+ ).save!
90
+ end
91
+
92
+ #def teardown
93
+ #end
94
+
95
+ def test_no_texts
96
+
97
+ assert_nil Whatever.texts
98
+ end
99
+
100
+ def test_texts
101
+
102
+ index = Product.texts
103
+
104
+ #p index
105
+
106
+ assert_equal %w[ lv52 tell2 ], index['bag'].sort
107
+ assert_equal nil, index['products']
108
+ end
109
+
110
+ def test_texts_key
111
+
112
+ assert_equal [ '951' ], Product.texts('omega')
113
+ end
114
+ end
115
+
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 2
9
- version: 0.1.2
8
+ - 3
9
+ version: 0.1.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - John Mettraux
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-23 00:00:00 +09:00
17
+ date: 2010-04-06 00:00:00 +09:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -141,6 +141,7 @@ files:
141
141
  - test/ut_6_model_associations.rb
142
142
  - test/ut_7_looser_associations.rb
143
143
  - test/ut_8_belongings.rb
144
+ - test/ut_9_any.rb
144
145
  has_rdoc: true
145
146
  homepage: http://github.com/jmettraux/rufus-doric/
146
147
  licenses: []