ransack 1.1.0 → 1.2.0

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +12 -4
  3. data/CONTRIBUTING.md +10 -4
  4. data/Gemfile +12 -9
  5. data/README.md +46 -11
  6. data/lib/ransack.rb +4 -2
  7. data/lib/ransack/adapters/active_record.rb +1 -1
  8. data/lib/ransack/adapters/active_record/3.0/compat.rb +16 -6
  9. data/lib/ransack/adapters/active_record/3.0/context.rb +32 -16
  10. data/lib/ransack/adapters/active_record/3.1/context.rb +32 -15
  11. data/lib/ransack/adapters/active_record/3.2/context.rb +1 -1
  12. data/lib/ransack/adapters/active_record/base.rb +9 -6
  13. data/lib/ransack/adapters/active_record/context.rb +193 -2
  14. data/lib/ransack/configuration.rb +4 -4
  15. data/lib/ransack/constants.rb +81 -18
  16. data/lib/ransack/context.rb +27 -12
  17. data/lib/ransack/helpers/form_builder.rb +126 -91
  18. data/lib/ransack/helpers/form_helper.rb +34 -12
  19. data/lib/ransack/naming.rb +2 -1
  20. data/lib/ransack/nodes/attribute.rb +6 -4
  21. data/lib/ransack/nodes/bindable.rb +3 -1
  22. data/lib/ransack/nodes/condition.rb +40 -27
  23. data/lib/ransack/nodes/grouping.rb +19 -13
  24. data/lib/ransack/nodes/node.rb +3 -3
  25. data/lib/ransack/nodes/sort.rb +5 -3
  26. data/lib/ransack/nodes/value.rb +2 -2
  27. data/lib/ransack/predicate.rb +18 -9
  28. data/lib/ransack/ransacker.rb +4 -4
  29. data/lib/ransack/search.rb +9 -12
  30. data/lib/ransack/translate.rb +42 -21
  31. data/lib/ransack/version.rb +1 -1
  32. data/lib/ransack/visitor.rb +4 -4
  33. data/ransack.gemspec +17 -7
  34. data/spec/blueprints/notes.rb +2 -0
  35. data/spec/blueprints/people.rb +4 -1
  36. data/spec/console.rb +3 -3
  37. data/spec/ransack/adapters/active_record/base_spec.rb +149 -22
  38. data/spec/ransack/adapters/active_record/context_spec.rb +5 -5
  39. data/spec/ransack/configuration_spec.rb +17 -8
  40. data/spec/ransack/dependencies_spec.rb +8 -0
  41. data/spec/ransack/helpers/form_builder_spec.rb +37 -14
  42. data/spec/ransack/helpers/form_helper_spec.rb +5 -5
  43. data/spec/ransack/predicate_spec.rb +6 -3
  44. data/spec/ransack/search_spec.rb +95 -73
  45. data/spec/ransack/translate_spec.rb +14 -0
  46. data/spec/spec_helper.rb +14 -8
  47. data/spec/support/en.yml +6 -0
  48. data/spec/support/schema.rb +76 -31
  49. metadata +48 -29
@@ -8,25 +8,35 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Ernie Miller", "Ryan Bigg"]
10
10
  s.email = ["ernie@erniemiller.org", "radarlistener@gmail.com"]
11
- s.homepage = "https://github.com/ernie/ransack"
11
+ s.homepage = "https://github.com/activerecord-hackery/ransack"
12
12
  s.summary = %q{Object-based searching for ActiveRecord (currently).}
13
13
  s.description = %q{Ransack is the successor to the MetaSearch gem. It improves and expands upon MetaSearch's functionality, but does not have a 100%-compatible API.}
14
+ s.license = 'MIT'
14
15
 
15
16
  s.rubyforge_project = "ransack"
16
17
 
17
- s.add_dependency 'activerecord', '>= 3.0'
18
18
  s.add_dependency 'actionpack', '>= 3.0'
19
- s.add_dependency 'polyamorous', '~> 0.6.0'
19
+ s.add_dependency 'activerecord', '>= 3.0'
20
+ s.add_dependency 'activesupport', '>= 3.0'
21
+ s.add_dependency 'i18n'
22
+ # s.add_dependency 'polyamorous', '~> 0.6.0'
20
23
  s.add_development_dependency 'rspec', '~> 2.8.0'
21
24
  s.add_development_dependency 'machinist', '~> 1.0.6'
22
25
  s.add_development_dependency 'faker', '~> 0.9.5'
23
26
  s.add_development_dependency 'sqlite3', '~> 1.3.3'
24
27
  s.add_development_dependency 'pg', '0.17.0'
25
- s.add_development_dependency 'mysql2', '0.3.13'
28
+ s.add_development_dependency 'mysql2', '0.3.14'
26
29
  s.add_development_dependency 'pry', '0.9.12.2'
27
30
 
28
- s.files = `git ls-files`.split("\n")
29
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
31
+ s.files = `git ls-files`
32
+ .split("\n")
33
+
34
+ s.test_files = `git ls-files -- {test,spec,features}/*`
35
+ .split("\n")
36
+
37
+ s.executables = `git ls-files -- bin/*`
38
+ .split("\n")
39
+ .map { |f| File.basename(f) }
40
+
31
41
  s.require_paths = ["lib"]
32
42
  end
@@ -1,3 +1,5 @@
1
1
  Note.blueprint do
2
2
  note
3
+ notable_type { "Article" }
4
+ notable_id
3
5
  end
@@ -2,4 +2,7 @@ Person.blueprint do
2
2
  name
3
3
  email { "test@example.com" }
4
4
  salary
5
- end
5
+ only_sort
6
+ only_search
7
+ only_admin
8
+ end
@@ -4,7 +4,8 @@ require 'sham'
4
4
  require 'faker'
5
5
  require 'ransack'
6
6
 
7
- Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
7
+ Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)]
8
+ .each do |f|
8
9
  require f
9
10
  end
10
11
 
@@ -12,10 +13,9 @@ Sham.define do
12
13
  name { Faker::Name.name }
13
14
  title { Faker::Lorem.sentence }
14
15
  body { Faker::Lorem.paragraph }
15
- salary {|index| 30000 + (index * 1000)}
16
+ salary { |index| 30000 + (index * 1000) }
16
17
  tag_name { Faker::Lorem.words(3).join(' ') }
17
18
  note { Faker::Lorem.words(7).join(' ') }
18
19
  end
19
20
 
20
21
  Schema.create
21
-
@@ -23,42 +23,69 @@ module Ransack
23
23
  # For infix tests
24
24
  def self.sane_adapter?
25
25
  case ::ActiveRecord::Base.connection.adapter_name
26
- when "SQLite3" || "PostgreSQL"
27
- true
28
- else
29
- false
26
+ when "SQLite3", "PostgreSQL"
27
+ true
28
+ else
29
+ false
30
30
  end
31
31
  end
32
32
  # in schema.rb, class Person:
33
- # ransacker :reversed_name, :formatter => proc {|v| v.reverse} do |parent|
33
+ # ransacker :reversed_name, formatter: proc { |v| v.reverse } do |parent|
34
34
  # parent.table[:name]
35
35
  # end
36
36
  #
37
37
  # ransacker :doubled_name do |parent|
38
38
  # Arel::Nodes::InfixOperation.new('||', parent.table[:name], parent.table[:name])
39
39
  # end
40
+
40
41
  it 'creates ransack attributes' do
41
- s = Person.search(:reversed_name_eq => 'htimS cirA')
42
+ s = Person.search(reversed_name_eq: 'htimS cirA')
42
43
  s.result.should have(1).person
43
- s.result.first.should eq Person.find_by_name('Aric Smith')
44
+
45
+ if ::ActiveRecord::VERSION::STRING >= "4"
46
+ s.result.first.should eq Person.find_by(name: 'Aric Smith')
47
+ else
48
+ s.result.first.should eq Person.find_by_name('Aric Smith')
49
+ end
44
50
  end
45
51
 
46
52
  it 'can be accessed through associations' do
47
- s = Person.search(:children_reversed_name_eq => 'htimS cirA')
48
-
49
- s.result.to_sql.should match /#{quote_table_name("children_people")}.#{quote_column_name("name")} = 'Aric Smith'/
53
+ s = Person.search(children_reversed_name_eq: 'htimS cirA')
54
+ s.result.to_sql.should match(
55
+ /#{quote_table_name("children_people")}.#{quote_column_name("name")} = 'Aric Smith'/
56
+ )
50
57
  end
51
58
 
52
59
  it 'allows an "attribute" to be an InfixOperation' do
53
- s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
54
- s.result.first.should eq Person.find_by_name('Aric Smith')
60
+ s = Person.search(doubled_name_eq: 'Aric SmithAric Smith')
61
+ if ::ActiveRecord::VERSION::STRING >= "4"
62
+ s.result.first.should eq Person.find_by(name: 'Aric Smith')
63
+ else
64
+ s.result.first.should eq Person.find_by_name('Aric Smith')
65
+ end
55
66
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
56
67
 
57
68
  it "doesn't break #count if using InfixOperations" do
58
- s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith')
69
+ s = Person.search(doubled_name_eq: 'Aric SmithAric Smith')
59
70
  s.result.count.should eq 1
60
71
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
61
72
 
73
+ if ::ActiveRecord::VERSION::STRING >= "4"
74
+ it "should remove empty key value pairs from the params hash" do
75
+ s = Person.search(children_reversed_name_eq: '')
76
+ s.result.to_sql.should_not match /LEFT OUTER JOIN/
77
+ end
78
+
79
+ it "should keep proper key value pairs in the params hash" do
80
+ s = Person.search(children_reversed_name_eq: 'Testing')
81
+ s.result.to_sql.should match /LEFT OUTER JOIN/
82
+ end
83
+
84
+ it "should function correctly when nil is passed in" do
85
+ s = Person.search(nil)
86
+ end
87
+ end
88
+
62
89
  it "should function correctly when using fields with dots in them" do
63
90
  s = Person.search(email_cont: "example.com")
64
91
  s.result.exists?.should be_true
@@ -75,22 +102,122 @@ module Ransack
75
102
  s = Person.search(name_cont: "\\WINNER\\")
76
103
  s.result.exists?.should be_true
77
104
  end
105
+
106
+ it 'allows sort by "only_sort" field' do
107
+ s = Person.search(
108
+ "s" => { "0" => { "dir" => "asc", "name" => "only_sort" } }
109
+ )
110
+ s.result.to_sql.should match(
111
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_sort")} ASC/
112
+ )
113
+ end
114
+
115
+ it "doesn't sort by 'only_search' field" do
116
+ s = Person.search(
117
+ "s" => { "0" => { "dir" => "asc", "name" => "only_search" } }
118
+ )
119
+ s.result.to_sql.should_not match(
120
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_search")} ASC/
121
+ )
122
+ end
123
+
124
+ it 'allows search by "only_search" field' do
125
+ s = Person.search(only_search_eq: 'htimS cirA')
126
+ s.result.to_sql.should match(
127
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_search")} = 'htimS cirA'/
128
+ )
129
+ end
130
+
131
+ it "can't be searched by 'only_sort'" do
132
+ s = Person.search(only_sort_eq: 'htimS cirA')
133
+ s.result.to_sql.should_not match(
134
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_sort")} = 'htimS cirA'/
135
+ )
136
+ end
137
+
138
+ it 'allows sort by "only_admin" field, if auth_object: :admin' do
139
+ s = Person.search(
140
+ { "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } } },
141
+ { auth_object: :admin }
142
+ )
143
+ s.result.to_sql.should match(
144
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_admin")} ASC/
145
+ )
146
+ end
147
+
148
+ it "doesn't sort by 'only_admin' field, if auth_object: nil" do
149
+ s = Person.search(
150
+ "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } }
151
+ )
152
+ s.result.to_sql.should_not match(
153
+ /ORDER BY #{quote_table_name("people")}.#{quote_column_name("only_admin")} ASC/
154
+ )
155
+ end
156
+
157
+ it 'allows search by "only_admin" field, if auth_object: :admin' do
158
+ s = Person.search(
159
+ { only_admin_eq: 'htimS cirA' },
160
+ { auth_object: :admin }
161
+ )
162
+ s.result.to_sql.should match(
163
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_admin")} = 'htimS cirA'/
164
+ )
165
+ end
166
+
167
+ it "can't be searched by 'only_admin', if auth_object: nil" do
168
+ s = Person.search(only_admin_eq: 'htimS cirA')
169
+ s.result.to_sql.should_not match(
170
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("only_admin")} = 'htimS cirA'/
171
+ )
172
+ end
78
173
  end
79
174
 
80
175
  describe '#ransackable_attributes' do
81
- subject { Person.ransackable_attributes }
176
+ context 'when auth_object is nil' do
177
+ subject { Person.ransackable_attributes }
178
+
179
+ it { should include 'name' }
180
+ it { should include 'reversed_name' }
181
+ it { should include 'doubled_name' }
182
+ it { should include 'only_search' }
183
+ it { should_not include 'only_sort' }
184
+ it { should_not include 'only_admin' }
185
+ end
186
+
187
+ context 'with auth_object :admin' do
188
+ subject { Person.ransackable_attributes(:admin) }
82
189
 
83
- it { should include 'name' }
84
- it { should include 'reversed_name' }
85
- it { should include 'doubled_name' }
190
+ it { should include 'name' }
191
+ it { should include 'reversed_name' }
192
+ it { should include 'doubled_name' }
193
+ it { should include 'only_search' }
194
+ it { should_not include 'only_sort' }
195
+ it { should include 'only_admin' }
196
+ end
86
197
  end
87
198
 
88
199
  describe '#ransortable_attributes' do
89
- subject { Person.ransortable_attributes }
200
+ context 'when auth_object is nil' do
201
+ subject { Person.ransortable_attributes }
202
+
203
+ it { should include 'name' }
204
+ it { should include 'reversed_name' }
205
+ it { should include 'doubled_name' }
206
+ it { should include 'only_sort' }
207
+ it { should_not include 'only_search' }
208
+ it { should_not include 'only_admin' }
209
+ end
90
210
 
91
- it { should include 'name' }
92
- it { should include 'reversed_name' }
93
- it { should include 'doubled_name' }
211
+ context 'with auth_object :admin' do
212
+ subject { Person.ransortable_attributes(:admin) }
213
+
214
+ it { should include 'name' }
215
+ it { should include 'reversed_name' }
216
+ it { should include 'doubled_name' }
217
+ it { should include 'only_sort' }
218
+ it { should_not include 'only_search' }
219
+ it { should include 'only_admin' }
220
+ end
94
221
  end
95
222
 
96
223
  describe '#ransackable_associations' do
@@ -104,4 +231,4 @@ module Ransack
104
231
  end
105
232
  end
106
233
  end
107
- end
234
+ end
@@ -14,16 +14,16 @@ module Ransack
14
14
 
15
15
  describe '#evaluate' do
16
16
  it 'evaluates search objects' do
17
- search = Search.new(Person, :name_eq => 'Joe Blow')
17
+ search = Search.new(Person, name_eq: 'Joe Blow')
18
18
  result = subject.evaluate(search)
19
19
 
20
20
  result.should be_an ::ActiveRecord::Relation
21
21
  result.to_sql.should match /#{quote_column_name("name")} = 'Joe Blow'/
22
22
  end
23
23
 
24
- it 'SELECTs DISTINCT when :distinct => true' do
25
- search = Search.new(Person, :name_eq => 'Joe Blow')
26
- result = subject.evaluate(search, :distinct => true)
24
+ it 'SELECTs DISTINCT when distinct: true' do
25
+ search = Search.new(Person, name_eq: 'Joe Blow')
26
+ result = subject.evaluate(search, distinct: true)
27
27
 
28
28
  result.should be_an ::ActiveRecord::Relation
29
29
  result.to_sql.should match /SELECT DISTINCT/
@@ -48,4 +48,4 @@ module Ransack
48
48
  end
49
49
  end
50
50
  end
51
- end
51
+ end
@@ -18,14 +18,19 @@ module Ransack
18
18
  Ransack.predicates.should have_key 'test_predicate_all'
19
19
  end
20
20
 
21
- it 'avoids creating compound predicates if :compounds => false' do
21
+ it 'avoids creating compound predicates if compounds: false' do
22
22
  Ransack.configure do |config|
23
- config.add_predicate :test_predicate_without_compound, :compounds => false
23
+ config.add_predicate(
24
+ :test_predicate_without_compound,
25
+ compounds: false
26
+ )
24
27
  end
25
-
26
- Ransack.predicates.should have_key 'test_predicate_without_compound'
27
- Ransack.predicates.should_not have_key 'test_predicate_without_compound_any'
28
- Ransack.predicates.should_not have_key 'test_predicate_without_compound_all'
28
+ Ransack.predicates
29
+ .should have_key 'test_predicate_without_compound'
30
+ Ransack.predicates
31
+ .should_not have_key 'test_predicate_without_compound_any'
32
+ Ransack.predicates
33
+ .should_not have_key 'test_predicate_without_compound_all'
29
34
  end
30
35
 
31
36
  it 'should have default value for search key' do
@@ -48,7 +53,11 @@ module Ransack
48
53
 
49
54
  it 'adds predicates that take arrays, overriding compounds' do
50
55
  Ransack.configure do |config|
51
- config.add_predicate :test_array_predicate, :wants_array => true, :compounds => true
56
+ config.add_predicate(
57
+ :test_array_predicate,
58
+ wants_array: true,
59
+ compounds: true
60
+ )
52
61
  end
53
62
 
54
63
  Ransack.predicates['test_array_predicate'].wants_array.should eq true
@@ -56,4 +65,4 @@ module Ransack
56
65
  Ransack.predicates.should_not have_key 'test_array_predicate_all'
57
66
  end
58
67
  end
59
- end
68
+ end
@@ -0,0 +1,8 @@
1
+ unless ::ActiveRecord::VERSION::STRING >= "4"
2
+ describe 'Ransack' do
3
+ it 'can be required without errors' do
4
+ output = `bundle exec ruby -e "require 'ransack'" 2>&1`
5
+ output.should be_empty
6
+ end
7
+ end
8
+ end
@@ -7,6 +7,7 @@ module Ransack
7
7
  router = ActionDispatch::Routing::RouteSet.new
8
8
  router.draw do
9
9
  resources :people
10
+ resources :notes
10
11
  get ':controller(/:action(/:id(.:format)))'
11
12
  end
12
13
 
@@ -32,7 +33,11 @@ module Ransack
32
33
 
33
34
  it 'selects previously-entered time values with datetime_select' do
34
35
  @s.created_at_eq = [2011, 1, 2, 3, 4, 5]
35
- html = @f.datetime_select :created_at_eq, :use_month_numbers => true, :include_seconds => true
36
+ html = @f.datetime_select(
37
+ :created_at_eq,
38
+ use_month_numbers: true,
39
+ include_seconds: true
40
+ )
36
41
  %w(2011 1 2 03 04 05).each do |val|
37
42
  html.should match /<option selected="selected" value="#{val}">#{val}<\/option>/
38
43
  end
@@ -49,7 +54,7 @@ module Ransack
49
54
 
50
55
  describe '#sort_link' do
51
56
  it 'sort_link for ransack attribute' do
52
- sort_link = @f.sort_link :name, :controller => 'people'
57
+ sort_link = @f.sort_link :name, controller: 'people'
53
58
  if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
54
59
  sort_link.should match /people\?q%5Bs%5D=name\+asc/
55
60
  else
@@ -60,7 +65,7 @@ module Ransack
60
65
  end
61
66
 
62
67
  it 'sort_link for common attribute' do
63
- sort_link = @f.sort_link :id, :controller => 'people'
68
+ sort_link = @f.sort_link :id, controller: 'people'
64
69
  sort_link.should match /id<\/a>/
65
70
  end
66
71
  end
@@ -78,15 +83,17 @@ module Ransack
78
83
 
79
84
  it 'returns ransackable attributes' do
80
85
  html = @f.attribute_select
81
- html.split(/\n/).should have(Person.ransackable_attributes.size + 1).lines
86
+ html.split(/\n/).
87
+ should have(Person.ransackable_attributes.size + 1).lines
82
88
  Person.ransackable_attributes.each do |attribute|
83
89
  html.should match /<option value="#{attribute}">/
84
90
  end
85
91
  end
86
92
 
87
93
  it 'returns ransackable attributes for associations with :associations' do
88
- attributes = Person.ransackable_attributes + Article.ransackable_attributes.map {|a| "articles_#{a}"}
89
- html = @f.attribute_select :associations => ['articles']
94
+ attributes = Person.ransackable_attributes + Article.
95
+ ransackable_attributes.map { |a| "articles_#{a}" }
96
+ html = @f.attribute_select(associations: ['articles'])
90
97
  html.split(/\n/).should have(attributes.size).lines
91
98
  attributes.each do |attribute|
92
99
  html.should match /<option value="#{attribute}">/
@@ -94,7 +101,7 @@ module Ransack
94
101
  end
95
102
 
96
103
  it 'returns option groups for base and associations with :associations' do
97
- html = @f.attribute_select :associations => ['articles']
104
+ html = @f.attribute_select(associations: ['articles'])
98
105
  [Person, Article].each do |model|
99
106
  html.should match /<optgroup label="#{model}">/
100
107
  end
@@ -112,26 +119,42 @@ module Ransack
112
119
  end
113
120
 
114
121
  it 'filters predicates with single-value :only' do
115
- html = @f.predicate_select :only => 'eq'
116
- Predicate.names.reject {|k| k =~ /^eq/}.each do |key|
122
+ html = @f.predicate_select only: 'eq'
123
+ Predicate.names.reject { |k| k =~ /^eq/ }.each do |key|
117
124
  html.should_not match /<option value="#{key}">/
118
125
  end
119
126
  end
120
127
 
121
128
  it 'filters predicates with multi-value :only' do
122
- html = @f.predicate_select :only => [:eq, :lt]
123
- Predicate.names.reject {|k| k =~ /^(eq|lt)/}.each do |key|
129
+ html = @f.predicate_select only: [:eq, :lt]
130
+ Predicate.names.reject { |k| k =~ /^(eq|lt)/ }.each do |key|
124
131
  html.should_not match /<option value="#{key}">/
125
132
  end
126
133
  end
127
134
 
128
- it 'excludes compounds when :compounds => false' do
129
- html = @f.predicate_select :compounds => false
130
- Predicate.names.select {|k| k =~ /_(any|all)$/}.each do |key|
135
+ it 'excludes compounds when compounds: false' do
136
+ html = @f.predicate_select compounds: false
137
+ Predicate.names.select { |k| k =~ /_(any|all)$/ }.each do |key|
131
138
  html.should_not match /<option value="#{key}">/
132
139
  end
133
140
  end
134
141
  end
142
+
143
+ context 'fields used in polymorphic relations as search attributes in form' do
144
+ before do
145
+ @controller.view_context.search_form_for Note.search do |f|
146
+ @f = f
147
+ end
148
+ end
149
+ it 'accepts poly_id field' do
150
+ html = @f.text_field(:notable_id_eq)
151
+ html.should match /id=\"q_notable_id_eq\"/
152
+ end
153
+ it 'accepts poly_type field' do
154
+ html = @f.text_field(:notable_type_eq)
155
+ html.should match /id=\"q_notable_type_eq\"/
156
+ end
157
+ end
135
158
  end
136
159
  end
137
160
  end