substantial-sunspot_rails 2.0.0.pre.111215

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 (66) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +1 -0
  3. data/History.txt +66 -0
  4. data/LICENSE +18 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +265 -0
  7. data/Rakefile +12 -0
  8. data/TODO +8 -0
  9. data/dev_tasks/rdoc.rake +24 -0
  10. data/dev_tasks/release.rake +4 -0
  11. data/dev_tasks/spec.rake +107 -0
  12. data/dev_tasks/todo.rake +4 -0
  13. data/gemfiles/rails-2.3.14 +15 -0
  14. data/gemfiles/rails-3.0.11 +15 -0
  15. data/gemfiles/rails-3.1.3 +15 -0
  16. data/generators/sunspot/sunspot_generator.rb +9 -0
  17. data/generators/sunspot/templates/sunspot.yml +18 -0
  18. data/install.rb +1 -0
  19. data/lib/generators/sunspot_rails.rb +9 -0
  20. data/lib/generators/sunspot_rails/install/install_generator.rb +13 -0
  21. data/lib/generators/sunspot_rails/install/templates/config/sunspot.yml +17 -0
  22. data/lib/substantial-sunspot_rails.rb +1 -0
  23. data/lib/sunspot/rails.rb +65 -0
  24. data/lib/sunspot/rails/adapters.rb +83 -0
  25. data/lib/sunspot/rails/configuration.rb +340 -0
  26. data/lib/sunspot/rails/init.rb +5 -0
  27. data/lib/sunspot/rails/log_subscriber.rb +33 -0
  28. data/lib/sunspot/rails/railtie.rb +36 -0
  29. data/lib/sunspot/rails/railties/controller_runtime.rb +36 -0
  30. data/lib/sunspot/rails/request_lifecycle.rb +36 -0
  31. data/lib/sunspot/rails/searchable.rb +480 -0
  32. data/lib/sunspot/rails/server.rb +106 -0
  33. data/lib/sunspot/rails/solr_instrumentation.rb +18 -0
  34. data/lib/sunspot/rails/solr_logging.rb +62 -0
  35. data/lib/sunspot/rails/spec_helper.rb +26 -0
  36. data/lib/sunspot/rails/stub_session_proxy.rb +142 -0
  37. data/lib/sunspot/rails/tasks.rb +84 -0
  38. data/lib/sunspot_rails.rb +12 -0
  39. data/spec/configuration_spec.rb +195 -0
  40. data/spec/model_lifecycle_spec.rb +63 -0
  41. data/spec/model_spec.rb +595 -0
  42. data/spec/rails_template/app/controllers/application_controller.rb +10 -0
  43. data/spec/rails_template/app/controllers/posts_controller.rb +6 -0
  44. data/spec/rails_template/app/models/author.rb +8 -0
  45. data/spec/rails_template/app/models/blog.rb +12 -0
  46. data/spec/rails_template/app/models/location.rb +2 -0
  47. data/spec/rails_template/app/models/photo_post.rb +2 -0
  48. data/spec/rails_template/app/models/post.rb +11 -0
  49. data/spec/rails_template/app/models/post_with_auto.rb +10 -0
  50. data/spec/rails_template/app/models/post_with_default_scope.rb +11 -0
  51. data/spec/rails_template/config/boot.rb +127 -0
  52. data/spec/rails_template/config/preinitializer.rb +22 -0
  53. data/spec/rails_template/config/routes.rb +9 -0
  54. data/spec/rails_template/config/sunspot.yml +22 -0
  55. data/spec/rails_template/db/schema.rb +27 -0
  56. data/spec/request_lifecycle_spec.rb +61 -0
  57. data/spec/schema.rb +27 -0
  58. data/spec/searchable_spec.rb +12 -0
  59. data/spec/server_spec.rb +33 -0
  60. data/spec/session_spec.rb +57 -0
  61. data/spec/shared_examples/indexed_after_save.rb +8 -0
  62. data/spec/shared_examples/not_indexed_after_save.rb +8 -0
  63. data/spec/spec_helper.rb +48 -0
  64. data/spec/stub_session_proxy_spec.rb +122 -0
  65. data/substantial-sunspot_rails.gemspec +43 -0
  66. metadata +228 -0
@@ -0,0 +1,57 @@
1
+ require 'thread'
2
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
3
+
4
+ describe 'Sunspot::Rails session' do
5
+ it 'is a different object for each thread' do
6
+ # Queue is just a convenient thread-safe container
7
+ sessions_queue = Queue.new
8
+
9
+ # Create some threads which dump their session into the queue
10
+ Array.new(2) {
11
+ Thread.new { sessions_queue << Sunspot.session.session }
12
+ }.each { |thread| thread.join }
13
+
14
+ # Collect the items from the queue
15
+ sessions = []
16
+ until sessions_queue.empty?
17
+ sessions << sessions_queue.pop
18
+ end
19
+
20
+ # There should be no items in the queue with the same object_id
21
+ object_ids = sessions.map(&:object_id)
22
+ object_ids.uniq.should == object_ids
23
+ end
24
+
25
+ it 'should create a separate master/slave session if configured' do
26
+ end
27
+
28
+ it 'should not create a separate master/slave session if no master configured' do
29
+ end
30
+
31
+ context 'disabled' do
32
+ before do
33
+ Sunspot::Rails.reset
34
+ ::Rails.stub!(:env).and_return("config_disabled_test")
35
+ end
36
+
37
+ after do
38
+ Sunspot::Rails.reset
39
+ end
40
+
41
+ it 'sets the session proxy as a stub' do
42
+ Sunspot::Rails.build_session.should be_a_kind_of(Sunspot::Rails::StubSessionProxy)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def with_configuration(options)
49
+ original_configuration = Sunspot::Rails.configuration
50
+ Sunspot::Rails.reset
51
+ Sunspot::Rails.configuration = Sunspot::Rails::Configuration.new
52
+ Sunspot::Rails.configuration.user_configuration = options
53
+ yield
54
+ Sunspot::Rails.reset
55
+ Sunspot::Rails.configuration = original_configuration
56
+ end
57
+ end
@@ -0,0 +1,8 @@
1
+ shared_examples_for 'indexed after save' do
2
+ it 'should be indexed after save' do
3
+ subject.save!
4
+ Sunspot.commit
5
+
6
+ subject.class.search.results.should include(subject)
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ shared_examples_for 'not indexed after save' do
2
+ it 'should not be indexed after save' do
3
+ subject.save!
4
+ Sunspot.commit
5
+
6
+ subject.class.search.results.should_not include(subject)
7
+ end
8
+ end
@@ -0,0 +1,48 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ if rsolr_version = ENV['RSOLR_GEM_VERSION']
3
+ STDERR.puts("Forcing RSolr version #{rsolr_version}")
4
+ gem "rsolr", rsolr_version
5
+ end
6
+
7
+ require File.expand_path('config/environment', ENV['RAILS_ROOT'])
8
+
9
+ begin
10
+ require 'rspec'
11
+ require 'rspec/rails'
12
+ rescue LoadError => e
13
+ require 'spec'
14
+ require 'spec/rails'
15
+ end
16
+ require 'rake'
17
+ require File.join('sunspot', 'rails', 'solr_logging')
18
+
19
+ def load_schema
20
+ stdout = $stdout
21
+ $stdout = StringIO.new # suppress output while building the schema
22
+ load File.join(ENV['RAILS_ROOT'], 'db', 'schema.rb')
23
+ $stdout = stdout
24
+ end
25
+
26
+ def silence_stderr(&block)
27
+ stderr = $stderr
28
+ $stderr = StringIO.new
29
+ yield
30
+ $stderr = stderr
31
+ end
32
+
33
+ rspec =
34
+ begin
35
+ RSpec
36
+ rescue NameError, ArgumentError
37
+ Spec::Runner
38
+ end
39
+
40
+ # Load all shared examples
41
+ Dir[File.expand_path("shared_examples/*.rb", File.dirname(__FILE__))].each {|f| require f}
42
+
43
+ rspec.configure do |config|
44
+ config.before(:each) do
45
+ load_schema
46
+ Sunspot.remove_all!
47
+ end
48
+ end
@@ -0,0 +1,122 @@
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
+ require File.expand_path('../lib/sunspot/rails/spec_helper', File.dirname(__FILE__))
3
+
4
+ describe 'specs with Sunspot stubbed' do
5
+ disconnect_sunspot
6
+
7
+ before :each do
8
+ @session = Sunspot.session.original_session
9
+ @post = Post.create!
10
+ end
11
+
12
+ it 'should not send index to session' do
13
+ @session.should_not_receive(:index)
14
+ @post.index
15
+ end
16
+
17
+ it 'should not send index! to session' do
18
+ @session.should_not_receive(:index!)
19
+ @post.index!
20
+ end
21
+
22
+ it 'should not send commit to session' do
23
+ @session.should_not_receive(:commit)
24
+ Sunspot.commit
25
+ end
26
+
27
+ it 'should not send remove to session' do
28
+ @session.should_not_receive(:remove)
29
+ @post.remove_from_index
30
+ end
31
+
32
+ it 'should not send remove! to session' do
33
+ @session.should_not_receive(:remove)
34
+ @post.remove_from_index!
35
+ end
36
+
37
+ it 'should not send remove_by_id to session' do
38
+ @session.should_not_receive(:remove_by_id)
39
+ Sunspot.remove_by_id(Post, 1)
40
+ end
41
+
42
+ it 'should not send remove_by_id! to session' do
43
+ @session.should_not_receive(:remove_by_id!)
44
+ Sunspot.remove_by_id!(Post, 1)
45
+ end
46
+
47
+ it 'should not send remove_all to session' do
48
+ @session.should_not_receive(:remove_all)
49
+ Post.remove_all_from_index
50
+ end
51
+
52
+ it 'should not send remove_all! to session' do
53
+ @session.should_not_receive(:remove_all!)
54
+ Post.remove_all_from_index!
55
+ end
56
+
57
+ it 'should return false for dirty?' do
58
+ @session.should_not_receive(:dirty?)
59
+ Sunspot.dirty?.should == false
60
+ end
61
+
62
+ it 'should not send commit_if_dirty to session' do
63
+ @session.should_not_receive(:commit_if_dirty)
64
+ Sunspot.commit_if_dirty
65
+ end
66
+
67
+ it 'should return false for delete_dirty?' do
68
+ @session.should_not_receive(:delete_dirty?)
69
+ Sunspot.delete_dirty?.should == false
70
+ end
71
+
72
+ it 'should not send commit_if_delete_dirty to session' do
73
+ @session.should_not_receive(:commit_if_delete_dirty)
74
+ Sunspot.commit_if_delete_dirty
75
+ end
76
+
77
+ it 'should not execute a search when #search called' do
78
+ @session.should_not_receive(:search)
79
+ Post.search
80
+ end
81
+
82
+ it 'should not execute a search when #search called' do
83
+ @session.should_not_receive(:search)
84
+ Post.search
85
+ end
86
+
87
+ it 'should not execute a search when #search called with parameters' do
88
+ @session.should_not_receive(:search)
89
+ Post.search(:include => :blog, :select => 'id, title')
90
+ end
91
+
92
+ it 'should return a new search' do
93
+ @session.should_not_receive(:new_search)
94
+ Sunspot.new_search(Post).should respond_to(:execute)
95
+ end
96
+
97
+ describe 'stub search' do
98
+ before :each do
99
+ @search = Post.search
100
+ end
101
+
102
+ it 'should return empty results' do
103
+ @search.results.should == []
104
+ end
105
+
106
+ it 'should return empty hits' do
107
+ @search.hits.should == []
108
+ end
109
+
110
+ it 'should return zero total' do
111
+ @search.total.should == 0
112
+ end
113
+
114
+ it 'should return nil for a given facet' do
115
+ @search.facet(:category_id).should be_nil
116
+ end
117
+
118
+ it 'should return nil for a given dynamic facet' do
119
+ @search.dynamic_facet(:custom).should be_nil
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../../sunspot/lib/', __FILE__)
3
+
4
+ $:.unshift(lib) unless $:.include?(lib)
5
+
6
+ require 'sunspot/version'
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = "substantial-sunspot_rails"
10
+ s.version = Sunspot::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.authors = ['Mat Brown', 'Peer Allan', 'Dmitriy Dzema', 'Benjamin Krause', 'Marcel de Graaf', 'Brandon Keepers', 'Peter Berkenbosch',
13
+ 'Brian Atkinson', 'Tom Coleman', 'Matt Mitchell', 'Nathan Beyer', 'Kieran Topping', 'Nicolas Braem', 'Jeremy Ashkenas',
14
+ 'Dylan Vaughn', 'Brian Durand', 'Sam Granieri', 'Nick Zadrozny', 'Jason Ronallo']
15
+ s.email = ["mat@patch.com"]
16
+ s.homepage = 'http://github.com/outoftime/sunspot/tree/master/sunspot_rails'
17
+ s.summary = 'Rails integration for the Sunspot Solr search library'
18
+ s.description = <<-TEXT
19
+ Sunspot::Rails is an extension to the Sunspot library for Solr search.
20
+ Sunspot::Rails adds integration between Sunspot and ActiveRecord, including
21
+ defining search and indexing related methods on ActiveRecord models themselves,
22
+ running a Sunspot-compatible Solr instance for development and test
23
+ environments, and automatically commit Solr index changes at the end of each
24
+ Rails request.
25
+ TEXT
26
+
27
+ s.rubyforge_project = "sunspot"
28
+
29
+ s.files = `git ls-files`.split("\n")
30
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
31
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
32
+ s.require_paths = ["lib"]
33
+
34
+ s.add_dependency 'sunspot', Sunspot::VERSION
35
+ s.add_dependency 'nokogiri'
36
+
37
+ s.add_development_dependency 'rspec', '~> 1.2'
38
+ s.add_development_dependency 'rspec-rails', '~> 1.2'
39
+
40
+ s.rdoc_options << '--webcvs=http://github.com/outoftime/sunspot/tree/master/%s' <<
41
+ '--title' << 'Sunspot-Rails - Rails integration for the Sunspot Solr search library - API Documentation' <<
42
+ '--main' << 'README.rdoc'
43
+ end
metadata ADDED
@@ -0,0 +1,228 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: substantial-sunspot_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0.pre.111215
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Mat Brown
9
+ - Peer Allan
10
+ - Dmitriy Dzema
11
+ - Benjamin Krause
12
+ - Marcel de Graaf
13
+ - Brandon Keepers
14
+ - Peter Berkenbosch
15
+ - Brian Atkinson
16
+ - Tom Coleman
17
+ - Matt Mitchell
18
+ - Nathan Beyer
19
+ - Kieran Topping
20
+ - Nicolas Braem
21
+ - Jeremy Ashkenas
22
+ - Dylan Vaughn
23
+ - Brian Durand
24
+ - Sam Granieri
25
+ - Nick Zadrozny
26
+ - Jason Ronallo
27
+ autorequire:
28
+ bindir: bin
29
+ cert_chain: []
30
+ date: 2012-11-12 00:00:00.000000000 Z
31
+ dependencies:
32
+ - !ruby/object:Gem::Dependency
33
+ name: sunspot
34
+ requirement: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 2.0.0.pre.111215
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 2.0.0.pre.111215
48
+ - !ruby/object:Gem::Dependency
49
+ name: nokogiri
50
+ requirement: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ - !ruby/object:Gem::Dependency
65
+ name: rspec
66
+ requirement: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ version: '1.2'
72
+ type: :development
73
+ prerelease: false
74
+ version_requirements: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ version: '1.2'
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec-rails
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '1.2'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ~>
94
+ - !ruby/object:Gem::Version
95
+ version: '1.2'
96
+ description: ! " Sunspot::Rails is an extension to the Sunspot library for Solr
97
+ search.\n Sunspot::Rails adds integration between Sunspot and ActiveRecord, including\n
98
+ \ defining search and indexing related methods on ActiveRecord models themselves,\n
99
+ \ running a Sunspot-compatible Solr instance for development and test\n environments,
100
+ and automatically commit Solr index changes at the end of each\n Rails request.\n"
101
+ email:
102
+ - mat@patch.com
103
+ executables: []
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - .gitignore
108
+ - .rspec
109
+ - History.txt
110
+ - LICENSE
111
+ - MIT-LICENSE
112
+ - README.rdoc
113
+ - Rakefile
114
+ - TODO
115
+ - dev_tasks/rdoc.rake
116
+ - dev_tasks/release.rake
117
+ - dev_tasks/spec.rake
118
+ - dev_tasks/todo.rake
119
+ - gemfiles/rails-2.3.14
120
+ - gemfiles/rails-3.0.11
121
+ - gemfiles/rails-3.1.3
122
+ - generators/sunspot/sunspot_generator.rb
123
+ - generators/sunspot/templates/sunspot.yml
124
+ - install.rb
125
+ - lib/generators/sunspot_rails.rb
126
+ - lib/generators/sunspot_rails/install/install_generator.rb
127
+ - lib/generators/sunspot_rails/install/templates/config/sunspot.yml
128
+ - lib/substantial-sunspot_rails.rb
129
+ - lib/sunspot/rails.rb
130
+ - lib/sunspot/rails/adapters.rb
131
+ - lib/sunspot/rails/configuration.rb
132
+ - lib/sunspot/rails/init.rb
133
+ - lib/sunspot/rails/log_subscriber.rb
134
+ - lib/sunspot/rails/railtie.rb
135
+ - lib/sunspot/rails/railties/controller_runtime.rb
136
+ - lib/sunspot/rails/request_lifecycle.rb
137
+ - lib/sunspot/rails/searchable.rb
138
+ - lib/sunspot/rails/server.rb
139
+ - lib/sunspot/rails/solr_instrumentation.rb
140
+ - lib/sunspot/rails/solr_logging.rb
141
+ - lib/sunspot/rails/spec_helper.rb
142
+ - lib/sunspot/rails/stub_session_proxy.rb
143
+ - lib/sunspot/rails/tasks.rb
144
+ - lib/sunspot_rails.rb
145
+ - spec/configuration_spec.rb
146
+ - spec/model_lifecycle_spec.rb
147
+ - spec/model_spec.rb
148
+ - spec/rails_template/app/controllers/application_controller.rb
149
+ - spec/rails_template/app/controllers/posts_controller.rb
150
+ - spec/rails_template/app/models/author.rb
151
+ - spec/rails_template/app/models/blog.rb
152
+ - spec/rails_template/app/models/location.rb
153
+ - spec/rails_template/app/models/photo_post.rb
154
+ - spec/rails_template/app/models/post.rb
155
+ - spec/rails_template/app/models/post_with_auto.rb
156
+ - spec/rails_template/app/models/post_with_default_scope.rb
157
+ - spec/rails_template/config/boot.rb
158
+ - spec/rails_template/config/preinitializer.rb
159
+ - spec/rails_template/config/routes.rb
160
+ - spec/rails_template/config/sunspot.yml
161
+ - spec/rails_template/db/schema.rb
162
+ - spec/request_lifecycle_spec.rb
163
+ - spec/schema.rb
164
+ - spec/searchable_spec.rb
165
+ - spec/server_spec.rb
166
+ - spec/session_spec.rb
167
+ - spec/shared_examples/indexed_after_save.rb
168
+ - spec/shared_examples/not_indexed_after_save.rb
169
+ - spec/spec_helper.rb
170
+ - spec/stub_session_proxy_spec.rb
171
+ - substantial-sunspot_rails.gemspec
172
+ - tmp/.gitkeep
173
+ homepage: http://github.com/outoftime/sunspot/tree/master/sunspot_rails
174
+ licenses: []
175
+ post_install_message:
176
+ rdoc_options:
177
+ - --webcvs=http://github.com/outoftime/sunspot/tree/master/%s
178
+ - --title
179
+ - Sunspot-Rails - Rails integration for the Sunspot Solr search library - API Documentation
180
+ - --main
181
+ - README.rdoc
182
+ require_paths:
183
+ - lib
184
+ required_ruby_version: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ required_rubygems_version: !ruby/object:Gem::Requirement
191
+ none: false
192
+ requirements:
193
+ - - ! '>'
194
+ - !ruby/object:Gem::Version
195
+ version: 1.3.1
196
+ requirements: []
197
+ rubyforge_project: sunspot
198
+ rubygems_version: 1.8.24
199
+ signing_key:
200
+ specification_version: 3
201
+ summary: Rails integration for the Sunspot Solr search library
202
+ test_files:
203
+ - spec/configuration_spec.rb
204
+ - spec/model_lifecycle_spec.rb
205
+ - spec/model_spec.rb
206
+ - spec/rails_template/app/controllers/application_controller.rb
207
+ - spec/rails_template/app/controllers/posts_controller.rb
208
+ - spec/rails_template/app/models/author.rb
209
+ - spec/rails_template/app/models/blog.rb
210
+ - spec/rails_template/app/models/location.rb
211
+ - spec/rails_template/app/models/photo_post.rb
212
+ - spec/rails_template/app/models/post.rb
213
+ - spec/rails_template/app/models/post_with_auto.rb
214
+ - spec/rails_template/app/models/post_with_default_scope.rb
215
+ - spec/rails_template/config/boot.rb
216
+ - spec/rails_template/config/preinitializer.rb
217
+ - spec/rails_template/config/routes.rb
218
+ - spec/rails_template/config/sunspot.yml
219
+ - spec/rails_template/db/schema.rb
220
+ - spec/request_lifecycle_spec.rb
221
+ - spec/schema.rb
222
+ - spec/searchable_spec.rb
223
+ - spec/server_spec.rb
224
+ - spec/session_spec.rb
225
+ - spec/shared_examples/indexed_after_save.rb
226
+ - spec/shared_examples/not_indexed_after_save.rb
227
+ - spec/spec_helper.rb
228
+ - spec/stub_session_proxy_spec.rb