rufus-doric 0.1.15 → 0.1.16
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +7 -0
- data/Rakefile +1 -1
- data/TODO.txt +10 -1
- data/lib/rufus/doric/model.rb +111 -82
- data/lib/rufus/doric/models.rb +36 -0
- data/lib/rufus/doric/one_doc_model.rb +12 -0
- data/lib/rufus/doric/version.rb +1 -1
- data/rufus-doric.gemspec +3 -2
- data/test/ut_16_model_custom_view.rb +15 -0
- data/test/ut_20_model_view_by_ignore_case.rb +25 -5
- data/test/ut_21_open_model.rb +85 -0
- data/test/ut_4_one_doc_model.rb +10 -4
- metadata +4 -3
data/CHANGELOG.txt
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
= rufus-doric CHANGELOG.txt
|
3
3
|
|
4
4
|
|
5
|
+
== rufus-doric - 0.1.16 released 2010/07/12
|
6
|
+
|
7
|
+
- OneDocModel#attach
|
8
|
+
- removed duplicates for custom view results. Thanks Claudio.
|
9
|
+
- 'open' models
|
10
|
+
|
11
|
+
|
5
12
|
== rufus-doric - 0.1.15 released 2010/06/22
|
6
13
|
|
7
14
|
- view_by :name, :ignore_case => true
|
data/Rakefile
CHANGED
data/TODO.txt
CHANGED
@@ -9,12 +9,21 @@
|
|
9
9
|
[o] view_by 'name', %{ emit(doc.nada) }
|
10
10
|
[o] start and end key should be by_x(:start => a, :end => b)
|
11
11
|
[o] credit Claudio
|
12
|
+
[o] case-insentive index (Claudio)
|
13
|
+
[o] open model
|
14
|
+
[o] custom views : remove duplicate docs from results (Claudio)
|
12
15
|
|
13
16
|
[x] lsof check (jig / patron)
|
14
17
|
|
15
18
|
[ ] eventually : cache the text index
|
16
19
|
|
17
20
|
[ ] view_by 'x', 'emit' ==> view 'x', 'emit' ==> Model.x(key)...
|
21
|
+
[ ] view_by 'x', :condition => '{some javascript}' (a la SimplyStored)
|
18
22
|
|
19
|
-
[ ]
|
23
|
+
[ ] open models, OK, what about on-the-fly views ?
|
24
|
+
do you really need views for those ?
|
25
|
+
|
26
|
+
[ ] internal _sum, _count and _stats
|
27
|
+
http://www.mikealrogers.com/archives/785
|
28
|
+
http://wiki.apache.org/couchdb/Built-In_Reduce_Functions
|
20
29
|
|
data/lib/rufus/doric/model.rb
CHANGED
@@ -105,6 +105,13 @@ module Doric
|
|
105
105
|
@_id_field
|
106
106
|
end
|
107
107
|
|
108
|
+
# Flags a model as "open". New properties can be added on the fly.
|
109
|
+
#
|
110
|
+
def self.open
|
111
|
+
|
112
|
+
@open = true
|
113
|
+
end
|
114
|
+
|
108
115
|
def self.attachment (attachment_name)
|
109
116
|
|
110
117
|
class_eval %{
|
@@ -294,36 +301,16 @@ module Doric
|
|
294
301
|
end
|
295
302
|
end
|
296
303
|
|
304
|
+
# Attaches a document to this model.
|
305
|
+
#
|
306
|
+
# o.attach('icon.jpg', File.read('path/to/file.jpg'))
|
307
|
+
#
|
308
|
+
# No need to save! after an attachment, but the model/instance has to be
|
309
|
+
# up to date (ie, latest _rev).
|
310
|
+
#
|
297
311
|
def attach (attname, data, opts={})
|
298
312
|
|
299
|
-
|
300
|
-
basename = File.basename(attname, extname)
|
301
|
-
mime = ::MIME::Types.type_for(attname).first
|
302
|
-
|
303
|
-
if data.is_a?(File)
|
304
|
-
mime = ::MIME::Types.type_for(data.path).first
|
305
|
-
data = data.read
|
306
|
-
elsif data.is_a?(Array)
|
307
|
-
data, mime = data
|
308
|
-
mime = ::MIME::Types[mime].first
|
309
|
-
end
|
310
|
-
|
311
|
-
raise ArgumentError.new("couldn't determine mime type") unless mime
|
312
|
-
|
313
|
-
attname = "#{attname}.#{mime.extensions.first}" if extname == ''
|
314
|
-
|
315
|
-
if @h['_rev'] # document has already been saved
|
316
|
-
|
317
|
-
db.attach(
|
318
|
-
@h['_id'], @h['_rev'], attname, data, :content_type => mime.to_s)
|
319
|
-
|
320
|
-
else # document hasn't yet been saved, inline attachment...
|
321
|
-
|
322
|
-
(@h['_attachments'] ||= {})[attname] = {
|
323
|
-
'content_type' => mime.to_s,
|
324
|
-
'data' => Base64.encode64(data).gsub(/[\r\n]/, '')
|
325
|
-
}
|
326
|
-
end
|
313
|
+
do_attach(@h, attname, data, opts)
|
327
314
|
end
|
328
315
|
|
329
316
|
# Reads an attachment
|
@@ -344,6 +331,8 @@ module Doric
|
|
344
331
|
db.get("#{@h['_id']}/#{attname}")
|
345
332
|
end
|
346
333
|
|
334
|
+
# Removes an attachment.
|
335
|
+
#
|
347
336
|
def detach (attname)
|
348
337
|
|
349
338
|
raise ArgumentError.new("model not yet saved") unless @h['_rev']
|
@@ -351,6 +340,18 @@ module Doric
|
|
351
340
|
db.delete("#{@h['_id']}/#{attname}?rev=#{@h['_rev']}")
|
352
341
|
end
|
353
342
|
|
343
|
+
# If this model is open, will remove a property (key and value).
|
344
|
+
# If the model is not open, will raise an error.
|
345
|
+
#
|
346
|
+
def remove (key)
|
347
|
+
|
348
|
+
raise(
|
349
|
+
"model #{self.class.name} is not open, cannot remove properties"
|
350
|
+
) unless self.class.instance_variable_get(:@open)
|
351
|
+
|
352
|
+
@h.delete(key.to_s)
|
353
|
+
end
|
354
|
+
|
354
355
|
#--
|
355
356
|
# methods required by ActiveModel (see test/unit/ut_3_model_lint.rb)
|
356
357
|
#++
|
@@ -454,64 +455,17 @@ module Doric
|
|
454
455
|
result['rows'].collect { |r| Rufus::Doric.instantiate(r['doc']) }
|
455
456
|
end
|
456
457
|
|
457
|
-
#
|
458
|
+
# The association and the 'open' magic occurs here, except for #belongings
|
458
459
|
#
|
459
460
|
def method_missing (m, *args)
|
460
461
|
|
461
|
-
|
462
|
-
|
463
|
-
multiple = (mm != sm)
|
464
|
-
|
465
|
-
klass = sm.camelize
|
466
|
-
klass = (self.class.const_get(klass) rescue nil)
|
467
|
-
|
468
|
-
#return super unless klass
|
469
|
-
|
470
|
-
id_method = multiple ? "#{sm}_ids" : "#{mm}_id"
|
471
|
-
|
472
|
-
unless klass
|
473
|
-
|
474
|
-
return super unless self.respond_to?(id_method)
|
475
|
-
|
476
|
-
i = self.send(id_method)
|
477
|
-
|
478
|
-
if multiple
|
479
|
-
|
480
|
-
return [] unless i
|
462
|
+
success, result = open_method_missing(m.to_s, args)
|
463
|
+
return result if success
|
481
464
|
|
482
|
-
|
483
|
-
|
484
|
-
}.select { |e|
|
485
|
-
e != nil
|
486
|
-
}
|
487
|
-
end
|
488
|
-
|
489
|
-
return Rufus::Doric.instantiate(db.get(i))
|
490
|
-
end
|
465
|
+
success, result = association_method_missing(m.to_s, args)
|
466
|
+
return result if success
|
491
467
|
|
492
|
-
|
493
|
-
|
494
|
-
if self.respond_to?(id_method)
|
495
|
-
|
496
|
-
ids = self.send(id_method)
|
497
|
-
return [] unless ids
|
498
|
-
|
499
|
-
ids.collect { |i| klass.find(i) }
|
500
|
-
|
501
|
-
else
|
502
|
-
|
503
|
-
by_method = "by_#{self.class.doric_type.singularize}_id"
|
504
|
-
klass.send(by_method, self._id)
|
505
|
-
end
|
506
|
-
|
507
|
-
else
|
508
|
-
|
509
|
-
return super unless self.respond_to?(id_method)
|
510
|
-
|
511
|
-
id = self.send(id_method)
|
512
|
-
|
513
|
-
id ? klass.find(id) : nil
|
514
|
-
end
|
468
|
+
super
|
515
469
|
end
|
516
470
|
|
517
471
|
def hash
|
@@ -589,6 +543,81 @@ module Doric
|
|
589
543
|
"#{self.class.doric_type}__#{s}"
|
590
544
|
end
|
591
545
|
|
546
|
+
def open_method_missing (m, args)
|
547
|
+
|
548
|
+
return false unless self.class.instance_variable_get(:@open)
|
549
|
+
|
550
|
+
key = m.match(/^(.+)=$/)
|
551
|
+
|
552
|
+
if key && args.length == 1
|
553
|
+
@h[key[1]] = args.first
|
554
|
+
return [ true, args.first ]
|
555
|
+
end
|
556
|
+
if ( ! key) && args.length == 0
|
557
|
+
return [ true, @h[m] ]
|
558
|
+
end
|
559
|
+
|
560
|
+
false
|
561
|
+
end
|
562
|
+
|
563
|
+
def association_method_missing (m, args)
|
564
|
+
|
565
|
+
sm = m.singularize
|
566
|
+
multiple = (m != sm)
|
567
|
+
|
568
|
+
klass = sm.camelize
|
569
|
+
klass = (self.class.const_get(klass) rescue nil)
|
570
|
+
|
571
|
+
id_method = multiple ? "#{sm}_ids" : "#{m}_id"
|
572
|
+
|
573
|
+
unless klass
|
574
|
+
|
575
|
+
return false unless self.respond_to?(id_method)
|
576
|
+
|
577
|
+
i = self.send(id_method)
|
578
|
+
|
579
|
+
if multiple
|
580
|
+
|
581
|
+
return [ true, [] ] unless i
|
582
|
+
|
583
|
+
return [
|
584
|
+
true,
|
585
|
+
i.collect { |ii|
|
586
|
+
Rufus::Doric.instantiate(db.get(ii))
|
587
|
+
}.select { |e|
|
588
|
+
e != nil
|
589
|
+
}
|
590
|
+
]
|
591
|
+
end
|
592
|
+
|
593
|
+
return [ true, Rufus::Doric.instantiate(db.get(i)) ]
|
594
|
+
end
|
595
|
+
|
596
|
+
if multiple
|
597
|
+
|
598
|
+
if self.respond_to?(id_method)
|
599
|
+
|
600
|
+
ids = self.send(id_method)
|
601
|
+
|
602
|
+
[ true, ids ? ids.collect { |i| klass.find(i) } : [] ]
|
603
|
+
|
604
|
+
else
|
605
|
+
|
606
|
+
by_method = "by_#{self.class.doric_type.singularize}_id"
|
607
|
+
|
608
|
+
[ true, klass.send(by_method, self._id) ]
|
609
|
+
end
|
610
|
+
|
611
|
+
else
|
612
|
+
|
613
|
+
return false unless self.respond_to?(id_method)
|
614
|
+
|
615
|
+
id = self.send(id_method)
|
616
|
+
|
617
|
+
[ true, id ? klass.find(id) : nil ]
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
592
621
|
def self.func (body, type=:map)
|
593
622
|
|
594
623
|
if type == :map
|
@@ -755,7 +784,7 @@ module Doric
|
|
755
784
|
r = result['rows'].first
|
756
785
|
r ? r['value'] : nil
|
757
786
|
else
|
758
|
-
result['rows'].collect { |r| self.new(r['doc']) }
|
787
|
+
result['rows'].collect { |r| self.new(r['doc']) }.uniq
|
759
788
|
end
|
760
789
|
end
|
761
790
|
|
data/lib/rufus/doric/models.rb
CHANGED
@@ -126,6 +126,42 @@ module Doric
|
|
126
126
|
|
127
127
|
self.class.db
|
128
128
|
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
def do_attach (doc, attname, data, opts={})
|
133
|
+
|
134
|
+
extname = File.extname(attname)
|
135
|
+
basename = File.basename(attname, extname)
|
136
|
+
mime = ::MIME::Types.type_for(attname).first
|
137
|
+
|
138
|
+
if data.is_a?(File)
|
139
|
+
mime = ::MIME::Types.type_for(data.path).first
|
140
|
+
data = data.read
|
141
|
+
elsif data.is_a?(Array)
|
142
|
+
data, mime = data
|
143
|
+
mime = ::MIME::Types[mime].first
|
144
|
+
end
|
145
|
+
|
146
|
+
mime ||= (::MIME::Types[opts[:content_type]] || []).first
|
147
|
+
|
148
|
+
raise ArgumentError.new("couldn't determine mime type") unless mime
|
149
|
+
|
150
|
+
attname = "#{attname}.#{mime.extensions.first}" if extname == ''
|
151
|
+
|
152
|
+
if doc['_rev'] # document has already been saved
|
153
|
+
|
154
|
+
db.attach(
|
155
|
+
doc['_id'], doc['_rev'], attname, data, :content_type => mime.to_s)
|
156
|
+
|
157
|
+
else # document hasn't yet been saved, inline attachment...
|
158
|
+
|
159
|
+
(doc['_attachments'] ||= {})[attname] = {
|
160
|
+
'content_type' => mime.to_s,
|
161
|
+
'data' => Base64.encode64(data).gsub(/[\r\n]/, '')
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
129
165
|
end
|
130
166
|
|
131
167
|
#--
|
@@ -94,6 +94,18 @@ module Doric
|
|
94
94
|
delete
|
95
95
|
end
|
96
96
|
|
97
|
+
# Given a model named User, some usage examples :
|
98
|
+
#
|
99
|
+
# u = User.find('john')
|
100
|
+
# u.attach(File.read('avatar.png', :content_type => 'image/png'
|
101
|
+
# u.attach([ File.read('avatar.png', 'image/png' ])
|
102
|
+
#
|
103
|
+
def attach (data, opts={})
|
104
|
+
|
105
|
+
#def do_attach (doc, attname, data, opts={})
|
106
|
+
do_attach(db.get(self.class.doc_id), _id, data, opts)
|
107
|
+
end
|
108
|
+
|
97
109
|
#--
|
98
110
|
# class methods
|
99
111
|
#++
|
data/lib/rufus/doric/version.rb
CHANGED
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.
|
8
|
+
s.version = "0.1.16"
|
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-
|
12
|
+
s.date = %q{2010-07-12}
|
13
13
|
s.description = %q{
|
14
14
|
something at the intersection of Rails3, CouchDB and rufus-jig
|
15
15
|
}
|
@@ -59,6 +59,7 @@ something at the intersection of Rails3, CouchDB and rufus-jig
|
|
59
59
|
"test/ut_19_neutralize_id.rb",
|
60
60
|
"test/ut_1_model.rb",
|
61
61
|
"test/ut_20_model_view_by_ignore_case.rb",
|
62
|
+
"test/ut_21_open_model.rb",
|
62
63
|
"test/ut_2_model_view.rb",
|
63
64
|
"test/ut_3_model_lint.rb",
|
64
65
|
"test/ut_4_one_doc_model.rb",
|
@@ -25,6 +25,11 @@ class Team < Rufus::Doric::Model
|
|
25
25
|
view 'tysec2', %{
|
26
26
|
emit([ doc.type, doc.security_level ], null);
|
27
27
|
}
|
28
|
+
|
29
|
+
view 'duplicates', %{
|
30
|
+
emit(doc.type, null);
|
31
|
+
emit(doc.type, null);
|
32
|
+
}
|
28
33
|
end
|
29
34
|
|
30
35
|
#class City < Rufus::Doric::Model
|
@@ -99,5 +104,15 @@ class UtModelCustomViewTest < Test::Unit::TestCase
|
|
99
104
|
|
100
105
|
assert_equal 1, Team.by_tysec('rifle__b').size
|
101
106
|
end
|
107
|
+
|
108
|
+
def test_remove_duplicates
|
109
|
+
|
110
|
+
Team.new(
|
111
|
+
'type' => 'cleaning',
|
112
|
+
'security_level' => 'b'
|
113
|
+
).save!
|
114
|
+
|
115
|
+
assert_equal 1, Team.duplicates('cleaning').size
|
116
|
+
end
|
102
117
|
end
|
103
118
|
|
@@ -20,6 +20,7 @@ class Handkerchief < Rufus::Doric::Model
|
|
20
20
|
h_accessor :colour
|
21
21
|
h_accessor :owner
|
22
22
|
|
23
|
+
view_by :colour
|
23
24
|
view_by :owner, :ignore_case => true
|
24
25
|
end
|
25
26
|
|
@@ -41,18 +42,37 @@ class UtModelViewTest < Test::Unit::TestCase
|
|
41
42
|
|
42
43
|
def test_view_by_owner
|
43
44
|
|
45
|
+
load_data
|
46
|
+
|
47
|
+
assert_equal 1, Handkerchief.by_owner('pepe').size
|
48
|
+
assert_equal 2, Handkerchief.by_owner('jefe').size
|
49
|
+
|
50
|
+
assert_equal 0, Handkerchief.by_owner('Pepe').size
|
51
|
+
assert_equal 0, Handkerchief.by_owner('Jefe').size
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_view_by_colour
|
55
|
+
|
56
|
+
load_data
|
57
|
+
|
58
|
+
assert_equal 1, Handkerchief.by_colour('brown').size
|
59
|
+
assert_equal 1, Handkerchief.by_colour('Brown').size
|
60
|
+
assert_equal 1, Handkerchief.by_colour('browN').size
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def load_data
|
66
|
+
|
44
67
|
Handkerchief.new(
|
45
|
-
:brand => 'valentino', :colour => '
|
68
|
+
:brand => 'valentino', :colour => 'browN', :owner => 'Pepe'
|
46
69
|
).save!
|
47
70
|
Handkerchief.new(
|
48
71
|
:brand => 'lv', :colour => 'brown', :owner => 'Jefe'
|
49
72
|
).save!
|
50
73
|
Handkerchief.new(
|
51
|
-
:brand => 'chanel', :colour => '
|
74
|
+
:brand => 'chanel', :colour => 'Brown', :owner => 'jefe'
|
52
75
|
).save!
|
53
|
-
|
54
|
-
assert_equal 1, Handkerchief.by_owner('pepe').size
|
55
|
-
assert_equal 2, Handkerchief.by_owner('jefe').size
|
56
76
|
end
|
57
77
|
end
|
58
78
|
|
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# testing rufus-doric
|
4
|
+
#
|
5
|
+
# Wed Jun 30 15:05:56 JST 2010
|
6
|
+
#
|
7
|
+
|
8
|
+
require File.join(File.dirname(__FILE__), 'base')
|
9
|
+
|
10
|
+
require 'rufus/doric'
|
11
|
+
|
12
|
+
|
13
|
+
class Audit < Rufus::Doric::Model
|
14
|
+
|
15
|
+
db :doric
|
16
|
+
doric_type :audits
|
17
|
+
|
18
|
+
open
|
19
|
+
|
20
|
+
_id_field { "audit_#{name}" }
|
21
|
+
property :name
|
22
|
+
end
|
23
|
+
|
24
|
+
class Participant < Rufus::Doric::Model
|
25
|
+
|
26
|
+
db :doric
|
27
|
+
doric_type :audits
|
28
|
+
|
29
|
+
_id_field { "participant_#{name}" }
|
30
|
+
property :name
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
class UtOpenModelTest < Test::Unit::TestCase
|
35
|
+
|
36
|
+
def setup
|
37
|
+
|
38
|
+
Rufus::Doric.db('doric').delete('.')
|
39
|
+
Rufus::Doric.db('doric').put('.')
|
40
|
+
|
41
|
+
Rufus::Doric.db('doric').http.cache.clear
|
42
|
+
# CouchDB feeds the same etags for views, even after a db has
|
43
|
+
# been deleted and put back, so have to do that 'forgetting'
|
44
|
+
end
|
45
|
+
|
46
|
+
#def teardown
|
47
|
+
#end
|
48
|
+
|
49
|
+
def test_openness
|
50
|
+
|
51
|
+
a = Audit.new(:name => 'nada')
|
52
|
+
a.save!
|
53
|
+
|
54
|
+
assert_equal nil, a.clerk
|
55
|
+
assert_equal nil, a.whatever
|
56
|
+
|
57
|
+
a = Audit.all.first
|
58
|
+
a.clerk = 'Wilfried'
|
59
|
+
a.save!
|
60
|
+
|
61
|
+
assert_equal 'Wilfried', a.clerk
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_remove
|
65
|
+
|
66
|
+
a = Audit.new(:name => 'berlusconi')
|
67
|
+
a.h['country'] = 'x'
|
68
|
+
|
69
|
+
a.remove(:country)
|
70
|
+
|
71
|
+
assert_equal %w[ doric_type name ], a.h.keys.sort
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_remove_on_a_closed_model
|
75
|
+
|
76
|
+
Participant.new(:name => 'Joseph').save!
|
77
|
+
|
78
|
+
pa = Participant.all.first
|
79
|
+
|
80
|
+
assert_raise RuntimeError do
|
81
|
+
pa.remove(:nationality)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
data/test/ut_4_one_doc_model.rb
CHANGED
@@ -94,9 +94,15 @@ class UtOneDocModelTest < Test::Unit::TestCase
|
|
94
94
|
assert_equal [], User.all
|
95
95
|
end
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
def test_attach
|
98
|
+
|
99
|
+
u = User.find('john')
|
100
|
+
u.attach(File.read(__FILE__), :content_type => 'text/plain')
|
101
|
+
|
102
|
+
assert_match /justin/, u.db.get('users/john.txt')
|
103
|
+
|
104
|
+
u = User.find('john')
|
105
|
+
assert_equal 'john.txt', u.attachment
|
106
|
+
end
|
101
107
|
end
|
102
108
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 16
|
9
|
+
version: 0.1.16
|
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-
|
17
|
+
date: 2010-07-12 00:00:00 +09:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -146,6 +146,7 @@ files:
|
|
146
146
|
- test/ut_19_neutralize_id.rb
|
147
147
|
- test/ut_1_model.rb
|
148
148
|
- test/ut_20_model_view_by_ignore_case.rb
|
149
|
+
- test/ut_21_open_model.rb
|
149
150
|
- test/ut_2_model_view.rb
|
150
151
|
- test/ut_3_model_lint.rb
|
151
152
|
- test/ut_4_one_doc_model.rb
|