searchtastic 0.5.0 → 0.6.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/Readme.md CHANGED
@@ -21,7 +21,7 @@ Make a model searchable by providing an array of attributes to search on:
21
21
  class User < ActiveRecord::Base
22
22
  attr_accessible :name, :bio
23
23
  has_one :club, class_name: Organization
24
- searchable_by %w(name bio club.name)
24
+ searchable_by :name, :bio, :'club.name'
25
25
  end
26
26
  ```
27
27
 
@@ -39,11 +39,25 @@ end
39
39
  ...
40
40
  ```
41
41
 
42
+ Note that this works for any ActiveRelation so this will work as well:
43
+
44
+ ```ruby
45
+ ...
46
+ def index
47
+ #@filter == "pete"
48
+ @users = @foo.users.search(@filter)
49
+ ...
50
+ end
51
+ ...
52
+ ```
53
+
42
54
  # To Do
43
55
 
44
- * Search on Dates -- there's no good way to do this right now
45
56
  * Search on combined fields, e.g. first_name + last_name
46
57
  * Search on all accessible attributes shorthand
58
+ * Search on Date Ranges
59
+ * Search against has_many :through associations
60
+ * Search against HABTM associations
47
61
 
48
62
  # Contributors
49
63
 
data/lib/searchtastic.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'searchtastic/version'
2
+ require 'chronic'
2
3
 
3
4
  # This module is automatically included in all ActiveRecords
4
5
  module Searchtastic
@@ -24,8 +25,10 @@ module Searchtastic
24
25
  # @filtered_models = User.search(filter)
25
26
  #
26
27
  def search filter
27
- if filter && self.fields.count
28
- handle_joins(self.fields).where(build_filter(filter, self.fields))
28
+ if filter && is_searchable?
29
+ filter = [filter, Chronic.parse(filter).strftime("%Y-%m-%d")] rescue filter
30
+ handle_joins(self.fields, scoped.select("DISTINCT(`#{self.table_name}`.`id`), `#{self.table_name}`.*"))
31
+ .where(build_filter(filter, self.fields))
29
32
  else
30
33
  scoped
31
34
  end
@@ -36,7 +39,7 @@ module Searchtastic
36
39
  # User.search(@filter) if User.is_searchable?
37
40
  #
38
41
  def is_searchable?
39
- self.fields.count < 0
42
+ self.fields.count > 0
40
43
  end
41
44
 
42
45
  private
@@ -59,41 +62,64 @@ module Searchtastic
59
62
  # Before adding the where clauses, we have to make sure the right tables are joined
60
63
  # into the query. We use .includes() instead of .joins() so we can get an OUTER JOIN
61
64
  #
62
- def handle_joins fields
63
- ret = scoped
64
- fields.each do |field|
65
- assoc, table = field_table(field)
66
- ret = ret.includes(assoc) unless table == self.table_name.to_sym
65
+ def handle_joins fields, select = nil
66
+ ret = select || scoped
67
+ fields.each do |qualified_field|
68
+ assoc, foreign_table, field = parse_field(qualified_field)
69
+ ret = ret.joins(join_string(assoc, foreign_table)) if assoc
67
70
  end
68
71
  ret
69
72
  end
70
73
 
74
+ def join_string(assoc, foreign_table)
75
+ reflection = self.reflect_on_association(assoc.to_sym)
76
+ case reflection.macro
77
+ when :belongs_to
78
+ "LEFT OUTER JOIN `#{foreign_table}` on `#{foreign_table}`.`id` = `#{self.table_name}`.`#{assoc.singularize}_id`"
79
+ when :has_one
80
+ if reflection.options.has_key?(:through)
81
+ "LEFT OUTER JOIN `#{reflection.options[:through].pluralize}` on `#{reflection.options[:through].pluralize}`.`id` = `#{self.table_name}`.`#{reflection.options[:through]}_id` "+
82
+ "LEFT OUTER JOIN `#{foreign_table}` on `#{foreign_table}`.`id` = `#{reflection.options[:through].pluralize}`.`#{assoc.singularize}_id`"
83
+ else
84
+ "LEFT OUTER JOIN `#{foreign_table}` on `#{foreign_table}`.`id` = `#{self.table_name}`.`#{assoc.singularize}_id`"
85
+ end
86
+ when :has_many
87
+ "LEFT OUTER JOIN `#{foreign_table}` on `#{self.table_name}`.`id` = `#{foreign_table}`.`#{self.table_name.singularize}_id`"
88
+ #when :has_and_belongs_to_many
89
+ else
90
+ raise "Searching against HABTM associations is not supported"
91
+ end
92
+ end
93
+
71
94
  # Get table name from association name
72
95
  #
73
96
  # class User < ActiveRecord::Base
74
97
  # belongs_to :company, class_name: Organization
75
- # searchable_by %w(name, email, company.name)
98
+ # searchable_by :name, :email, :'company.name'
76
99
  # end
77
100
  #
78
- # field_table('company.name') => [:companies, :organizations]
101
+ # parse_field('company.name') => ['companies', 'organizations', 'name']
79
102
  #
80
- def field_table(field)
81
- if field.include? '.'
82
- assoc = field.split('.').first
103
+ def parse_field(qualified_field)
104
+ if qualified_field.include? '.'
105
+ assoc, field = qualified_field.split('.')
83
106
  if assoc != self.table_name
84
- return assoc.to_sym, self.reflect_on_association(assoc.to_sym).table_name.to_sym
107
+ return assoc, self.reflect_on_association(assoc.to_sym).table_name, field
85
108
  end
86
109
  end
87
110
 
88
- return nil, self.table_name
111
+ return nil, self.table_name, field
89
112
 
90
113
  end
91
114
 
92
115
  # Build the filter for the .where() clause in the search method
93
116
  #
94
- def build_filter filter, fields
95
- where = [associations_to_tables(fields).map { |f| "#{f} like ?" }.join(" || ")]
96
- fields.count.times { |n| where << "%#{filter}%" }
117
+ def build_filter filters, fields
118
+ filters = [filters] unless filters.is_a? Array
119
+ where = [Array.new(filters.count, associations_to_tables(fields).map { |f| "#{f} LIKE ?" }.join(" || ")).join(" || ")]
120
+ filters.each do |filter|
121
+ fields.count.times { |n| where << "%#{filter}%" }
122
+ end
97
123
  where
98
124
  end
99
125
 
@@ -102,7 +128,7 @@ module Searchtastic
102
128
  def associations_to_tables fields
103
129
  fields.map do |field|
104
130
  _, column = field.split('.')
105
- "#{field_table(field)[1]}.#{column}"
131
+ "#{parse_field(field)[1]}.#{column}"
106
132
  end
107
133
  end
108
134
 
@@ -1,3 +1,3 @@
1
1
  module Searchtastic
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
data/searchtastic.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.require_paths = ["config", "lib"]
18
18
 
19
19
  #s.add_dependency 'activesupport', '~> 3.2'
20
- #s.add_dependency 'actionpack', '~> 3.2'
20
+ s.add_dependency 'chronic', '~> 0.9'
21
21
 
22
22
  s.add_development_dependency 'rake', '~> 0.9.2'
23
23
  s.add_development_dependency 'rspec', '~> 2.10'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchtastic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-17 00:00:00.000000000 Z
12
+ date: 2013-02-20 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: chronic
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.9'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.9'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: rake
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -112,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
128
  version: '0'
113
129
  requirements: []
114
130
  rubyforge_project: searchtastic
115
- rubygems_version: 1.8.24
131
+ rubygems_version: 1.8.25
116
132
  signing_key:
117
133
  specification_version: 3
118
134
  summary: Enables ActiveRecord Model Searching