dase 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dase.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Vladimir Yartsev
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Dase
2
+
3
+ The gem is named by the german mathematician [Johann Dase](http://en.wikipedia.org/wiki/Zacharias_Dase),
4
+ who was a mental calculator - he could count and multiply numbers very quickly. Dase gem adds extra speed
5
+ to Active Record whenever you need to calculate the number of associated records.
6
+
7
+ Here's an example of the code that will cause N + 1 queries:
8
+
9
+ ```
10
+ Author.find_each do |author|
11
+ cnt = author.books.where(year: 1992).count
12
+ puts "#{author.name} has published #{cnt} books in 1992"
13
+ end
14
+ ```
15
+
16
+ Active Record has a built-in solution for efficient fetching of
17
+ associated records - see [Rails Guides](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations)
18
+ ```
19
+ authors = Author.includes(:books) # => will cause only 2 queries
20
+ authors.first
21
+ ```
22
+
23
+ The Dase gem provides a similar solution for the efficient counting of associated records:
24
+ ```
25
+ authors = Author.includes_count_of(:books) # => will cause only 2 queries
26
+ authors.first.books_count
27
+ ```
28
+
29
+
30
+ ## Installation
31
+
32
+ Add this line to your application's Gemfile:
33
+
34
+ gem 'dase', "~> 3.2.0"
35
+
36
+ And then execute:
37
+
38
+ $ bundle
39
+
40
+ Or install it yourself as:
41
+
42
+ $ gem install dase
43
+
44
+ ### Note on version numbers
45
+
46
+ Dase version number correlates with the Active Record's versions number,
47
+ which it has been tested with.
48
+ E.g. the latest 3.2.* version of Dase will play nicely with the latest 3.2.* version of Active Record.
49
+ Since it's a sort of a "hack", make sure you specified the version number for "dase" gem in your Gemfile.
50
+
51
+ ## Usage
52
+
53
+ ### Basic usage:
54
+
55
+ ```
56
+ Author.includes_count_of(:books).find_each do |author|
57
+ puts "#{author.name} has #{author.books_count} books published"
58
+ end
59
+ ```
60
+
61
+ ### Advanced usage:
62
+
63
+ You can specify a hash of options which will be passed to the underlying finder
64
+ which retrieves the association. Valid keys are: :conditions, :group, :having, :joins, :include
65
+ ```
66
+ Author.includes_count_of(:books, :conditions => {:year => 1990})
67
+ ```
68
+
69
+ ### Known problems
70
+
71
+ 1. Dase doesn't support :through option on associations
72
+ 2. You can't put includes_count_of calls into a scope declaration, like this:
73
+
74
+ ```
75
+ class Author
76
+ scope :with_counters, lambda {
77
+ includes_count_of(:books) # this will not work!!!
78
+ }
79
+ end
80
+ ```
81
+
82
+ ## How it works
83
+
84
+ The equivalent code would look something like this:
85
+ ```
86
+ counters_hash = Book.count(:group => :author_id)
87
+ Author.find_each do |author|
88
+ puts "#{author.name} has #{counters_hash[author.id] || 0} books published"
89
+ end
90
+ ```
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+ require 'rake/testtask'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'test'
9
+ end
10
+
11
+ task :default => :test
data/dase.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dase/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "dase"
8
+ gem.version = Dase::VERSION
9
+ gem.authors = ["Vladimir Yartsev"]
10
+ gem.email = ["vovayartsev@gmail.com"]
11
+ gem.description = %q{A solution for N+1 querying problem in Active Record associated records counting}
12
+ gem.summary = %q{Really fast associated records counting}
13
+ gem.homepage = "https://github.com/vovayartsev"
14
+
15
+ gem.add_runtime_dependency "activerecord", "~> 3.2.0"
16
+ gem.add_runtime_dependency "activesupport", "~> 3.2.0"
17
+ gem.add_development_dependency 'shoulda'
18
+ gem.add_development_dependency 'sqlite3', '~> 1.3.3'
19
+ gem.add_development_dependency 'debugger'
20
+
21
+ gem.files = `git ls-files`.split($/)
22
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
24
+ gem.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,38 @@
1
+ # Monkey-patching ActiveRecord::Base to add xxx_count methods to it
2
+
3
+ module Dase
4
+ module ARBaseInstanceMethods
5
+ def set_dase_counter(name, value)
6
+ @dase_counters ||= {}
7
+ @dase_counters[name] = value
8
+ end
9
+
10
+ def get_dase_counter(name)
11
+ @dase_counters[name]
12
+ end
13
+
14
+ def respond_to?(*args)
15
+ @dase_counters && @dase_counters.has_key?(args.first.to_sym) || super
16
+ end
17
+
18
+ def method_missing(name, *args)
19
+ if @dase_counters and @dase_counters.has_key?(name.to_sym)
20
+ get_dase_counter(name, *args)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def self.included(klass)
29
+ class << klass
30
+ delegate :includes_count_of, :to => :scoped
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ ActiveRecord::Base.class_eval do
37
+ include Dase::ARBaseInstanceMethods
38
+ end
@@ -0,0 +1,40 @@
1
+ module Dase
2
+ module ARRelationInstanceMethods
3
+ attr_accessor :dase_values
4
+
5
+ def includes_count_of(*args)
6
+ args.reject! { |a| a.blank? }
7
+ options = args.extract_options!
8
+ options.assert_valid_keys(:conditions, :group, :having, :limit, :offset, :joins, :include, :from, :lock)
9
+ return self if args.empty?
10
+ relation = clone
11
+ relation.dase_values ||= {}
12
+ args.each { |a| relation.dase_values[a] = options}
13
+ relation
14
+ end
15
+
16
+ def attach_dase_counters_to_records
17
+ if dase_values.present? and !@has_dase_counters
18
+ dase_values.each do |associations, options|
19
+ Dase::Preloader.new(@records, associations, options).run
20
+ end
21
+ @has_dase_counters = true
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+
28
+ ActiveRecord::Relation.class_eval do
29
+ include Dase::ARRelationInstanceMethods
30
+
31
+ private
32
+
33
+ alias_method :exec_queries_before_dase, :exec_queries
34
+
35
+ def exec_queries
36
+ exec_queries_before_dase # original method from ActiveRecord
37
+ attach_dase_counters_to_records
38
+ @records
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ module Dase
2
+ class Preloader < ::ActiveRecord::Associations::Preloader
3
+
4
+ # custom preloaders for different association types
5
+ class HasMany < ::ActiveRecord::Associations::Preloader::HasMany
6
+ include Dase::PreloaderMethods
7
+ end
8
+ class HasAndBelongsToMany < ::ActiveRecord::Associations::Preloader::HasAndBelongsToMany
9
+ include Dase::PreloaderMethods
10
+ end
11
+
12
+ # Not implemented yet
13
+ #class HasManyThrough < ::ActiveRecord::Associations::Preloader::HasManyThrough
14
+ # include Dase::PreloaderMethods
15
+ #end
16
+
17
+ # an overloaded version of ActiveRecord::Associations::Preloader's preloader_for
18
+ # which returns a class of a custom preloader for a given association
19
+ def preloader_for(reflection)
20
+ case reflection.macro
21
+ when :has_many
22
+ if reflection.options[:through]
23
+ raise NotImplementedError, "The support for HasManyThrough associations is not implemented yet"
24
+ else
25
+ HasMany
26
+ end
27
+ when :has_one, :belongs_to
28
+ raise ArgumentError, "You can't use includes_count_of with a #{reflection.macro} association"
29
+ when :has_and_belongs_to_many
30
+ HasAndBelongsToMany
31
+ end
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,15 @@
1
+ module Dase
2
+ module PreloaderMethods
3
+ def preload
4
+ counter_name = "#{reflection.name}_count".to_sym
5
+ pk = model.primary_key.to_sym
6
+ ids = owners.map(&pk)
7
+ fk = "#{scoped.quoted_table_name}.#{reflection.foreign_key}"
8
+ counters_hash = records_for(ids).count(group: fk)
9
+ owners.each do |owner|
10
+ value = counters_hash[owner[pk]] || 0 # 0 is "default count", when no records found
11
+ owner.set_dase_counter(counter_name, value)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Dase
2
+ VERSION = "3.2.1"
3
+ end
data/lib/dase.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "dase/version"
2
+ require "dase/preloader_methods"
3
+ require "dase/preloader"
4
+ require "dase/mp_active_record_base"
5
+ require "dase/mp_active_record_relation"
6
+
7
+ module Dase
8
+ # take a look at mp_active_record_* files
9
+ # (there we monkey-patch Active Record)
10
+ end
@@ -0,0 +1,9 @@
1
+ class Author < ActiveRecord::Base
2
+ attr_accessible :name
3
+
4
+ has_many :books
5
+ has_many :old_books, :class_name => "Book", :conditions => {:year => 1990}
6
+
7
+ has_many :quotes, :through => :books
8
+
9
+ end
@@ -0,0 +1,20 @@
1
+ joe:
2
+ name : Joe
3
+ id : 1
4
+ created_at: 1999-02-19 08:00
5
+ updated_at: 1999-02-19 08:00
6
+ # has 3 books: two old (year 1990) and one new (year 2012) book
7
+
8
+ bobby:
9
+ name : Bobby
10
+ id : 2
11
+ created_at: 1999-02-19 08:00
12
+ updated_at: 1999-02-19 08:00
13
+ # has 1 old book (year 1990)
14
+
15
+ teddy:
16
+ name : Teddy
17
+ id : 3
18
+ created_at: 1999-02-19 08:00
19
+ updated_at: 1999-02-19 08:00
20
+ # has no books at all
@@ -0,0 +1,7 @@
1
+ class Book < ActiveRecord::Base
2
+ attr_accessible :title, :year
3
+
4
+ belongs_to :author
5
+ has_many :quotes
6
+
7
+ end
@@ -0,0 +1,34 @@
1
+ # Joe's books
2
+ first:
3
+ id : 1
4
+ year : 1990
5
+ title : First book
6
+ author_id : 1
7
+ created_at: 1999-02-19 08:00
8
+ updated_at: 1999-02-19 08:00
9
+
10
+ second:
11
+ id : 2
12
+ year : 1990
13
+ title : Second book
14
+ author_id : 1
15
+ created_at: 1999-02-19 08:00
16
+ updated_at: 1999-02-19 08:00
17
+
18
+ third:
19
+ id : 3
20
+ year : 2012
21
+ title : Third book
22
+ author_id : 1
23
+ created_at: 1999-02-19 08:00
24
+ updated_at: 1999-02-19 08:00
25
+
26
+
27
+ # Bobby's book
28
+ fourth:
29
+ id : 4
30
+ year : 1990
31
+ title : Fourth book
32
+ author_id : 2
33
+ created_at: 1999-02-19 08:00
34
+ updated_at: 1999-02-19 08:00
@@ -0,0 +1,4 @@
1
+ class Quote < ActiveRecord::Base
2
+ belongs_to :book
3
+
4
+ end
@@ -0,0 +1,16 @@
1
+ # Joe's quotation
2
+ first:
3
+ id: 1
4
+ book_id: 1
5
+ snippet: "To be or not to be, that is the question"
6
+
7
+ # Bobby's quotations
8
+ second:
9
+ id: 2
10
+ book_id: 4
11
+ snippet: "No matter how much cats fight, there always seem to be plenty of kittens"
12
+
13
+ third:
14
+ id: 3
15
+ book_id: 4
16
+ snippet: "I am fond of pigs. Dogs look up to us. Cats look down on us. Pigs treat us as equals."
@@ -0,0 +1,22 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table "authors", :force => true do |t|
4
+ t.string "name"
5
+ t.datetime "created_at", :null => false
6
+ t.datetime "updated_at", :null => false
7
+ end
8
+
9
+ create_table "books", :force => true do |t|
10
+ t.integer "year"
11
+ t.string "title"
12
+ t.integer "author_id"
13
+ t.datetime "created_at", :null => false
14
+ t.datetime "updated_at", :null => false
15
+ end
16
+
17
+ create_table "quotes", :force => true do |t|
18
+ t.string "snippet"
19
+ t.integer "book_id"
20
+ end
21
+
22
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,30 @@
1
+ # borrowed from https://github.com/ernie/meta_where/blob/master/test/helper.rb
2
+ # with slight modifications
3
+ require "rubygems"
4
+ require "bundler"
5
+ Bundler.setup
6
+ require 'test/unit'
7
+ require 'shoulda'
8
+ require 'active_record'
9
+ require 'active_record/fixtures'
10
+ require 'dase'
11
+
12
+ FIXTURES_PATH = File.join(File.dirname(__FILE__), 'fixtures')
13
+
14
+ ActiveRecord::Base.establish_connection(
15
+ :adapter => defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3',
16
+ :database => ':memory:'
17
+ )
18
+
19
+ dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies
20
+ dep.autoload_paths.unshift FIXTURES_PATH
21
+
22
+ ActiveRecord::Base.silence do
23
+ ActiveRecord::Migration.verbose = false
24
+ load File.join(FIXTURES_PATH, 'schema.rb')
25
+ end
26
+
27
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_PATH, ActiveRecord::Base.connection.tables)
28
+
29
+ class Test::Unit::TestCase
30
+ end
data/test/test_dase.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'helper'
2
+
3
+ class TestBase < Test::Unit::TestCase
4
+
5
+ def compare_counts(traditional, dase, true_counts)
6
+ assert_equal true_counts, traditional, "traditional counting failed - fixtures not loaded"
7
+ assert_equal true_counts, dase, "dase countings failed"
8
+ end
9
+
10
+ context "includes_count_of" do
11
+ setup do
12
+ @bobby, @joe, @teddy = Author.order(:name).all
13
+ end
14
+
15
+ should "count books" do
16
+ traditional_counts = Author.order(:name).map { |a| a.books.count }
17
+ dase_counts = Author.includes_count_of(:books).order(:name).map { |a| a.books_count }
18
+ # the order is: Bobby, Joe, Teddy - due to order(:name)
19
+ true_counts = [1, 3, 0] # see books.yml
20
+ compare_counts(traditional_counts, dase_counts, true_counts)
21
+ end
22
+
23
+ should "respond_to the counter method" do
24
+ assert_equal true, Author.includes_count_of(:books).first.respond_to?(:books_count), "doesn't respond'"
25
+ end
26
+
27
+ should "count old books" do
28
+ traditional_counts = Author.order(:name).map { |a| a.old_books.count }
29
+ dase_counts = Author.includes_count_of(:old_books).order(:name).map { |a| a.old_books_count }
30
+ # the order is: Bobby, Joe, Teddy - due to order(:name)
31
+ true_counts = [1, 2, 0] # see books.yml
32
+ compare_counts(traditional_counts, dase_counts, true_counts)
33
+ end
34
+
35
+ should "count books for year 1990" do
36
+ traditional_counts = Author.order(:name).map { |a| a.books.where(year: 1990).count }
37
+ dase_counts = Author.includes_count_of(:books, :conditions => {:year => 1990}).order(:name).map { |a| a.books_count }
38
+ # the order is: Bobby, Joe, Teddy - due to order(:name)
39
+ true_counts = [1, 2, 0] # see books.yml
40
+ compare_counts(traditional_counts, dase_counts, true_counts)
41
+ end
42
+
43
+ # Not yet implemented
44
+ #should "count quotations" do
45
+ # traditional_counts = Author.order(:name).map { |a| a.quotes.count }
46
+ # dase_counts = Author.order(:name).includes_count_of(:quotes).map { |a| a.quotes_count }
47
+ # # the order is: Bobby, Joe, Teddy - due to order(:name)
48
+ # true_counts = [2, 1, 0] # see quotes.yml
49
+ # compare_counts(traditional_counts, dase_counts, true_counts)
50
+ #end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dase
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.2.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Vladimir Yartsev
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &70291652811140 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70291652811140
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70291652810620 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 3.2.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70291652810620
36
+ - !ruby/object:Gem::Dependency
37
+ name: shoulda
38
+ requirement: &70291652810220 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70291652810220
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3
49
+ requirement: &70291652808960 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.3
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70291652808960
58
+ - !ruby/object:Gem::Dependency
59
+ name: debugger
60
+ requirement: &70291652808360 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70291652808360
69
+ description: A solution for N+1 querying problem in Active Record associated records
70
+ counting
71
+ email:
72
+ - vovayartsev@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - dase.gemspec
83
+ - lib/dase.rb
84
+ - lib/dase/mp_active_record_base.rb
85
+ - lib/dase/mp_active_record_relation.rb
86
+ - lib/dase/preloader.rb
87
+ - lib/dase/preloader_methods.rb
88
+ - lib/dase/version.rb
89
+ - test/fixtures/author.rb
90
+ - test/fixtures/authors.yml
91
+ - test/fixtures/book.rb
92
+ - test/fixtures/books.yml
93
+ - test/fixtures/quote.rb
94
+ - test/fixtures/quotes.yml
95
+ - test/fixtures/schema.rb
96
+ - test/helper.rb
97
+ - test/test_dase.rb
98
+ homepage: https://github.com/vovayartsev
99
+ licenses: []
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 1.8.11
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Really fast associated records counting
122
+ test_files:
123
+ - test/fixtures/author.rb
124
+ - test/fixtures/authors.yml
125
+ - test/fixtures/book.rb
126
+ - test/fixtures/books.yml
127
+ - test/fixtures/quote.rb
128
+ - test/fixtures/quotes.yml
129
+ - test/fixtures/schema.rb
130
+ - test/helper.rb
131
+ - test/test_dase.rb