make_it_searchable 0.0.7.pre

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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in make_it_searchable.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ # Make It Searchable
2
+
3
+ 간단한 칼럼 filtering, sorting 기능, 아직 마무리 안됨.
4
+
5
+
6
+ ### Filtering
7
+ ```ruby
8
+ ActiveRecordModel.custom_filter({:column_name => "query_string", :another_column => 'another_query_string'})
9
+ ```
10
+ ### Sorting
11
+ ```ruby
12
+ ActiveRecordModel.custom_order("#{column_name}_asc")
13
+ ActiveRecordModel.custom_order("#{column_name}_desc")
14
+ ```
15
+
16
+ ### Filtering and Sorting
17
+ ```ruby
18
+ ActiveRecordModel.custom_query({:column_name => "query_string"}, "#{column_name}_desc")
19
+ ```
20
+
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,26 @@
1
+ require "active_support"
2
+ require "make_it_searchable/version"
3
+ require "make_it_searchable/class_methods"
4
+ require "make_it_searchable/generate_query"
5
+
6
+ require "make_it_searchable/validator"
7
+ require 'make_it_searchable/view_helper'
8
+
9
+ require "make_it_searchable/railtie" if defined? Rails
10
+
11
+ module MakeItSearchable
12
+ def self._extract_column_name_and_query_option(column_and_option)
13
+ reg = /\A(?<column>(.+))-(?<query_option>(eq|gt|lt|gte|lte|asc|desc))\Z/
14
+ md = reg.match(column_and_option.to_s)
15
+
16
+ if md
17
+ column, query_option = md[:column], md[:query_option]
18
+ else
19
+ column, query_option = column_and_option, nil
20
+ end
21
+
22
+ [column, query_option]
23
+ end
24
+ end
25
+
26
+
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module MakeItSearchable::ClassMethods
4
+
5
+ def make_it_search(sort_option, filter_option)
6
+ make_it_sort(sort_option).make_it_filter(filter_option)
7
+ end
8
+
9
+ def make_it_filter(options = nil)
10
+ custom_scope = scoped
11
+ if options.present?
12
+ options.each { |key, value| custom_scope = custom_scope._generate_query(key, value) }
13
+ custom_scope
14
+ end
15
+ custom_scope
16
+ end
17
+
18
+ def make_it_sort(name_and_option, default_order = "created_at DESC")
19
+ if _valid_sort_option?(name_and_option)
20
+ column_name, direction = MakeItSearchable._extract_column_name_and_query_option(name_and_option)
21
+ # direction = "_" if direction.nil?
22
+ order("#{column_name} #{direction}")
23
+ else
24
+ send(:order, default_order)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ module MakeItSearchable::GenerateQuery
3
+
4
+ def _generate_query(column_and_option, value)
5
+ return self.scoped unless value.present?
6
+ return self.scoped unless _valid_filter_option?(column_and_option)
7
+
8
+ column, query_option = MakeItSearchable._extract_column_name_and_query_option(column_and_option)
9
+
10
+ custom_scope = self.scoped
11
+ custom_scope = _add_inner_join(custom_scope, column)
12
+
13
+ data_type = self.columns.select {|col| col.name == column.to_s}.first.type rescue nil
14
+ if data_type == :datetime or data_type == :date
15
+ value = Time.zone.parse(value)
16
+ if query_option == "lt" || query_option == 'lte'
17
+ value = value.end_of_day
18
+ end
19
+ value
20
+ end
21
+
22
+ case query_option
23
+ when 'eq'
24
+ if value == 'nil'
25
+ custom_scope = custom_scope.where( column => nil)
26
+ elsif value == 'true' or value == 'false'
27
+ custom_scope = custom_scope.where( column => (value == 'true') )
28
+ else
29
+ custom_scope = custom_scope.where(column => value)
30
+ end
31
+ when 'gt'
32
+ custom_scope = custom_scope.where("#{column} > ?", value)
33
+ when 'lt'
34
+ custom_scope = custom_scope.where("#{column} < ?", value)
35
+ when 'gte'
36
+ custom_scope = custom_scope.where("#{column} >= ?", value)
37
+ when 'lte'
38
+ custom_scope = custom_scope.where("#{column} <= ?", value)
39
+ else
40
+ if value.to_s.match(/([\u3131-\u318E]|[\uAC00-\uD7A3])/) # if Korean
41
+ custom_scope = custom_scope.where("replace(#{column}, ' ', '') LIKE ?", "%#{value.to_s.gsub(/\s/, '')}%")
42
+ else
43
+ custom_scope = custom_scope.where("#{column} LIKE ?", "%#{value}%")
44
+ end
45
+ end
46
+
47
+ custom_scope
48
+ end
49
+
50
+ def _add_inner_join(custom_scope, column)
51
+ if column.to_s.include?(".")
52
+ join_table_name = column.split(".").first
53
+ associations_name = custom_scope.klass.reflect_on_all_associations.map(&:name)
54
+
55
+ if associations_name.include?(join_table_name.to_sym)
56
+ custom_scope.joins(join_table_name.to_sym)
57
+ else
58
+ custom_scope.joins(join_table_name.singularize.to_sym)
59
+ end
60
+
61
+ else
62
+ custom_scope
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module MakeItSearchable
4
+ class Railtie < Rails::Railtie
5
+
6
+ initializer 'make_it_searchable' do
7
+ ActiveSupport.on_load :active_record do
8
+ extend ClassMethods
9
+ extend Validator
10
+ extend GenerateQuery
11
+ end
12
+ end
13
+
14
+ initializer 'make_it_searchable.view_helper' do
15
+ ActionView::Base.send :include, ViewHelper
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ module MakeItSearchable::Validator
4
+ def _valid_sort_option?(name_and_option)
5
+ if name_and_option.present?
6
+ column_name, direction = MakeItSearchable._extract_column_name_and_query_option(name_and_option)
7
+ ["asc", "desc", nil].include?(direction) && column_names.include?(column_name.to_s)
8
+ else
9
+ false
10
+ end
11
+ end
12
+
13
+ def _valid_filter_option?(name_and_option)
14
+ if name_and_option.present?
15
+ column_name, option = MakeItSearchable._extract_column_name_and_query_option(name_and_option)
16
+ column_names.include?(column_name.to_s) or column_name.to_s.include?(".")
17
+ else
18
+ false
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,3 @@
1
+ module MakeItSearchable
2
+ VERSION = "0.0.7.pre"
3
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ module MakeItSearchable::ViewHelper
4
+ def get_direction(column_name, a_string)
5
+ reg = /\A(?<column_name>(.+))-(?<direction>(asc|desc))\Z/
6
+ md = reg.match(a_string)
7
+
8
+ if md && md[:column_name] == column_name && md[:direction]
9
+ md[:direction]
10
+ end
11
+ end
12
+
13
+ def toggle_direction(a_string)
14
+ reg = /-(?<direction>(asc|desc))\Z/
15
+ md = reg.match(a_string)
16
+
17
+ if md && md[:direction] == "asc"
18
+ a_string.sub(reg, "-desc")
19
+ elsif md && md[:direction] == "desc"
20
+ a_string.sub(reg, "-asc")
21
+ else
22
+ a_string
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "make_it_searchable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "make_it_searchable"
7
+ s.version = MakeItSearchable::VERSION
8
+ s.authors = ["KunHa"]
9
+ s.email = ["potato9@gmail.com"]
10
+ s.homepage = "https://github.com/bayja/make_it_searchable"
11
+ s.summary = %q{simple column filtering and sorting}
12
+ s.description = %q{simple column filtering and sorting.}
13
+
14
+ s.rubyforge_project = "make_it_searchable"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_development_dependency "activerecord"
23
+ s.add_development_dependency "guard"
24
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ class LectureReview < ActiveRecord::Base
5
+ extend MakeItSearchable::ClassMethods
6
+ end
7
+
8
+ describe MakeItSearchable::ClassMethods do
9
+ context "filtering and sorting" do
10
+ before :each do
11
+ LectureReview.destroy_all
12
+ @review_01 = LectureReview.create(:lecture_id => 3, :region_id => 2, :title => '이것은 제목입니다.', :body => "질럿이 짱 먹는다 1")
13
+ @review_02 = LectureReview.create(:lecture_id => 2, :region_id => 1, :title => '두번째 제목입니다. 정말로', :body => "뮤탈리스크 2")
14
+ @review_03 = LectureReview.create(:lecture_id => 1, :region_id => 3, :title => '세번째 제목입니다. 정말로', :body => "우리집 강아지 이름은 가루. 밀가루의 가루. 가루가 짱먹는다 3")
15
+ end
16
+
17
+ it 'should search title' do
18
+ LectureReview.make_it_filter('lecture_id-eq' => '2').should == [@review_02]
19
+ LectureReview.make_it_filter('region_id-eq' => '3').should == [@review_03]
20
+
21
+
22
+ LectureReview.make_it_filter(:title => '제목').count.should == 3
23
+ LectureReview.make_it_filter(:title => '정말로').count.should == 2
24
+ end
25
+
26
+ it 'should search with multiple search options' do
27
+ @review_03_1 = LectureReview.create(:lecture_id => 1, :region_id => 3, :title => '새로운 리뷰', :body => "3-1")
28
+ @review_03_2 = LectureReview.create(:lecture_id => 1, :region_id => 9, :title => '새로운 리뷰2', :body => "3-2")
29
+
30
+ LectureReview.make_it_filter('region_id-eq' => '3').count.should == 2
31
+ LectureReview.make_it_filter('region_id-eq' => '3', 'title' => '새로운').count.should == 1
32
+ end
33
+
34
+ it 'should ignoring white space when searching korean' do
35
+ LectureReview.make_it_filter(:title => '번째제목').count.should == 2
36
+ end
37
+
38
+ it 'should search title and body' do
39
+ LectureReview.make_it_filter(:title => '정말로', :body => '뮤탈리스크').count.should == 1
40
+ LectureReview.make_it_filter(:title => '제목', :body => '짱먹는다').count.should == 2
41
+ end
42
+
43
+ it 'should sort by region_id' do
44
+ LectureReview.make_it_sort("region_id-asc").should == [@review_02, @review_01, @review_03]
45
+ LectureReview.make_it_sort("region_id-desc").should == [@review_03, @review_01, @review_02]
46
+ LectureReview.make_it_sort("lecture_id-asc").should == [@review_03, @review_02, @review_01]
47
+ end
48
+
49
+ it 'should sort to asc if no direction is provided' do
50
+ LectureReview.make_it_sort("region_id").should == [@review_02, @review_01, @review_03]
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ class LectureReview < ActiveRecord::Base
5
+ extend MakeItSearchable::GenerateQuery
6
+ extend MakeItSearchable::Validator
7
+ belongs_to :lecture
8
+ end
9
+
10
+ class Lecture < ActiveRecord::Base
11
+ extend MakeItSearchable::GenerateQuery
12
+ extend MakeItSearchable::Validator
13
+ has_many :lecture_reviews
14
+ end
15
+
16
+ describe MakeItSearchable::GenerateQuery do
17
+
18
+ context 'translate query option' do
19
+ it 'should generate filter query' do
20
+ LectureReview._generate_query("lecture_id-eq", 2).to_sql.should == (
21
+ LectureReview.where(:lecture_id => 2).to_sql
22
+ )
23
+ LectureReview._generate_query("lecture_id-gte", 2).to_sql.should == (
24
+ LectureReview.where("lecture_id >= ?", "2").to_sql
25
+ )
26
+ LectureReview._generate_query("region_id-lt", 10).to_sql.should == (
27
+ LectureReview.where("region_id < ?", "10").to_sql
28
+ )
29
+ LectureReview._generate_query("title", '보리 출판사').to_sql.should == (
30
+ LectureReview.where("replace(title, ' ', '') LIKE ?", "%보리출판사%").to_sql
31
+ )
32
+ LectureReview._generate_query("title", 'English').to_sql.should == (
33
+ LectureReview.where("title LIKE ?", "%English%").to_sql
34
+ )
35
+ LectureReview._generate_query("won-eq", 'true').to_sql.should == (
36
+ LectureReview.where(:won => true).to_sql
37
+ )
38
+ LectureReview._generate_query("won-eq", 'false').to_sql.should == (
39
+ LectureReview.where(:won => false).to_sql
40
+ )
41
+ LectureReview._generate_query("region_id-eq", 'nil').to_sql.should == (
42
+ LectureReview.where(:region_id => nil).to_sql
43
+ )
44
+ LectureReview._generate_query("created_at-gt", '2011-1-1').to_sql.should == (
45
+ # LectureReview.where("created_at > ?", Time.zone.parse('2011-1-1')).to_sql
46
+ )
47
+ end
48
+
49
+ it 'should fall back to default scope when invalid filter option provied' do
50
+ LectureReview._generate_query("wrong_column_name", 2).to_sql.should == (
51
+ LectureReview.scoped.to_sql
52
+ )
53
+ end
54
+
55
+ it 'should fall back to default scope when no value has provided' do
56
+ LectureReview._generate_query("title", '').to_sql.should == (
57
+ LectureReview.scoped.to_sql
58
+ )
59
+ end
60
+ end
61
+
62
+ context 'translate join query options' do
63
+ it 'should generate join filter query' do
64
+ LectureReview._generate_query("lectures.title-eq", 2).to_sql.should == (
65
+ LectureReview.joins(:lecture).where("lectures.title" => 2).to_sql
66
+ )
67
+ end
68
+ end
69
+
70
+ context 'add inner join' do
71
+ it 'should add inner join query with sigular association name' do
72
+ LectureReview._add_inner_join(LectureReview.scoped, 'lectures.title').to_sql.should == (
73
+ LectureReview.scoped.joins(:lecture).to_sql
74
+ )
75
+ end
76
+
77
+ it 'should add inner join query with plural association name' do
78
+ Lecture._add_inner_join(Lecture.scoped, 'lecture_reviews.title').to_sql.should == (
79
+ Lecture.scoped.joins(:lecture_reviews).to_sql
80
+ )
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+
87
+
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ class LectureReview < ActiveRecord::Base
5
+ extend MakeItSearchable::Validator
6
+ end
7
+
8
+ describe MakeItSearchable::Validator do
9
+ it 'should validate sort option' do
10
+ LectureReview._valid_sort_option?("region_id-asc").should be_true
11
+ LectureReview._valid_sort_option?("lecture_id-desc").should be_true
12
+ LectureReview._valid_sort_option?("lecture_id-aeae").should be_false
13
+ LectureReview._valid_sort_option?("wrong_column_name-asc").should be_false
14
+
15
+ LectureReview._valid_sort_option?("region_id").should be_true # rails default
16
+ end
17
+
18
+ it 'should validate filter option' do
19
+ LectureReview._valid_filter_option?("lecture_id-eq").should be_true
20
+ LectureReview._valid_filter_option?("lecture_id-gt").should be_true
21
+ LectureReview._valid_filter_option?("lecture_id").should be_true
22
+
23
+ LectureReview._valid_filter_option?("wrong_column_name").should be_false
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ class Object
4
+ include MakeItSearchable::ViewHelper
5
+ end
6
+
7
+ describe MakeItSearchable::ViewHelper do
8
+ before :each do
9
+ @view = Object.new
10
+ end
11
+
12
+ it 'should toggle direction' do
13
+ @view.toggle_direction('column_name-asc').should == "column_name-desc"
14
+ @view.toggle_direction('asc_asc-asc').should == "asc_asc-desc"
15
+ @view.toggle_direction('column_name-desc').should == "column_name-asc"
16
+ end
17
+
18
+ it "should do nothing to the string" do
19
+ @view.toggle_direction('column_name').should == "column_name"
20
+ end
21
+
22
+ it 'should extract direction if column_name matches' do
23
+ @view.get_direction("column_name", "column_name-asc").should == "asc"
24
+ @view.get_direction("diffrent_column", "column_name-asc").should == nil
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe MakeItSearchable do
6
+ it 'should extract column name and option from a string' do
7
+ MakeItSearchable._extract_column_name_and_query_option("region_id-gt").should == ['region_id', 'gt']
8
+ MakeItSearchable._extract_column_name_and_query_option("region_id-eq").should == ['region_id', 'eq']
9
+ MakeItSearchable._extract_column_name_and_query_option("my_column_name-asc").should == ['my_column_name', 'asc']
10
+ MakeItSearchable._extract_column_name_and_query_option("my_column_name").should == ['my_column_name', nil]
11
+ end
12
+
13
+ it 'should extract join table options too' do
14
+ MakeItSearchable._extract_column_name_and_query_option("lectures.name-eq").should == ['lectures.name', 'eq']
15
+ MakeItSearchable._extract_column_name_and_query_option("lectures.name").should == ['lectures.name', nil]
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ require 'make_it_searchable'
2
+ require 'active_record'
3
+
4
+ ActiveRecord::Base.establish_connection(
5
+ :adapter => "sqlite3",
6
+ :database => "make_it_searchable.sqlite3"
7
+ )
8
+
9
+ class CreateTestData < ActiveRecord::Migration
10
+ def self.up
11
+ create_table :lecture_reviews do |t|
12
+ t.integer :lecture_id
13
+ t.integer :region_id
14
+ t.string :title
15
+ t.string :body
16
+ t.boolean :won
17
+ t.boolean :best_review
18
+
19
+ t.datetime :created_at
20
+ t.datetime :updated_at
21
+ end
22
+
23
+ create_table :lectures do |t|
24
+ t.string :title
25
+ t.text :body
26
+ end
27
+ end
28
+ end
29
+
30
+ # CreateTestData.up
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: make_it_searchable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7.pre
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - KunHa
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: guard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: simple column filtering and sorting.
63
+ email:
64
+ - potato9@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .rspec
71
+ - Gemfile
72
+ - README.md
73
+ - Rakefile
74
+ - lib/make_it_searchable.rb
75
+ - lib/make_it_searchable/class_methods.rb
76
+ - lib/make_it_searchable/generate_query.rb
77
+ - lib/make_it_searchable/railtie.rb
78
+ - lib/make_it_searchable/validator.rb
79
+ - lib/make_it_searchable/version.rb
80
+ - lib/make_it_searchable/view_helper.rb
81
+ - make_it_searchable.gemspec
82
+ - make_it_searchable.sqlite3
83
+ - spec/make_it_searchable/class_methods_spec.rb
84
+ - spec/make_it_searchable/generate_query_spec.rb
85
+ - spec/make_it_searchable/validator_spec.rb
86
+ - spec/make_it_searchable/view_helper_spec.rb
87
+ - spec/make_it_searchable_spec.rb
88
+ - spec/spec_helper.rb
89
+ homepage: https://github.com/bayja/make_it_searchable
90
+ licenses: []
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>'
105
+ - !ruby/object:Gem::Version
106
+ version: 1.3.1
107
+ requirements: []
108
+ rubyforge_project: make_it_searchable
109
+ rubygems_version: 1.8.24
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: simple column filtering and sorting
113
+ test_files:
114
+ - spec/make_it_searchable/class_methods_spec.rb
115
+ - spec/make_it_searchable/generate_query_spec.rb
116
+ - spec/make_it_searchable/validator_spec.rb
117
+ - spec/make_it_searchable/view_helper_spec.rb
118
+ - spec/make_it_searchable_spec.rb
119
+ - spec/spec_helper.rb
120
+ has_rdoc: