thinking-sphinx 1.5.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/README.textile +15 -48
  2. data/VERSION +1 -0
  3. data/features/attribute_transformation.feature +7 -7
  4. data/features/attribute_updates.feature +16 -18
  5. data/features/deleting_instances.feature +13 -16
  6. data/features/excerpts.feature +0 -8
  7. data/features/facets.feature +19 -25
  8. data/features/handling_edits.feature +20 -25
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +5 -6
  11. data/features/searching_by_model.feature +29 -29
  12. data/features/sphinx_scopes.feature +0 -26
  13. data/features/step_definitions/common_steps.rb +6 -18
  14. data/features/step_definitions/scope_steps.rb +0 -4
  15. data/features/step_definitions/search_steps.rb +4 -9
  16. data/features/support/env.rb +10 -3
  17. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -8
  18. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  19. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  20. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  21. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  22. data/features/thinking_sphinx/db/fixtures/posts.rb +1 -5
  23. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -1
  24. data/features/thinking_sphinx/models/alpha.rb +0 -1
  25. data/features/thinking_sphinx/models/beta.rb +0 -5
  26. data/features/thinking_sphinx/models/developer.rb +1 -6
  27. data/features/thinking_sphinx/models/music.rb +1 -3
  28. data/features/thinking_sphinx/models/person.rb +1 -2
  29. data/features/thinking_sphinx/models/post.rb +0 -1
  30. data/lib/cucumber/thinking_sphinx/external_world.rb +4 -8
  31. data/lib/cucumber/thinking_sphinx/internal_world.rb +27 -36
  32. data/lib/thinking_sphinx.rb +60 -132
  33. data/lib/thinking_sphinx/active_record.rb +98 -124
  34. data/lib/thinking_sphinx/active_record/attribute_updates.rb +13 -17
  35. data/lib/thinking_sphinx/active_record/delta.rb +15 -21
  36. data/lib/thinking_sphinx/active_record/has_many_association.rb +23 -16
  37. data/lib/thinking_sphinx/active_record/scopes.rb +0 -18
  38. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +15 -63
  39. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -4
  40. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +24 -65
  41. data/lib/thinking_sphinx/association.rb +11 -36
  42. data/lib/thinking_sphinx/attribute.rb +85 -92
  43. data/lib/thinking_sphinx/auto_version.rb +3 -21
  44. data/lib/thinking_sphinx/class_facet.rb +3 -8
  45. data/lib/thinking_sphinx/configuration.rb +58 -114
  46. data/lib/thinking_sphinx/context.rb +20 -22
  47. data/lib/thinking_sphinx/core/array.rb +13 -0
  48. data/lib/thinking_sphinx/deltas.rb +0 -2
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +22 -18
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +31 -30
  51. data/lib/thinking_sphinx/excerpter.rb +1 -2
  52. data/lib/thinking_sphinx/facet.rb +35 -45
  53. data/lib/thinking_sphinx/facet_search.rb +24 -58
  54. data/lib/thinking_sphinx/field.rb +0 -18
  55. data/lib/thinking_sphinx/index.rb +36 -38
  56. data/lib/thinking_sphinx/index/builder.rb +59 -74
  57. data/lib/thinking_sphinx/property.rb +45 -66
  58. data/lib/thinking_sphinx/railtie.rb +35 -0
  59. data/lib/thinking_sphinx/search.rb +250 -506
  60. data/lib/thinking_sphinx/source.rb +31 -50
  61. data/lib/thinking_sphinx/source/internal_properties.rb +3 -8
  62. data/lib/thinking_sphinx/source/sql.rb +31 -71
  63. data/lib/thinking_sphinx/tasks.rb +27 -48
  64. data/spec/thinking_sphinx/active_record/delta_spec.rb +41 -36
  65. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -96
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +29 -29
  67. data/spec/thinking_sphinx/active_record_spec.rb +169 -140
  68. data/spec/thinking_sphinx/association_spec.rb +2 -20
  69. data/spec/thinking_sphinx/attribute_spec.rb +97 -101
  70. data/spec/thinking_sphinx/auto_version_spec.rb +11 -75
  71. data/spec/thinking_sphinx/configuration_spec.rb +62 -63
  72. data/spec/thinking_sphinx/context_spec.rb +66 -66
  73. data/spec/thinking_sphinx/facet_search_spec.rb +99 -99
  74. data/spec/thinking_sphinx/facet_spec.rb +4 -30
  75. data/spec/thinking_sphinx/field_spec.rb +3 -17
  76. data/spec/thinking_sphinx/index/builder_spec.rb +132 -169
  77. data/spec/thinking_sphinx/index_spec.rb +39 -45
  78. data/spec/thinking_sphinx/search_methods_spec.rb +33 -37
  79. data/spec/thinking_sphinx/search_spec.rb +269 -491
  80. data/spec/thinking_sphinx/source_spec.rb +48 -62
  81. data/spec/thinking_sphinx_spec.rb +49 -49
  82. data/tasks/distribution.rb +46 -0
  83. data/tasks/testing.rb +74 -0
  84. metadata +123 -199
  85. data/features/field_sorting.feature +0 -18
  86. data/features/thinking_sphinx/db/.gitignore +0 -1
  87. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  88. data/features/thinking_sphinx/models/andrew.rb +0 -17
  89. data/lib/thinking-sphinx.rb +0 -1
  90. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  91. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  92. data/lib/thinking_sphinx/connection.rb +0 -71
  93. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  94. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  95. data/lib/thinking_sphinx/rails_additions.rb +0 -181
  96. data/spec/fixtures/data.sql +0 -32
  97. data/spec/fixtures/database.yml.default +0 -3
  98. data/spec/fixtures/models.rb +0 -161
  99. data/spec/fixtures/structure.sql +0 -146
  100. data/spec/spec_helper.rb +0 -54
  101. data/spec/sphinx_helper.rb +0 -67
  102. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  103. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  104. data/spec/thinking_sphinx/rails_additions_spec.rb +0 -203
@@ -3,41 +3,41 @@ require 'spec_helper'
3
3
  describe "ThinkingSphinx::ActiveRecord::Delta" do
4
4
  it "should call the toggle_delta method after a save" do
5
5
  @beta = Beta.new(:name => 'beta')
6
- @beta.should_receive(:toggle_delta).at_least(1).times.and_return(true)
7
-
6
+ @beta.should_receive(:toggle_delta).and_return(true)
7
+
8
8
  @beta.save
9
9
  end
10
-
10
+
11
11
  it "should call the toggle_delta method after a save!" do
12
12
  @beta = Beta.new(:name => 'beta')
13
- @beta.should_receive(:toggle_delta).at_least(1).times.and_return(true)
14
-
13
+ @beta.should_receive(:toggle_delta).and_return(true)
14
+
15
15
  @beta.save!
16
16
  end
17
-
17
+
18
18
  describe "suspended_delta method" do
19
19
  before :each do
20
- ThinkingSphinx.deltas_suspended = false
20
+ ThinkingSphinx.deltas_enabled = true
21
21
  Person.sphinx_indexes.first.delta_object.stub!(:` => "")
22
22
  end
23
23
 
24
24
  it "should execute the argument block with deltas disabled" do
25
- ThinkingSphinx.should_receive(:deltas_suspended=).once.with(true)
26
- ThinkingSphinx.should_receive(:deltas_suspended=).once.with(false)
25
+ ThinkingSphinx.should_receive(:deltas_enabled=).once.with(false)
26
+ ThinkingSphinx.should_receive(:deltas_enabled=).once.with(true)
27
27
  lambda { Person.suspended_delta { raise 'i was called' } }.should(
28
28
  raise_error(Exception)
29
29
  )
30
30
  end
31
31
 
32
32
  it "should restore deltas_enabled to its original setting" do
33
- ThinkingSphinx.deltas_suspended = true
34
- ThinkingSphinx.should_receive(:deltas_suspended=).twice.with(true)
33
+ ThinkingSphinx.deltas_enabled = false
34
+ ThinkingSphinx.should_receive(:deltas_enabled=).twice.with(false)
35
35
  Person.suspended_delta { 'no-op' }
36
36
  end
37
37
 
38
38
  it "should restore deltas_enabled to its original setting even if there was an exception" do
39
- ThinkingSphinx.deltas_suspended = true
40
- ThinkingSphinx.should_receive(:deltas_suspended=).twice.with(true)
39
+ ThinkingSphinx.deltas_enabled = false
40
+ ThinkingSphinx.should_receive(:deltas_enabled=).twice.with(false)
41
41
  lambda { Person.suspended_delta { raise 'bad error' } }.should(
42
42
  raise_error(Exception)
43
43
  )
@@ -47,76 +47,81 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
47
47
  Person.should_receive(:index_delta)
48
48
  Person.suspended_delta { 'no-op' }
49
49
  end
50
-
50
+
51
51
  it "should not reindex after the code block if false is passed in" do
52
52
  Person.should_not_receive(:index_delta)
53
53
  Person.suspended_delta(false) { 'no-op' }
54
54
  end
55
55
  end
56
-
56
+
57
57
  describe "toggle_delta method" do
58
58
  it "should set the delta value to true" do
59
59
  @person = Person.new
60
-
60
+
61
61
  @person.delta.should be_false
62
62
  @person.send(:toggle_delta)
63
63
  @person.delta.should be_true
64
64
  end
65
65
  end
66
-
66
+
67
67
  describe "index_delta method" do
68
- let(:index_job) { double :perform => true }
69
-
70
68
  before :each do
71
69
  ThinkingSphinx::Configuration.stub!(:environment => "spec")
72
70
  ThinkingSphinx.deltas_enabled = true
73
71
  ThinkingSphinx.updates_enabled = true
74
72
  ThinkingSphinx.stub!(:sphinx_running? => true)
75
- Person.delta_objects.first.stub!(:` => "", :toggled => true)
76
- ThinkingSphinx::Deltas::IndexJob.stub :new => index_job
77
-
73
+ Person.delta_object.stub!(:` => "", :toggled => true)
74
+
78
75
  @person = Person.new
79
76
  Person.stub!(:search_for_id => false)
80
77
  @person.stub!(:sphinx_document_id => 1)
81
-
78
+
82
79
  @client = Riddle::Client.new
83
80
  @client.stub!(:update => true)
84
- ThinkingSphinx::Connection.stub(:take).and_yield @client
81
+ ThinkingSphinx::Configuration.instance.stub!(:client => @client)
85
82
  end
86
-
83
+
87
84
  it "shouldn't index if delta indexing is disabled" do
88
85
  ThinkingSphinx.deltas_enabled = false
89
86
  Person.sphinx_indexes.first.delta_object.should_not_receive(:`)
90
87
  @client.should_not_receive(:update)
91
-
88
+
92
89
  @person.send(:index_delta)
93
90
  end
94
-
91
+
95
92
  it "shouldn't index if index updating is disabled" do
96
93
  ThinkingSphinx.updates_enabled = false
97
94
  Person.sphinx_indexes.first.delta_object.should_not_receive(:`)
98
-
95
+
99
96
  @person.send(:index_delta)
100
97
  end
101
-
98
+
102
99
  it "shouldn't index if the environment is 'test'" do
103
100
  ThinkingSphinx.deltas_enabled = nil
104
101
  ThinkingSphinx::Configuration.stub!(:environment => "test")
105
102
  Person.sphinx_indexes.first.delta_object.should_not_receive(:`)
106
-
103
+
107
104
  @person.send(:index_delta)
108
105
  end
109
-
110
- it "should run the index job" do
111
- index_job.should_receive(:perform)
112
-
106
+
107
+ it "should call indexer for the delta index" do
108
+ Person.sphinx_indexes.first.delta_object.should_receive(:`).with(
109
+ "#{ThinkingSphinx::Configuration.instance.bin_path}indexer --config '#{ThinkingSphinx::Configuration.instance.config_file}' --rotate person_delta"
110
+ )
111
+
113
112
  @person.send(:index_delta)
114
113
  end
115
-
114
+
115
+ it "shouldn't update the deleted attribute if not in the index" do
116
+ @client.should_not_receive(:update)
117
+
118
+ @person.send(:index_delta)
119
+ end
120
+
116
121
  it "should update the deleted attribute if in the core index" do
117
122
  Person.stub!(:search_for_id => true)
118
123
  @client.should_receive(:update)
119
-
124
+
120
125
  @person.send(:index_delta)
121
126
  end
122
127
  end
@@ -23,17 +23,6 @@ describe 'ThinkingSphinx::ActiveRecord::HasManyAssociation' do
23
23
  @person.friendships.search "test"
24
24
  end
25
25
 
26
- it "should add a filter for an aliased attribute into a normal search call" do
27
- @team = CricketTeam.new
28
- @team.stub!(:id => 1)
29
-
30
- Person.should_receive(:search).with do |query, options|
31
- options[:with][:team_id].should == @team.id
32
- end
33
-
34
- @team.people.search "test"
35
- end
36
-
37
26
  it "should define indexes for the reflection class" do
38
27
  Friendship.should_receive(:define_indexes)
39
28
 
@@ -41,46 +30,6 @@ describe 'ThinkingSphinx::ActiveRecord::HasManyAssociation' do
41
30
  end
42
31
  end
43
32
 
44
- describe "facets method" do
45
- before :each do
46
- Friendship.stub!(:facets => true)
47
-
48
- @person = Person.find(:first)
49
- @index = Friendship.sphinx_indexes.first
50
- end
51
-
52
- it "should raise an error if the required attribute doesn't exist" do
53
- @index.stub!(:attributes => [])
54
-
55
- lambda { @person.friendships.facets "test" }.should raise_error(RuntimeError)
56
- end
57
-
58
- it "should add a filter for the attribute into a normal facets call" do
59
- Friendship.should_receive(:facets) do |query, options|
60
- options[:with][:person_id].should == @person.id
61
- end
62
-
63
- @person.friendships.facets "test"
64
- end
65
-
66
- it "should add a filter for an aliased attribute into a normal facets call" do
67
- @team = CricketTeam.new
68
- @team.stub!(:id => 1)
69
-
70
- Person.should_receive(:facets).with do |query, options|
71
- options[:with][:team_id].should == @team.id
72
- end
73
-
74
- @team.people.facets "test"
75
- end
76
-
77
- it "should define indexes for the reflection class" do
78
- Friendship.should_receive(:define_indexes)
79
-
80
- @person.friendships.facets 'test'
81
- end
82
- end
83
-
84
33
  describe "search method for has_many :through" do
85
34
  before :each do
86
35
  Person.stub!(:search => true)
@@ -102,53 +51,8 @@ describe 'ThinkingSphinx::ActiveRecord::HasManyAssociation' do
102
51
 
103
52
  @person.friends.search "test"
104
53
  end
105
-
106
- it "should add a filter for an aliased attribute into a normal search call" do
107
- @team = FootballTeam.new
108
- @team.stub!(:id => 1)
109
-
110
- Person.should_receive(:search).with do |query, options|
111
- options[:with][:football_team_id].should == @team.id
112
- end
113
-
114
- @team.people.search "test"
115
- end
116
54
  end
117
55
 
118
- describe "facets method for has_many :through" do
119
- before :each do
120
- Person.stub!(:facets => true)
121
-
122
- @person = Person.find(:first)
123
- @index = Person.sphinx_indexes.first
124
- end
125
-
126
- it "should raise an error if the required attribute doesn't exist" do
127
- @index.stub!(:attributes => [])
128
-
129
- lambda { @person.friends.facets "test" }.should raise_error(RuntimeError)
130
- end
131
-
132
- it "should add a filter for the attribute into a normal facets call" do
133
- Person.should_receive(:facets).with do |query, options|
134
- options[:with][:friendly_ids].should == @person.id
135
- end
136
-
137
- @person.friends.facets "test"
138
- end
139
-
140
- it "should add a filter for an aliased attribute into a normal facets call" do
141
- @team = FootballTeam.new
142
- @team.stub!(:id => 1)
143
-
144
- Person.should_receive(:facets).with do |query, options|
145
- options[:with][:football_team_id].should == @team.id
146
- end
147
-
148
- @team.people.facets "test"
149
- end
150
- end
151
-
152
56
  describe 'filtering sphinx scopes' do
153
57
  before :each do
154
58
  Friendship.stub!(:search => Friendship)
@@ -4,32 +4,32 @@ describe ThinkingSphinx::ActiveRecord::Scopes do
4
4
  after :each do
5
5
  Alpha.remove_sphinx_scopes
6
6
  end
7
-
7
+
8
8
  it "should be included into models with indexes" do
9
9
  Alpha.included_modules.should include(ThinkingSphinx::ActiveRecord::Scopes)
10
10
  end
11
-
11
+
12
12
  it "should not be included into models without indexes" do
13
13
  Gamma.included_modules.should_not include(
14
14
  ThinkingSphinx::ActiveRecord::Scopes
15
15
  )
16
16
  end
17
-
17
+
18
18
  describe '.sphinx_scope' do
19
19
  before :each do
20
20
  Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
21
21
  end
22
-
22
+
23
23
  it "should define a method on the model" do
24
24
  Alpha.should respond_to(:by_name)
25
25
  end
26
26
  end
27
-
27
+
28
28
  describe '.sphinx_scopes' do
29
29
  before :each do
30
30
  Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
31
31
  end
32
-
32
+
33
33
  it "should return an array of defined scope names as symbols" do
34
34
  Alpha.sphinx_scopes.should == [:by_name]
35
35
  end
@@ -40,11 +40,11 @@ describe ThinkingSphinx::ActiveRecord::Scopes do
40
40
  Alpha.sphinx_scope(:scope_used_as_default_scope) { {:conditions => {:name => 'name'}} }
41
41
  Alpha.default_sphinx_scope :scope_used_as_default_scope
42
42
  end
43
-
43
+
44
44
  it "should return an array of defined scope names as symbols" do
45
45
  Alpha.sphinx_scopes.should == [:scope_used_as_default_scope]
46
46
  end
47
-
47
+
48
48
  it "should have a default_sphinx_scope" do
49
49
  Alpha.has_default_sphinx_scope?.should be_true
50
50
  end
@@ -55,11 +55,11 @@ describe ThinkingSphinx::ActiveRecord::Scopes do
55
55
  Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
56
56
  Alpha.remove_sphinx_scopes
57
57
  end
58
-
58
+
59
59
  it "should remove sphinx scope methods" do
60
60
  Alpha.should_not respond_to(:by_name)
61
61
  end
62
-
62
+
63
63
  it "should empty the list of sphinx scopes" do
64
64
  Alpha.sphinx_scopes.should be_empty
65
65
  end
@@ -87,9 +87,10 @@ describe ThinkingSphinx::ActiveRecord::Scopes do
87
87
  search.by_foo('foo').search.options[:conditions].should == {:foo => 'foo', :name => 'foo'}
88
88
  end
89
89
 
90
- it "should apply the default scope options before other scope options to the underlying search object" do
90
+ # FIXME: Probably the other way around is more logical? How to do this?
91
+ it "should apply the default scope options after other scope options to the underlying search object" do
91
92
  search = ThinkingSphinx::Search.new(:classes => [Alpha])
92
- search.by_name('bar').search.options[:conditions].should == {:name => 'bar'}
93
+ search.by_name('bar').search.options[:conditions].should == {:name => 'foo'}
93
94
  end
94
95
  end
95
96
 
@@ -99,73 +100,72 @@ describe ThinkingSphinx::ActiveRecord::Scopes do
99
100
  Alpha.sphinx_scope(:by_foo) { |foo| {:conditions => {:foo => foo}} }
100
101
  Alpha.sphinx_scope(:with_betas) { {:classes => [Beta]} }
101
102
  end
102
-
103
+
103
104
  it "should return a ThinkingSphinx::Search object" do
104
105
  Alpha.by_name('foo').should be_a(ThinkingSphinx::Search)
105
106
  end
106
-
107
+
107
108
  it "should set the classes option" do
108
109
  Alpha.by_name('foo').options[:classes].should == [Alpha]
109
110
  end
110
-
111
+
111
112
  it "should be able to be called on a ThinkingSphinx::Search object" do
112
113
  search = ThinkingSphinx::Search.new(:classes => [Alpha])
113
114
  lambda {
114
115
  search.by_name('foo')
115
116
  }.should_not raise_error
116
117
  end
117
-
118
+
118
119
  it "should return the search object it gets called upon" do
119
120
  search = ThinkingSphinx::Search.new(:classes => [Alpha])
120
- search.by_name('foo').object_id.should == search.object_id
121
+ search.by_name('foo').should == search
121
122
  end
122
-
123
+
123
124
  it "should apply the scope options to the underlying search object" do
124
125
  search = ThinkingSphinx::Search.new(:classes => [Alpha])
125
126
  search.by_name('foo').options[:conditions].should == {:name => 'foo'}
126
127
  end
127
-
128
+
128
129
  it "should combine hash option scopes such as :conditions" do
129
130
  search = ThinkingSphinx::Search.new(:classes => [Alpha])
130
131
  search.by_name('foo').by_foo('bar').options[:conditions].
131
132
  should == {:name => 'foo', :foo => 'bar'}
132
133
  end
133
-
134
+
134
135
  it "should combine array option scopes such as :classes" do
135
136
  search = ThinkingSphinx::Search.new(:classes => [Alpha])
136
137
  search.with_betas.options[:classes].should == [Alpha, Beta]
137
138
  end
138
139
  end
139
-
140
+
140
141
  describe '.search_count_with_scope' do
141
142
  before :each do
142
143
  @config = ThinkingSphinx::Configuration.instance
143
144
  @client = Riddle::Client.new
144
145
 
145
- ThinkingSphinx::Connection.stub(:take).and_yield(@client)
146
-
146
+ @config.stub!(:client => @client)
147
147
  @client.stub!(:query => {:matches => [], :total_found => 43})
148
148
  Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
149
149
  Alpha.sphinx_scope(:ids_only) { {:ids_only => true} }
150
150
  end
151
-
151
+
152
152
  it "should return the total number of results" do
153
153
  Alpha.by_name('foo').search_count.should == 43
154
154
  end
155
-
155
+
156
156
  it "should not make any calls to the database" do
157
157
  Alpha.should_not_receive(:find)
158
-
158
+
159
159
  Alpha.by_name('foo').search_count
160
160
  end
161
-
161
+
162
162
  it "should not leave the :ids_only option set and the results populated if it was not set before" do
163
163
  stored_scope = Alpha.by_name('foo')
164
164
  stored_scope.search_count
165
165
  stored_scope.options[:ids_only].should be_false
166
166
  stored_scope.populated?.should be_false
167
167
  end
168
-
168
+
169
169
  it "should leave the :ids_only option set and the results populated if it was set before" do
170
170
  stored_scope = Alpha.by_name('foo').ids_only
171
171
  stored_scope.search_count
@@ -173,5 +173,5 @@ describe ThinkingSphinx::ActiveRecord::Scopes do
173
173
  stored_scope.populated?.should be_true
174
174
  end
175
175
  end
176
-
176
+
177
177
  end