thinking-sphinx 3.1.2 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/Appraisals +7 -3
  4. data/HISTORY +12 -0
  5. data/README.textile +5 -3
  6. data/gemfiles/rails_3_2.gemfile +1 -1
  7. data/gemfiles/rails_4_0.gemfile +1 -1
  8. data/gemfiles/rails_4_1.gemfile +1 -1
  9. data/gemfiles/rails_4_2.gemfile +11 -0
  10. data/lib/thinking_sphinx.rb +1 -1
  11. data/lib/thinking_sphinx/active_record.rb +1 -1
  12. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +3 -1
  13. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +1 -1
  14. data/lib/thinking_sphinx/active_record/filter_reflection.rb +75 -0
  15. data/lib/thinking_sphinx/active_record/polymorpher.rb +4 -4
  16. data/lib/thinking_sphinx/active_record/property_query.rb +1 -1
  17. data/lib/thinking_sphinx/active_record/simple_many_query.rb +1 -1
  18. data/lib/thinking_sphinx/active_record/sql_builder.rb +0 -4
  19. data/lib/thinking_sphinx/active_record/sql_builder/statement.rb +0 -1
  20. data/lib/thinking_sphinx/configuration.rb +6 -3
  21. data/lib/thinking_sphinx/core/index.rb +1 -1
  22. data/lib/thinking_sphinx/excerpter.rb +4 -1
  23. data/lib/thinking_sphinx/facet_search.rb +8 -2
  24. data/lib/thinking_sphinx/index_set.rb +33 -11
  25. data/lib/thinking_sphinx/middlewares/inquirer.rb +1 -1
  26. data/lib/thinking_sphinx/middlewares/sphinxql.rb +3 -1
  27. data/lib/thinking_sphinx/railtie.rb +4 -2
  28. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +2 -4
  29. data/lib/thinking_sphinx/real_time/index.rb +2 -0
  30. data/lib/thinking_sphinx/real_time/index/template.rb +1 -1
  31. data/spec/acceptance/big_integers_spec.rb +27 -6
  32. data/spec/acceptance/facets_spec.rb +1 -2
  33. data/spec/acceptance/real_time_updates_spec.rb +8 -0
  34. data/spec/acceptance/remove_deleted_records_spec.rb +4 -2
  35. data/spec/acceptance/searching_across_schemas_spec.rb +38 -0
  36. data/spec/acceptance/searching_with_sti_spec.rb +19 -7
  37. data/spec/internal/app/indices/bird_index.rb +4 -0
  38. data/spec/internal/app/indices/product_index.rb +21 -0
  39. data/spec/internal/db/schema.rb +9 -9
  40. data/spec/spec_helper.rb +2 -1
  41. data/spec/support/multi_schema.rb +46 -0
  42. data/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb +5 -3
  43. data/spec/thinking_sphinx/active_record/filter_reflection_spec.rb +172 -0
  44. data/spec/thinking_sphinx/active_record/polymorpher_spec.rb +18 -11
  45. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +0 -105
  46. data/spec/thinking_sphinx/configuration_spec.rb +0 -13
  47. data/spec/thinking_sphinx/facet_search_spec.rb +1 -2
  48. data/spec/thinking_sphinx/index_set_spec.rb +31 -13
  49. data/spec/thinking_sphinx/middlewares/inquirer_spec.rb +19 -0
  50. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +6 -4
  51. data/thinking-sphinx.gemspec +1 -1
  52. metadata +13 -6
  53. data/lib/thinking_sphinx/active_record/filtered_reflection.rb +0 -75
  54. data/spec/thinking_sphinx/active_record/filtered_reflection_spec.rb +0 -141
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 629217e38872edc12e92be134409a509b84f1304
4
- data.tar.gz: 4b19eec90cfe3b560b1fcdd56a7018f07704ec94
3
+ metadata.gz: c17f93cf2e09238f8df63a50264d09abe50051ba
4
+ data.tar.gz: 90d74a5a5d8f5b7edc71826ec67adeb9ae4550b3
5
5
  SHA512:
6
- metadata.gz: 5116aad817ad119c97f05ec5bcdf330ca89ecae39ecac5695c4b9573ba062a88e25b6c8db22c596a32ec57ca8c044b2805360696b4b6342196d19b775886e340
7
- data.tar.gz: 6923c30ad7163f0ec5c286b5cf98403c27618dc57191bff659808000f9f51bcf2e1ef0eec22fbf6ecc54752a9e1e6474d6e62a65b65db6cc462d8657beae183e
6
+ metadata.gz: c861cf2251899f29b7f82d7c471918f9147042792f5856e5205f4dc431dfac38e1e41204e74415710848ce8d1ce72333c296cf695250930d3070aa55f629ff48
7
+ data.tar.gz: af1e25122e4710693c232f98e1ffd9a03092222417a2b5f20a274dcabc30785047f05bade7212689de939ee2724ce47161a665b14b6b14731ef8220d76bf75c1
data/.travis.yml CHANGED
@@ -19,3 +19,5 @@ env:
19
19
  gemfile:
20
20
  - gemfiles/rails_3_2.gemfile
21
21
  - gemfiles/rails_4_0.gemfile
22
+ - gemfiles/rails_4_1.gemfile
23
+ - gemfiles/rails_4_2.gemfile
data/Appraisals CHANGED
@@ -1,11 +1,15 @@
1
1
  appraise 'rails_3_2' do
2
- gem 'rails', '~> 3.2.0'
2
+ gem 'rails', '~> 3.2.21'
3
3
  end
4
4
 
5
5
  appraise 'rails_4_0' do
6
- gem 'rails', '~> 4.0.2'
6
+ gem 'rails', '~> 4.0.12'
7
7
  end
8
8
 
9
9
  appraise 'rails_4_1' do
10
- gem 'rails', '~> 4.1.0.beta1'
10
+ gem 'rails', '~> 4.1.8'
11
+ end
12
+
13
+ appraise 'rails_4_2' do
14
+ gem 'rails', '~> 4.2.0'
11
15
  end
data/HISTORY CHANGED
@@ -1,3 +1,15 @@
1
+ 2015-01-21: 3.1.3
2
+ * [CHANGE] Log excerpt SphinxQL queries just like the search queries.
3
+ * [CHANGE] Load Railtie if Rails::Railtie is defined, instead of just Rails (Andrew Cone).
4
+ * [CHANGE] Convert raw Sphinx results to an array when querying (Bryan Ricker).
5
+ * [FIX] Generate de-polymorphised associations properly for Rails 4.2
6
+ * [FIX] Use reflect_on_association instead of reflections, to stick to the public ActiveRecord::Base API.
7
+ * [FIX] Don't load ActiveRecord early - fixes a warning in Rails 4.2.
8
+ * [FEATURE] Allow for custom offset references with the :offset_as option - thus one model across many schemas with Apartment can be treated differently.
9
+ * [FEATURE] Allow for custom IndexSet classes.
10
+ * [FIX] Don't double-up on STI filtering, already handled by Rails.
11
+ * [CHANGE] Add bigint support for real-time indices, and use bigints for the sphinx_internal_id attribute (mapped to model primary keys) (Chance Downs).
12
+
1
13
  2014-11-04: 3.1.2
2
14
  * [CHANGE] regenerate task now only deletes index files for real-time indices.
3
15
  * [CHANGE] Raise an exception when a populated search query is modified (as it can't be requeried).
data/README.textile CHANGED
@@ -1,11 +1,13 @@
1
1
  h1. Thinking Sphinx
2
2
 
3
- Thinking Sphinx is a library for connecting ActiveRecord to the Sphinx full-text search tool, and integrates closely with Rails (but also works with other Ruby web frameworks). The current release is v3.1.1.
3
+ Thinking Sphinx is a library for connecting ActiveRecord to the Sphinx full-text search tool, and integrates closely with Rails (but also works with other Ruby web frameworks). The current release is v3.1.3.
4
4
 
5
5
  h2. Upgrading
6
6
 
7
7
  Please refer to the release notes for any changes you need to make when upgrading:
8
8
 
9
+ * "v3.1.3":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.3
10
+ * "v3.1.2":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.2
9
11
  * "v3.1.1":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.1
10
12
  * "v3.1.0":https://github.com/pat/thinking-sphinx/releases/tag/v3.1.0
11
13
  * "v3.0.6":https://github.com/pat/thinking-sphinx/releases/tag/v3.0.6
@@ -18,7 +20,7 @@ It's a gem, so install it like you would any other gem. You will also need to sp
18
20
 
19
21
  <pre><code>gem 'mysql2', '~> 0.3.13', :platform => :ruby
20
22
  gem 'jdbc-mysql', '~> 5.1.28', :platform => :jruby
21
- gem 'thinking-sphinx', '~> 3.1.1'</code></pre>
23
+ gem 'thinking-sphinx', '~> 3.1.3'</code></pre>
22
24
 
23
25
  The MySQL gems mentioned are required for connecting to Sphinx, so please include it even when you're using PostgreSQL for your database.
24
26
 
@@ -75,4 +77,4 @@ You can then run the unit tests with @rake spec:unit@, the acceptance tests with
75
77
 
76
78
  h2. Licence
77
79
 
78
- Copyright (c) 2007-2014, Thinking Sphinx is developed and maintained by Pat Allan, and is released under the open MIT Licence. Many thanks to "all who have contributed patches":https://github.com/pat/thinking-sphinx/contributors.
80
+ Copyright (c) 2007-2015, Thinking Sphinx is developed and maintained by Pat Allan, and is released under the open MIT Licence. Many thanks to "all who have contributed patches":https://github.com/pat/thinking-sphinx/contributors.
@@ -6,6 +6,6 @@ gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
6
6
  gem "pg", "~> 0.16.0", :platform=>:ruby
7
7
  gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
8
8
  gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
9
- gem "rails", "~> 3.2.0"
9
+ gem "rails", "~> 3.2.21"
10
10
 
11
11
  gemspec :path=>"../"
@@ -6,6 +6,6 @@ gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
6
6
  gem "pg", "~> 0.16.0", :platform=>:ruby
7
7
  gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
8
8
  gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
9
- gem "rails", "~> 4.0.2"
9
+ gem "rails", "~> 4.0.12"
10
10
 
11
11
  gemspec :path=>"../"
@@ -6,6 +6,6 @@ gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
6
6
  gem "pg", "~> 0.16.0", :platform=>:ruby
7
7
  gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
8
8
  gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
9
- gem "rails", "~> 4.1.0.beta1"
9
+ gem "rails", "~> 4.1.8"
10
10
 
11
11
  gemspec :path=>"../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "mysql2", "~> 0.3.12b4", :platform=>:ruby
6
+ gem "pg", "~> 0.16.0", :platform=>:ruby
7
+ gem "activerecord-jdbcmysql-adapter", "~> 1.3.4", :platform=>:jruby
8
+ gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.4", :platform=>:jruby
9
+ gem "rails", "~> 4.2.0"
10
+
11
+ gemspec :path=>"../"
@@ -77,4 +77,4 @@ require 'thinking_sphinx/distributed'
77
77
  require 'thinking_sphinx/logger'
78
78
  require 'thinking_sphinx/real_time'
79
79
 
80
- require 'thinking_sphinx/railtie' if defined?(Rails)
80
+ require 'thinking_sphinx/railtie' if defined?(Rails::Railtie)
@@ -14,7 +14,7 @@ require 'thinking_sphinx/active_record/column'
14
14
  require 'thinking_sphinx/active_record/column_sql_presenter'
15
15
  require 'thinking_sphinx/active_record/database_adapters'
16
16
  require 'thinking_sphinx/active_record/field'
17
- require 'thinking_sphinx/active_record/filtered_reflection'
17
+ require 'thinking_sphinx/active_record/filter_reflection'
18
18
  require 'thinking_sphinx/active_record/index'
19
19
  require 'thinking_sphinx/active_record/interpreter'
20
20
  require 'thinking_sphinx/active_record/join_association'
@@ -14,6 +14,8 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeleteCallbacks <
14
14
  private
15
15
 
16
16
  def indices
17
- ThinkingSphinx::IndexSet.new([instance.class], []).to_a
17
+ ThinkingSphinx::Configuration.instance.index_set_class.new(
18
+ :classes => [instance.class]
19
+ ).to_a
18
20
  end
19
21
  end
@@ -42,7 +42,7 @@ class ThinkingSphinx::ActiveRecord::Callbacks::DeltaCallbacks <
42
42
  end
43
43
 
44
44
  def indices
45
- @indices ||= ThinkingSphinx::IndexSet.new [instance.class], []
45
+ @indices ||= config.index_set_class.new :classes => [instance.class]
46
46
  end
47
47
 
48
48
  def processors
@@ -0,0 +1,75 @@
1
+ class ThinkingSphinx::ActiveRecord::FilterReflection
2
+ attr_reader :reflection, :class_name
3
+
4
+ delegate :foreign_type, :active_record, :to => :reflection
5
+
6
+ def self.call(reflection, name, class_name)
7
+ filter = new(reflection, class_name)
8
+ klass = reflection.class
9
+
10
+ if defined?(ActiveRecord::Reflection::MacroReflection)
11
+ klass.new name, filter.scope, filter.options, reflection.active_record
12
+ elsif reflection.respond_to?(:scope)
13
+ klass.new reflection.macro, name, filter.scope, filter.options,
14
+ reflection.active_record
15
+ else
16
+ klass.new reflection.macro, name, filter.options,
17
+ reflection.active_record
18
+ end
19
+ end
20
+
21
+ def initialize(reflection, class_name)
22
+ @reflection, @class_name = reflection, class_name
23
+ @options = reflection.options.clone
24
+ end
25
+
26
+ def options
27
+ @options.delete :polymorphic
28
+ @options[:class_name] = class_name
29
+ @options[:foreign_key] ||= "#{reflection.name}_id"
30
+ @options[:foreign_type] = reflection.foreign_type
31
+
32
+ if reflection.respond_to?(:scope)
33
+ @options[:sphinx_internal_filtered] = true
34
+ return @options
35
+ end
36
+
37
+ case @options[:conditions]
38
+ when nil
39
+ @options[:conditions] = condition
40
+ when Array
41
+ @options[:conditions] << condition
42
+ when Hash
43
+ @options[:conditions].merge!(reflection.foreign_type => @options[:class_name])
44
+ else
45
+ @options[:conditions] << " AND #{condition}"
46
+ end
47
+
48
+ @options
49
+ end
50
+
51
+ def scope
52
+ if ::Joiner::Joins.instance_methods.include?(:join_association_class)
53
+ return nil
54
+ end
55
+
56
+ lambda { |association|
57
+ reflection = association.reflection
58
+ klass = reflection.class_name.constantize
59
+ where(
60
+ association.parent.aliased_table_name.to_sym =>
61
+ {reflection.foreign_type => klass.base_class.name}
62
+ )
63
+ }
64
+ end
65
+
66
+ private
67
+
68
+ def condition
69
+ "::ts_join_alias::.#{quoted_foreign_type} = '#{class_name}'"
70
+ end
71
+
72
+ def quoted_foreign_type
73
+ active_record.connection.quote_column_name foreign_type
74
+ end
75
+ end
@@ -14,7 +14,7 @@ class ThinkingSphinx::ActiveRecord::Polymorpher
14
14
 
15
15
  def append_reflections
16
16
  mappings.each do |class_name, name|
17
- next if klass.reflections[name]
17
+ next if klass.reflect_on_association(name)
18
18
 
19
19
  reflection = clone_with name, class_name
20
20
  if ActiveRecord::Reflection.respond_to?(:add_reflection)
@@ -26,7 +26,7 @@ class ThinkingSphinx::ActiveRecord::Polymorpher
26
26
  end
27
27
 
28
28
  def clone_with(name, class_name)
29
- ThinkingSphinx::ActiveRecord::FilteredReflection.clone_with_filter(
29
+ ThinkingSphinx::ActiveRecord::FilterReflection.call(
30
30
  reflection, name, class_name
31
31
  )
32
32
  end
@@ -51,12 +51,12 @@ class ThinkingSphinx::ActiveRecord::Polymorpher
51
51
  end
52
52
 
53
53
  def reflection
54
- @reflection ||= klass.reflections[column.__name]
54
+ @reflection ||= klass.reflect_on_association column.__name
55
55
  end
56
56
 
57
57
  def klass
58
58
  @klass ||= column.__stack.inject(source.model) { |parent, key|
59
- parent.reflections[key].klass
59
+ parent.reflect_on_association(key).klass
60
60
  }
61
61
  end
62
62
  end
@@ -110,7 +110,7 @@ primary key.
110
110
  base = source.model
111
111
 
112
112
  column.__stack.collect { |key|
113
- reflection = base.reflections[key]
113
+ reflection = base.reflect_on_association key
114
114
  base = reflection.klass
115
115
 
116
116
  extend_reflection reflection
@@ -8,7 +8,7 @@ class ThinkingSphinx::ActiveRecord::SimpleManyQuery <
8
8
  private
9
9
 
10
10
  def reflection
11
- @reflection ||= source.model.reflections[column.__stack.first]
11
+ @reflection ||= source.model.reflect_on_association column.__stack.first
12
12
  end
13
13
 
14
14
  def quoted_foreign_key
@@ -100,10 +100,6 @@ module ThinkingSphinx
100
100
  "($id - #{source.offset}) / #{config.indices.count}"
101
101
  end
102
102
 
103
- def inheritance_column_condition
104
- "#{quoted_inheritance_column} = '#{model_name}'"
105
- end
106
-
107
103
  def range_condition
108
104
  condition = []
109
105
  condition << "#{quoted_primary_key} BETWEEN $start AND $end" unless source.disable_range?
@@ -129,7 +129,6 @@ module ThinkingSphinx
129
129
 
130
130
  def where_clause(for_range = false)
131
131
  builder = SQLBuilder::ClauseBuilder.new(nil)
132
- builder.add_clause inheritance_column_condition unless model.descends_from_active_record?
133
132
  builder.add_clause delta_processor.clause(source.delta?) if delta_processor
134
133
  builder.add_clause range_condition unless for_range
135
134
  builder.separated(' AND ')
@@ -3,7 +3,7 @@ require 'pathname'
3
3
  class ThinkingSphinx::Configuration < Riddle::Configuration
4
4
  attr_accessor :configuration_file, :indices_location, :version
5
5
  attr_reader :index_paths
6
- attr_writer :controller
6
+ attr_writer :controller, :index_set_class
7
7
 
8
8
  delegate :environment, :to => :framework
9
9
 
@@ -56,9 +56,12 @@ class ThinkingSphinx::Configuration < Riddle::Configuration
56
56
  end
57
57
  end
58
58
 
59
+ def index_set_class
60
+ @index_set_class ||= ThinkingSphinx::IndexSet
61
+ end
62
+
59
63
  def indices_for_references(*references)
60
- preload_indices
61
- indices.select { |index| references.include?(index.reference) }
64
+ index_set_class.new(:references => references).to_a
62
65
  end
63
66
 
64
67
  def next_offset(reference)
@@ -12,7 +12,7 @@ module ThinkingSphinx::Core::Index
12
12
  @docinfo = :extern
13
13
  @charset_type = 'utf-8'
14
14
  @options = options
15
- @offset = config.next_offset(reference)
15
+ @offset = config.next_offset(options[:offset_as] || reference)
16
16
  @type = 'plain'
17
17
 
18
18
  super "#{options[:name] || reference.to_s.gsub('/', '_')}_#{name_suffix}"
@@ -15,7 +15,10 @@ class ThinkingSphinx::Excerpter
15
15
 
16
16
  def excerpt!(text)
17
17
  result = ThinkingSphinx::Connection.take do |connection|
18
- connection.execute(statement_for(text)).first['snippet']
18
+ query = statement_for text
19
+ ThinkingSphinx::Logger.log :query, query do
20
+ connection.execute(query).first['snippet']
21
+ end
19
22
  end
20
23
 
21
24
  encoded? ? result : ThinkingSphinx::UTF8.encode(result)
@@ -63,6 +63,10 @@ class ThinkingSphinx::FacetSearch
63
63
 
64
64
  private
65
65
 
66
+ def configuration
67
+ ThinkingSphinx::Configuration.instance
68
+ end
69
+
66
70
  def facets
67
71
  @facets ||= properties.group_by(&:name).collect { |name, matches|
68
72
  ThinkingSphinx::Facet.new name, matches
@@ -92,11 +96,13 @@ class ThinkingSphinx::FacetSearch
92
96
  end
93
97
 
94
98
  def indices
95
- @indices ||= ThinkingSphinx::IndexSet.new options[:classes], options[:indices]
99
+ @indices ||= configuration.index_set_class.new(
100
+ options.slice(:classes, :indices)
101
+ )
96
102
  end
97
103
 
98
104
  def max_matches
99
- ThinkingSphinx::Configuration.instance.settings['max_matches'] || 1000
105
+ configuration.settings['max_matches'] || 1000
100
106
  end
101
107
 
102
108
  def limit
@@ -3,9 +3,9 @@ class ThinkingSphinx::IndexSet
3
3
 
4
4
  delegate :each, :empty?, :to => :indices
5
5
 
6
- def initialize(classes, index_names, configuration = nil)
7
- @classes = classes || []
8
- @index_names = index_names
6
+ def initialize(options = {}, configuration = nil)
7
+ @options = options
8
+ @index_names = options[:indices] || []
9
9
  @configuration = configuration || ThinkingSphinx::Configuration.instance
10
10
  end
11
11
 
@@ -19,7 +19,20 @@ class ThinkingSphinx::IndexSet
19
19
 
20
20
  private
21
21
 
22
- attr_reader :classes, :configuration, :index_names
22
+ attr_reader :configuration, :options
23
+
24
+ def all_indices
25
+ configuration.preload_indices
26
+ configuration.indices
27
+ end
28
+
29
+ def classes
30
+ options[:classes] || []
31
+ end
32
+
33
+ def classes_specified?
34
+ classes.any? || references_specified?
35
+ end
23
36
 
24
37
  def classes_and_ancestors
25
38
  @classes_and_ancestors ||= classes.collect { |model|
@@ -31,21 +44,30 @@ class ThinkingSphinx::IndexSet
31
44
  }.flatten
32
45
  end
33
46
 
34
- def indices
35
- configuration.preload_indices
47
+ def index_names
48
+ options[:indices] || []
49
+ end
36
50
 
37
- return configuration.indices.select { |index|
51
+ def indices
52
+ return all_indices.select { |index|
38
53
  index_names.include?(index.name)
39
- } if index_names && index_names.any?
54
+ } if index_names.any?
40
55
 
41
- everything = classes.empty? ? configuration.indices :
42
- configuration.indices_for_references(*references)
56
+ everything = classes_specified? ? indices_for_references : all_indices
43
57
  everything.reject &:distributed?
44
58
  end
45
59
 
60
+ def indices_for_references
61
+ all_indices.select { |index| references.include? index.reference }
62
+ end
63
+
46
64
  def references
47
- classes_and_ancestors.collect { |klass|
65
+ options[:references] || classes_and_ancestors.collect { |klass|
48
66
  klass.name.underscore.to_sym
49
67
  }
50
68
  end
69
+
70
+ def references_specified?
71
+ options[:references] && options[:references].any?
72
+ end
51
73
  end