ts-datetime-delta 1.0.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Pat Allan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,50 @@
1
+ h1. Datetime Deltas for Thinking Sphinx
2
+
3
+ h2. Installation
4
+
5
+ You'll need Thinking Sphinx 1.3.0 or later.
6
+
7
+ <pre><code>gem install ts-datetime-delta --source http://gemcutter.org</code></pre>
8
+
9
+ In your @environment.rb@ file, with the rest of your gem dependencies:
10
+
11
+ <pre><code>config.gem 'ts-datetime-delta',
12
+ :lib => 'thinking_sphinx/deltas/datetime_delta'
13
+ :version => '>= 1.0.0',
14
+ :source => 'http://gemcutter.org'</code></pre>
15
+
16
+ And add the following line to the bottom of your @Rakefile@:
17
+
18
+ <pre><code>require 'thinking_sphinx/deltas/datetime_delta/tasks'</code></pre>
19
+
20
+ h2. Usage
21
+
22
+ For the indexes you want to use this delta approach, make sure you set that up in their @define_index@ blocks.
23
+
24
+ <pre><code>define_index do
25
+ # ...
26
+
27
+ set_property :delta => :datetime
28
+ end</code></pre>
29
+
30
+ If you want to use a column other than @updated_at@, you can specify it using the @:delta_column@ option. The same goes for the threshold, which defaults to one day.
31
+
32
+ <pre><code>set_property :delta => :datetime,
33
+ :threshold => 1.hour,
34
+ :delta_column => :changed_at</code></pre>
35
+
36
+ Then, while your Rails application is running, you'll need to run the delta indexing rake task regularly - as often as your threshold, allowing for some time for the indexing to actually happen.
37
+
38
+ For example, if you're going to run the delta indexing task every hour, I would recommend setting your threshold to 70 minutes.
39
+
40
+ To ensure this rake task is called regularly, it's best to set it up as a recurring task via cron or similar tools.
41
+
42
+ <pre><code>rake thinking_sphinx:index:delta</code></pre>
43
+
44
+ The shorthand version is:
45
+
46
+ <pre><code>rake ts:in:delta</code></pre>
47
+
48
+ h2. Copyright
49
+
50
+ Copyright (c) 2009 Pat Allan, and released under an MIT Licence.
@@ -0,0 +1,66 @@
1
+ Feature: Datetime Delta Indexing
2
+ In order to have delta indexing on frequently-updated sites
3
+ Developers
4
+ Should be able to use an existing datetime column to track changes
5
+
6
+ Scenario: Delta Index should not fire automatically
7
+ Given Sphinx is running
8
+ And I am searching on thetas
9
+ When I search for one
10
+ Then I should get 1 result
11
+
12
+ When I change the name of theta one to eleven
13
+ And I wait for Sphinx to catch up
14
+ And I search for one
15
+ Then I should get 1 result
16
+
17
+ When I search for eleven
18
+ Then I should get 0 results
19
+
20
+ Scenario: Delta Index should fire when jobs are run
21
+ Given Sphinx is running
22
+ And I am searching on thetas
23
+ When I search for two
24
+ Then I should get 1 result
25
+
26
+ When I change the name of theta two to twelve
27
+ And I wait for Sphinx to catch up
28
+ And I search for twelve
29
+ Then I should get 0 results
30
+
31
+ When I index the theta datetime delta
32
+ And I wait for Sphinx to catch up
33
+ And I search for twelve
34
+ Then I should get 1 result
35
+
36
+ When I search for two
37
+ Then I should get 0 results
38
+
39
+ Scenario: New records should be merged into the core index
40
+ Given Sphinx is running
41
+ And I am searching on thetas
42
+ When I search for thirteen
43
+ Then I should get 0 results
44
+
45
+ When I create a new theta named thirteen
46
+ And I search for thirteen
47
+ Then I should get 0 results
48
+
49
+ When I index the theta datetime delta
50
+ And I wait for Sphinx to catch up
51
+ And I search for thirteen
52
+ Then I should get 1 result
53
+
54
+ When I search for the document id of theta thirteen in the theta_core index
55
+ Then it should exist
56
+
57
+ Scenario: Deleting records
58
+ Given Sphinx is running
59
+ And I am searching on thetas
60
+ When I search for three
61
+ Then I should get 1 result
62
+
63
+ When I delete the theta named three
64
+ And I wait for Sphinx to catch up
65
+ And I search for three
66
+ Then I should get 0 results
@@ -0,0 +1,61 @@
1
+ Before do
2
+ $queries_executed = []
3
+
4
+ @model = nil
5
+ @method = :search
6
+ @query = ""
7
+ @conditions = {}
8
+ @with = {}
9
+ @without = {}
10
+ @with_all = {}
11
+ @options = {}
12
+ @results = nil
13
+ end
14
+
15
+ Given "Sphinx is running" do
16
+ ThinkingSphinx::Configuration.instance.controller.should be_running
17
+ end
18
+
19
+ Given /^I am searching on (.+)$/ do |model|
20
+ @model = model.gsub(/\s/, '_').singularize.camelize.constantize
21
+ end
22
+
23
+ When "I wait for Sphinx to catch up" do
24
+ sleep(0.25)
25
+ end
26
+
27
+ When /^I search for (\w+)$/ do |query|
28
+ @results = nil
29
+ @query = query
30
+ end
31
+
32
+ When /^I search for the document id of (\w+) (\w+) in the (\w+) index$/ do |model, name, index|
33
+ model = model.gsub(/\s/, '_').camelize.constantize
34
+ @id = model.find_by_name(name).sphinx_document_id
35
+ @index = index
36
+ end
37
+
38
+ Then /^I should get (\d+) results?$/ do |count|
39
+ results.length.should == count.to_i
40
+ end
41
+
42
+ Then "it should exist" do
43
+ ThinkingSphinx::Search.search_for_id(@id, @index).should == true
44
+ end
45
+
46
+ Then "it should not exist" do
47
+ ThinkingSphinx::Search.search_for_id(@id, @index).should == false
48
+ end
49
+
50
+ def results
51
+ @results ||= (@model || ThinkingSphinx).send(
52
+ @method,
53
+ @query,
54
+ @options.merge(
55
+ :conditions => @conditions,
56
+ :with => @with,
57
+ :without => @without,
58
+ :with_all => @with_all
59
+ )
60
+ )
61
+ end
@@ -0,0 +1,15 @@
1
+ When /^I index the theta datetime delta$/ do
2
+ Theta.sphinx_indexes.first.delta_object.delayed_index(Theta)
3
+ end
4
+
5
+ When /^I change the name of theta (\w+) to (\w+)$/ do |current, replacement|
6
+ Theta.find_by_name(current).update_attributes(:name => replacement)
7
+ end
8
+
9
+ When /^I create a new theta named (\w+)$/ do |name|
10
+ Theta.create(:name => name)
11
+ end
12
+
13
+ When /^I delete the theta named (\w+)$/ do |name|
14
+ Theta.find_by_name(name).destroy
15
+ end
@@ -0,0 +1,3 @@
1
+ username: root
2
+ host: localhost
3
+ password:
@@ -0,0 +1,5 @@
1
+ username: thinking_sphinx
2
+ host: localhost
3
+ password: thinking_sphinx
4
+ pool: 1
5
+ min_messages: warning
@@ -0,0 +1,10 @@
1
+ Theta.create :name => "one"
2
+ Theta.create :name => "two"
3
+ Theta.create :name => "three"
4
+ Theta.create :name => "four"
5
+ Theta.create :name => "five"
6
+ Theta.create :name => "six"
7
+ Theta.create :name => "seven"
8
+ Theta.create :name => "eight"
9
+ Theta.create :name => "nine"
10
+ Theta.create :name => "ten"
@@ -0,0 +1,5 @@
1
+ ActiveRecord::Base.connection.create_table :thetas, :force => true do |t|
2
+ t.column :name, :string, :null => false
3
+ t.column :created_at, :datetime, :null => false
4
+ t.column :updated_at, :datetime, :null => false
5
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'cucumber'
3
+ require 'spec/expectations'
4
+ require 'fileutils'
5
+ require 'active_record'
6
+
7
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
8
+
9
+ require 'cucumber/thinking_sphinx/internal_world'
10
+
11
+ world = Cucumber::ThinkingSphinx::InternalWorld.new
12
+ world.configure_database
13
+
14
+ require 'thinking_sphinx'
15
+ require 'thinking_sphinx/deltas/datetime_delta'
16
+
17
+ world.setup
@@ -0,0 +1,7 @@
1
+ class Theta < ActiveRecord::Base
2
+ define_index do
3
+ indexes :name, :sortable => true
4
+
5
+ set_property :delta => :datetime, :threshold => 1.hour
6
+ end
7
+ end
@@ -0,0 +1,124 @@
1
+ # Datetime Deltas for Thinking Sphinx
2
+ #
3
+ # This documentation is aimed at those reading the code. If you're looking for
4
+ # a guide to Thinking Sphinx and/or deltas, I recommend you start with the
5
+ # Thinking Sphinx site instead - or the README for this library at the very
6
+ # least.
7
+ #
8
+ # @author Patrick Allan
9
+ # @see http://ts.freelancing-gods.com Thinking Sphinx
10
+ #
11
+ class ThinkingSphinx::Deltas::DatetimeDelta < ThinkingSphinx::Deltas::DefaultDelta
12
+ attr_accessor :column, :threshold
13
+
14
+ # Initialises the Delta object for the given index and settings. All handled
15
+ # by Thinking Sphinx, so you shouldn't need to call this method yourself in
16
+ # general day-to-day situations.
17
+ #
18
+ # @example
19
+ # ThinkingSphinx::Deltas::DatetimeDelta.new index,
20
+ # :delta_column => :updated_at,
21
+ # :threshold => 1.day
22
+ #
23
+ # @param [ThinkingSphinx::Index] index the index using this delta object
24
+ # @param [Hash] options a hash of options for the index
25
+ # @option options [Symbol] :delta_column (:updated_at) The column to use for
26
+ # tracking when a record has changed. Default to :updated_at.
27
+ # @option options [Integer] :threshold (1.day) The window of time to store
28
+ # changes for, in seconds. Defaults to one day.
29
+ #
30
+ def initialize(index, options = {})
31
+ @index = index
32
+ @column = options.delete(:delta_column) || :updated_at
33
+ @threshold = options.delete(:threshold) || 1.day
34
+ end
35
+
36
+ # Does absolutely nothing, beyond returning true. Thinking Sphinx expects
37
+ # this method, though, and we don't want to use the inherited behaviour from
38
+ # DefaultDelta.
39
+ #
40
+ # All the real indexing logic is done by the delayed_index method.
41
+ #
42
+ # @param [Class] model the ActiveRecord model to index.
43
+ # @param [ActiveRecord::Base] instance the instance of the given model that
44
+ # has changed. Optional.
45
+ # @return [Boolean] true
46
+ # @see #delayed_index
47
+ #
48
+ def index(model, instance = nil)
49
+ # do nothing
50
+ true
51
+ end
52
+
53
+ # Processes the delta index for the given model, and then merges the relevant
54
+ # core and delta indexes together. By default, the output of these indexer
55
+ # commands are printed to stdout. If you'd rather it didn't, set
56
+ # ThinkingSphinx.suppress_delta_output to true.
57
+ #
58
+ # @param [Class] model the ActiveRecord model to index
59
+ # @return [Boolean] true
60
+ #
61
+ def delayed_index(model)
62
+ config = ThinkingSphinx::Configuration.instance
63
+ rotate = ThinkingSphinx.sphinx_running? ? " --rotate" : ""
64
+
65
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file}#{rotate} #{delta_index_name model}`
66
+ output += `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file}#{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
67
+ puts output unless ThinkingSphinx.suppress_delta_output?
68
+
69
+ true
70
+ end
71
+
72
+ # Toggles the given instance to be flagged as part of the next delta indexing.
73
+ # For datetime deltas, this means do nothing at all.
74
+ #
75
+ # @param [ActiveRecord::Base] instance the instance to be toggled
76
+ #
77
+ def toggle(instance)
78
+ # do nothing
79
+ end
80
+
81
+ # Report whether a given instance is considered toggled (part of the next
82
+ # delta process). For datetime deltas, this is true if the delta column
83
+ # (updated_at by default) has a value within the threshold. Otherwise, false
84
+ # is returned.
85
+ #
86
+ # @param [ActiveRecord::Base] instance the instance to check
87
+ # @return [Boolean] True if within the threshold window, otherwise false.
88
+ #
89
+ def toggled(instance)
90
+ instance.send(@column) > @threshold.ago
91
+ end
92
+
93
+ # Returns the SQL query that resets the model data after a normal index. For
94
+ # datetime deltas, nothing needs to be done, so this method returns nil.
95
+ #
96
+ # @param [Class] model The ActiveRecord model that is requesting the query
97
+ # @return [NilClass] Always nil
98
+ #
99
+ def reset_query(model)
100
+ nil
101
+ end
102
+
103
+ # A SQL condition (as part of the WHERE clause) that limits the result set to
104
+ # just the delta data, or all data, depending on whether the toggled argument
105
+ # is true or not. For datetime deltas, the former value is a check on the
106
+ # delta column being within the threshold. In the latter's case, no condition
107
+ # is needed, so nil is returned.
108
+ #
109
+ # @param [Class] model The ActiveRecord model to generate the SQL condition
110
+ # for.
111
+ # @param [Boolean] toggled Whether the query should request delta documents or
112
+ # all documents.
113
+ # @return [String, NilClass] The SQL condition if the toggled version is
114
+ # requested, otherwise nil.
115
+ #
116
+ def clause(model, toggled)
117
+ if toggled
118
+ "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
119
+ " > #{adapter.time_difference(@threshold)}"
120
+ else
121
+ nil
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,23 @@
1
+ namespace :thinking_sphinx do
2
+ namespace :index do
3
+ desc "Index Thinking Sphinx datetime delta indexes"
4
+ task :delta => :app_env do
5
+ ThinkingSphinx.indexed_models.select { |model|
6
+ model.constantize.sphinx_indexes.any? { |index| index.delta? }
7
+ }.each do |model|
8
+ model.constantize.sphinx_indexes.select { |index|
9
+ index.delta? && index.delta_object.respond_to?(:delayed_index)
10
+ }.each { |index|
11
+ index.delta_object.delayed_index(index.model)
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ namespace :ts do
19
+ namespace :in do
20
+ desc "Index Thinking Sphinx datetime delta indexes"
21
+ task :delta => "thinking_sphinx:index:delta"
22
+ end
23
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ require 'thinking_sphinx'
8
+ require 'thinking_sphinx/deltas/delayed_delta'
9
+
10
+ Spec::Runner.configure do |config|
11
+ #
12
+ end
@@ -0,0 +1,130 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Deltas::DatetimeDelta do
4
+ before :each do
5
+ @datetime_delta = ThinkingSphinx::Deltas::DatetimeDelta.new(
6
+ stub('index'), {}
7
+ )
8
+ end
9
+
10
+ describe '#index' do
11
+ it "should do nothing to the model" do
12
+ @datetime_delta.index(stub('model'))
13
+ end
14
+
15
+ it "should do nothing to the instance, if provided" do
16
+ @datetime_delta.index(stub('model'), stub('instance'))
17
+ end
18
+
19
+ it "should make no system calls" do
20
+ @datetime_delta.stub! :` => true
21
+ @datetime_delta.stub! :system => true
22
+
23
+ @datetime_delta.should_not_receive(:`)
24
+ @datetime_delta.should_not_receive(:system)
25
+
26
+ @datetime_delta.index(stub('model'), stub('instance'))
27
+ end
28
+
29
+ it "should return true" do
30
+ @datetime_delta.index(stub('model')).should be_true
31
+ end
32
+ end
33
+
34
+ describe '#delayed_index' do
35
+ before :each do
36
+ @model = stub('foo')
37
+ @model.stub!(:name => 'foo')
38
+ @model.stub!(:source_of_sphinx_index => @model)
39
+
40
+ ThinkingSphinx.suppress_delta_output = false
41
+
42
+ @datetime_delta.stub! :` => ""
43
+ @datetime_delta.stub! :puts => nil
44
+ end
45
+
46
+ it "should process the delta index for the given model" do
47
+ @datetime_delta.should_receive(:`).
48
+ with('indexer --config /config/development.sphinx.conf foo_delta')
49
+
50
+ @datetime_delta.delayed_index(@model)
51
+ end
52
+
53
+ it "should merge the core and delta indexes for the given model" do
54
+ @datetime_delta.should_receive(:`).with('indexer --config /config/development.sphinx.conf --merge foo_core foo_delta --merge-dst-range sphinx_deleted 0 0')
55
+
56
+ @datetime_delta.delayed_index(@model)
57
+ end
58
+
59
+ it "should include --rotate if Sphinx is running" do
60
+ ThinkingSphinx.stub!(:sphinx_running? => true)
61
+ @datetime_delta.should_receive(:`) do |command|
62
+ command.should match(/\s--rotate\s/)
63
+ end
64
+
65
+ @datetime_delta.delayed_index(@model)
66
+ end
67
+
68
+ it "should output the details by default" do
69
+ @datetime_delta.should_receive(:puts)
70
+
71
+ @datetime_delta.delayed_index(@model)
72
+ end
73
+
74
+ it "should hide the details if suppressing delta output" do
75
+ ThinkingSphinx.suppress_delta_output = true
76
+ @datetime_delta.should_not_receive(:puts)
77
+
78
+ @datetime_delta.delayed_index(@model)
79
+ end
80
+ end
81
+
82
+ describe '#toggle' do
83
+ it "should do nothing to the instance" do
84
+ @datetime_delta.toggle(stub('instance'))
85
+ end
86
+ end
87
+
88
+ describe '#toggled' do
89
+ it "should return true if the column value is more recent than the threshold" do
90
+ instance = stub('instance', :updated_at => 20.minutes.ago)
91
+ @datetime_delta.threshold = 30.minutes
92
+
93
+ @datetime_delta.toggled(instance).should be_true
94
+ end
95
+
96
+ it "should return false if the column value is older than the threshold" do
97
+ instance = stub('instance', :updated_at => 30.minutes.ago)
98
+ @datetime_delta.threshold = 20.minutes
99
+
100
+ @datetime_delta.toggled(instance).should be_false
101
+ end
102
+ end
103
+
104
+ describe '#reset_query' do
105
+ it "should be nil" do
106
+ @datetime_delta.reset_query(@model).should be_nil
107
+ end
108
+ end
109
+
110
+ describe '#clause' do
111
+ before :each do
112
+ @model = stub('model', :connection => stub('connection'))
113
+ @model.stub!(:quoted_table_name => '`foo`')
114
+ @model.connection.stub!(:quote_column_name => '`updated_at`')
115
+
116
+ @datetime_delta.stub!(
117
+ :adapter => stub('adapter', :time_difference => 'time_difference')
118
+ )
119
+ end
120
+
121
+ it "should return nil if not for the toggled results" do
122
+ @datetime_delta.clause(@model, false).should be_nil
123
+ end
124
+
125
+ it "should return only records within the threshold" do
126
+ @datetime_delta.clause(@model, true).
127
+ should == '`foo`.`updated_at` > time_difference'
128
+ end
129
+ end
130
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ts-datetime-delta
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Pat Allan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-03 00:00:00 +11:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: cucumber
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Manage delta indexes via datetime columns for Thinking Sphinx
46
+ email: pat@freelancing-gods.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.textile
54
+ files:
55
+ - LICENSE
56
+ - README.textile
57
+ - lib/thinking_sphinx/deltas/datetime_delta.rb
58
+ - lib/thinking_sphinx/deltas/datetime_delta/tasks.rb
59
+ has_rdoc: true
60
+ homepage: http://github.com/freelancing-god/ts-datetime-delta
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --charset=UTF-8
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.5
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Thinking Sphinx - Datetime Deltas
87
+ test_files:
88
+ - features/datetime_deltas.feature
89
+ - features/step_definitions/common_steps.rb
90
+ - features/step_definitions/datetime_delta_steps.rb
91
+ - features/support/database.example.yml
92
+ - features/support/database.yml
93
+ - features/support/db/fixtures/thetas.rb
94
+ - features/support/db/migrations/create_thetas.rb
95
+ - features/support/env.rb
96
+ - features/support/models/theta.rb
97
+ - spec/spec.opts
98
+ - spec/spec_helper.rb
99
+ - spec/thinking_sphinx/deltas/datetime_delta_spec.rb