meta_search_hub 1.1.3
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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitmodules +6 -0
- data/CHANGELOG +109 -0
- data/Gemfile +12 -0
- data/LICENSE +20 -0
- data/README.rdoc +438 -0
- data/Rakefile +69 -0
- data/VERSION +1 -0
- data/lib/meta_search/builder.rb +316 -0
- data/lib/meta_search/exceptions.rb +17 -0
- data/lib/meta_search/helpers/form_builder.rb +166 -0
- data/lib/meta_search/helpers/form_helper.rb +24 -0
- data/lib/meta_search/helpers/url_helper.rb +66 -0
- data/lib/meta_search/helpers.rb +3 -0
- data/lib/meta_search/locale/en.yml +24 -0
- data/lib/meta_search/method.rb +129 -0
- data/lib/meta_search/model_compatibility.rb +75 -0
- data/lib/meta_search/searches/active_record.rb +203 -0
- data/lib/meta_search/utility.rb +110 -0
- data/lib/meta_search/where.rb +264 -0
- data/lib/meta_search.rb +58 -0
- data/meta_search.gemspec +90 -0
- data/test/fixtures/companies.yml +17 -0
- data/test/fixtures/company.rb +22 -0
- data/test/fixtures/data_type.rb +5 -0
- data/test/fixtures/data_types.yml +15 -0
- data/test/fixtures/developer.rb +11 -0
- data/test/fixtures/developers.yml +62 -0
- data/test/fixtures/developers_projects.yml +25 -0
- data/test/fixtures/note.rb +3 -0
- data/test/fixtures/notes.yml +79 -0
- data/test/fixtures/project.rb +4 -0
- data/test/fixtures/projects.yml +34 -0
- data/test/fixtures/schema.rb +47 -0
- data/test/helper.rb +44 -0
- data/test/locales/es.yml +5 -0
- data/test/locales/flanders.yml +18 -0
- data/test/test_search.rb +969 -0
- data/test/test_view_helpers.rb +447 -0
- metadata +160 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
require 'meta_search/exceptions'
|
|
2
|
+
|
|
3
|
+
module MetaSearch
|
|
4
|
+
# Wheres are how MetaSearch does its magic. Wheres have a name (and possible aliases) which are
|
|
5
|
+
# appended to your model and association attributes. When you instantiate a MetaSearch::Builder
|
|
6
|
+
# against a model (manually or by calling your model's +search+ method) the builder responds to
|
|
7
|
+
# methods named for your model's attributes and associations, suffixed by the name of the Where.
|
|
8
|
+
#
|
|
9
|
+
# These are the default Wheres, broken down by the types of ActiveRecord columns they can search
|
|
10
|
+
# against:
|
|
11
|
+
#
|
|
12
|
+
# === All data types
|
|
13
|
+
#
|
|
14
|
+
# * _equals_ (alias: _eq_) - Just as it sounds.
|
|
15
|
+
# * _does_not_equal_ (aliases: _ne_, _noteq_) - The opposite of equals, oddly enough.
|
|
16
|
+
# * _in_ - Takes an array, matches on equality with any of the items in the array.
|
|
17
|
+
# * _not_in_ (aliases: _ni_, _notin_) - Like above, but negated.
|
|
18
|
+
# * _is_null_ - The column has an SQL NULL value.
|
|
19
|
+
# * _is_not_null_ - The column contains anything but NULL.
|
|
20
|
+
#
|
|
21
|
+
# === Strings
|
|
22
|
+
#
|
|
23
|
+
# * _contains_ (aliases: _like_, _matches_) - Substring match.
|
|
24
|
+
# * _does_not_contain_ (aliases: _nlike_, _nmatches_) - Negative substring match.
|
|
25
|
+
# * _starts_with_ (alias: _sw_) - Match strings beginning with the entered term.
|
|
26
|
+
# * _does_not_start_with_ (alias: _dnsw_) - The opposite of above.
|
|
27
|
+
# * _ends_with_ (alias: _ew_) - Match strings ending with the entered term.
|
|
28
|
+
# * _does_not_end_with_ (alias: _dnew_) - Negative of above.
|
|
29
|
+
#
|
|
30
|
+
# === Numbers, dates, and times
|
|
31
|
+
#
|
|
32
|
+
# * _greater_than_ (alias: _gt_) - Greater than.
|
|
33
|
+
# * _greater_than_or_equal_to_ (aliases: _gte_, _gteq_) - Greater than or equal to.
|
|
34
|
+
# * _less_than_ (alias: _lt_) - Less than.
|
|
35
|
+
# * _less_than_or_equal_to_ (aliases: _lte_, _lteq_) - Less than or equal to.
|
|
36
|
+
#
|
|
37
|
+
# === Booleans
|
|
38
|
+
#
|
|
39
|
+
# * _is_true_ - Is true. Useful for a checkbox like "only show admin users".
|
|
40
|
+
# * _is_false_ - The complement of _is_true_.
|
|
41
|
+
#
|
|
42
|
+
# === Non-boolean data types
|
|
43
|
+
#
|
|
44
|
+
# * _is_present_ - As with _is_true_, useful with a checkbox. Not NULL or the empty string.
|
|
45
|
+
# * _is_blank_ - Returns records with a value of NULL or the empty string in the column.
|
|
46
|
+
#
|
|
47
|
+
# So, given a model like this...
|
|
48
|
+
#
|
|
49
|
+
# class Article < ActiveRecord::Base
|
|
50
|
+
# belongs_to :author
|
|
51
|
+
# has_many :comments
|
|
52
|
+
# has_many :moderations, :through => :comments
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# ...you might end up with attributes like <tt>title_contains</tt>,
|
|
56
|
+
# <tt>comments_title_starts_with</tt>, <tt>moderations_value_less_than</tt>,
|
|
57
|
+
# <tt>author_name_equals</tt>, and so on.
|
|
58
|
+
#
|
|
59
|
+
# Additionally, all of the above predicate types also have an _any and _all version, which
|
|
60
|
+
# expects an array of the corresponding parameter type, and requires any or all of the
|
|
61
|
+
# parameters to be a match, respectively. So:
|
|
62
|
+
#
|
|
63
|
+
# Article.search :author_name_starts_with_any => ['Jim', 'Bob', 'Fred']
|
|
64
|
+
#
|
|
65
|
+
# will match articles authored by Jimmy, Bobby, or Freddy, but not Winifred.
|
|
66
|
+
class Where
|
|
67
|
+
attr_reader :name, :aliases, :types, :cast, :predicate, :formatter, :validator
|
|
68
|
+
def initialize(where)
|
|
69
|
+
if [String,Symbol].include?(where.class)
|
|
70
|
+
where = Where.get(where) or raise ArgumentError("A where could not be instantiated for the argument #{where}")
|
|
71
|
+
end
|
|
72
|
+
@name = where[:name]
|
|
73
|
+
@aliases = where[:aliases]
|
|
74
|
+
@types = where[:types]
|
|
75
|
+
@cast = where[:cast]
|
|
76
|
+
@predicate = where[:predicate]
|
|
77
|
+
@validator = where[:validator]
|
|
78
|
+
@formatter = where[:formatter]
|
|
79
|
+
@splat_param = where[:splat_param]
|
|
80
|
+
@skip_compounds = where[:skip_compounds]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def splat_param?
|
|
84
|
+
!!@splat_param
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def skip_compounds?
|
|
88
|
+
!!@skip_compounds
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Format a parameter for searching using the Where's defined formatter.
|
|
92
|
+
def format_param(param)
|
|
93
|
+
formatter.call(param)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Validate the parameter for use in a search using the Where's defined validator.
|
|
97
|
+
def validate(param)
|
|
98
|
+
validator.call(param)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Evaluate the Where for the given relation, attribute, and parameter(s)
|
|
102
|
+
def evaluate(relation, attributes, param)
|
|
103
|
+
if splat_param?
|
|
104
|
+
conditions = attributes.map {|a| a.send(predicate, *format_param(param))}
|
|
105
|
+
else
|
|
106
|
+
conditions = attributes.map {|a| a.send(predicate, format_param(param))}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
relation.where(conditions.inject(nil) {|memo, c| memo ? memo.or(c) : c})
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class << self
|
|
113
|
+
# At application initialization, you can add additional custom Wheres to the mix.
|
|
114
|
+
# in your application's <tt>config/initializers/meta_search.rb</tt>, place lines
|
|
115
|
+
# like this:
|
|
116
|
+
#
|
|
117
|
+
# MetaSearch::Where.add :between, :btw,
|
|
118
|
+
# :predicate => :in,
|
|
119
|
+
# :types => [:integer, :float, :decimal, :date, :datetime, :timestamp, :time],
|
|
120
|
+
# :formatter => Proc.new {|param| Range.new(param.first, param.last)},
|
|
121
|
+
# :validator => Proc.new {|param|
|
|
122
|
+
# param.is_a?(Array) && !(param[0].blank? || param[1].blank?)
|
|
123
|
+
# }
|
|
124
|
+
#
|
|
125
|
+
# The first options are all names for the where. Well, the first is a name, the rest
|
|
126
|
+
# are aliases, really. They will determine the suffix you will use to access your Where.
|
|
127
|
+
#
|
|
128
|
+
# <tt>types</tt> is an array of types the comparison is valid for. The where will not
|
|
129
|
+
# be available against columns that are not one of these types. Default is +ALL_TYPES+,
|
|
130
|
+
# Which is one of several MetaSearch constants available for type assignment (the others
|
|
131
|
+
# being +DATES+, +TIIMES+, +STRINGS+, and +NUMBERS+).
|
|
132
|
+
#
|
|
133
|
+
# <tt>predicate</tt> is the Arel::Attribute predication (read: conditional operator) used
|
|
134
|
+
# for the comparison. Default is :eq, or equality.
|
|
135
|
+
#
|
|
136
|
+
# <tt>formatter</tt> is the Proc that will do any formatting to the variables to be substituted.
|
|
137
|
+
# The default proc is <tt>{|param| param}</tt>, which doesn't really do anything. If you pass a
|
|
138
|
+
# string, it will be +eval+ed in the context of this Proc.
|
|
139
|
+
#
|
|
140
|
+
# For example, this is the definition of the "contains" Where:
|
|
141
|
+
#
|
|
142
|
+
# ['contains', 'like', {:types => STRINGS, :predicate => :matches, :formatter => '"%#{param}%"'}]
|
|
143
|
+
#
|
|
144
|
+
# Be sure to single-quote the string, so that variables aren't interpolated until later. If in doubt,
|
|
145
|
+
# just use a Proc.
|
|
146
|
+
#
|
|
147
|
+
# <tt>validator</tt> is the Proc that will be used to check whether a parameter supplied to the
|
|
148
|
+
# Where is valid. If it is not valid, it won't be used in the query. The default is
|
|
149
|
+
# <tt>{|param| !param.blank?}</tt>, so that empty parameters aren't added to the search, but you
|
|
150
|
+
# can get more complex if you desire, like the one in the between example, above.
|
|
151
|
+
#
|
|
152
|
+
# <tt>splat_param</tt>, if true, will cause the parameters sent to the predicate in question
|
|
153
|
+
# to be splatted (converted to an argument list). This is not normally useful and defaults to
|
|
154
|
+
# false, but is used when automatically creating compound Wheres (*_any, *_all) so that the
|
|
155
|
+
# Arel attribute method gets the correct parameter list.
|
|
156
|
+
#
|
|
157
|
+
# <tt>skip_compounds</tt> will prevent creation of compound condition methods (ending in
|
|
158
|
+
# _any_ or _all_) for situations where they wouldn't make sense, such as the built-in
|
|
159
|
+
# conditions <tt>is_true</tt> and <tt>is_false</tt>.
|
|
160
|
+
#
|
|
161
|
+
# <tt>cast</tt> will override the normal cast of the parameter when using this Where
|
|
162
|
+
# condition. Normally, the value supplied to a condition is cast to the type of the column
|
|
163
|
+
# it's being compared against. In cases where this isn't desirable, because the value you
|
|
164
|
+
# intend to accept isn't the same kind of data you'll be comparing against, you can override
|
|
165
|
+
# that cast here, using one of the standard DB type symbols such as :integer, :string, :boolean
|
|
166
|
+
# and so on.
|
|
167
|
+
def add(*args)
|
|
168
|
+
where = create_where_from_args(*args)
|
|
169
|
+
create_where_compounds_for(where) unless where.skip_compounds?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns the complete array of Wheres
|
|
173
|
+
def all
|
|
174
|
+
@@wheres
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Get the where matching a method or predicate.
|
|
178
|
+
def get(method_id_or_predicate)
|
|
179
|
+
return nil unless where_key = @@wheres.keys.
|
|
180
|
+
sort {|a,b| b.length <=> a.length}.
|
|
181
|
+
detect {|n| method_id_or_predicate.to_s.match(/#{n}=?$/)}
|
|
182
|
+
where = @@wheres[where_key]
|
|
183
|
+
where = @@wheres[where] if where.is_a?(String)
|
|
184
|
+
where
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Set the wheres to their default values, removing any customized settings.
|
|
188
|
+
def initialize_wheres
|
|
189
|
+
@@wheres = {}
|
|
190
|
+
DEFAULT_WHERES.each do |where|
|
|
191
|
+
add(*where)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
# "Creates" the Where by adding it (and its aliases) to the current hash of wheres. It then
|
|
198
|
+
# instantiates a Where and returns it for use.
|
|
199
|
+
def create_where_from_args(*args)
|
|
200
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
|
201
|
+
args = args.compact.flatten.map {|a| a.to_s }
|
|
202
|
+
raise ArgumentError, "Name parameter required" if args.blank?
|
|
203
|
+
opts[:name] ||= args.first
|
|
204
|
+
opts[:types] ||= ALL_TYPES
|
|
205
|
+
opts[:types] = [opts[:types]].flatten
|
|
206
|
+
opts[:cast] = opts[:cast]
|
|
207
|
+
opts[:predicate] ||= :eq
|
|
208
|
+
opts[:splat_param] ||= false
|
|
209
|
+
opts[:skip_compounds] ||= false
|
|
210
|
+
opts[:formatter] ||= Proc.new {|param| param}
|
|
211
|
+
if opts[:formatter].is_a?(String)
|
|
212
|
+
formatter = opts[:formatter]
|
|
213
|
+
opts[:formatter] = Proc.new {|param| eval formatter}
|
|
214
|
+
end
|
|
215
|
+
unless opts[:formatter].respond_to?(:call)
|
|
216
|
+
raise ArgumentError, "Invalid formatter for #{opts[:name]}, should be a Proc or String."
|
|
217
|
+
end
|
|
218
|
+
opts[:validator] ||= Proc.new {|param| !param.blank?}
|
|
219
|
+
unless opts[:validator].respond_to?(:call)
|
|
220
|
+
raise ArgumentError, "Invalid validator for #{opts[:name]}, should be a Proc."
|
|
221
|
+
end
|
|
222
|
+
opts[:aliases] ||= [args - [opts[:name]]].flatten
|
|
223
|
+
@@wheres ||= {}
|
|
224
|
+
if @@wheres.has_key?(opts[:name])
|
|
225
|
+
raise ArgumentError, "\"#{opts[:name]}\" is not available for use as a where name."
|
|
226
|
+
end
|
|
227
|
+
@@wheres[opts[:name]] = opts
|
|
228
|
+
opts[:aliases].each do |a|
|
|
229
|
+
if @@wheres.has_key?(a)
|
|
230
|
+
opts[:aliases].delete(a)
|
|
231
|
+
else
|
|
232
|
+
@@wheres[a] = opts[:name]
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
new(opts[:name])
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Takes the provided +where+ param and derives two additional Wheres from it, with the
|
|
239
|
+
# name appended by _any/_all. These will use Arel's grouped predicate methods (matching
|
|
240
|
+
# the same naming convention) to be invoked instead, with a list of possible/required
|
|
241
|
+
# matches.
|
|
242
|
+
def create_where_compounds_for(where)
|
|
243
|
+
['any', 'all'].each do |compound|
|
|
244
|
+
args = [where.name, *where.aliases].map {|n| "#{n}_#{compound}"}
|
|
245
|
+
create_where_from_args(*args + [{
|
|
246
|
+
:types => where.types,
|
|
247
|
+
:predicate => "#{where.predicate}_#{compound}".to_sym,
|
|
248
|
+
# Only use valid elements in the array
|
|
249
|
+
:formatter => Proc.new {|param|
|
|
250
|
+
param.select {|p| where.validator.call(p)}.map {|p| where.formatter.call(p)}
|
|
251
|
+
},
|
|
252
|
+
# Compound where is valid if it has at least one element which is valid
|
|
253
|
+
:validator => Proc.new {|param|
|
|
254
|
+
param.is_a?(Array) &&
|
|
255
|
+
!param.select {|p| where.validator.call(p)}.blank?}
|
|
256
|
+
}]
|
|
257
|
+
)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
Where.initialize_wheres
|
|
264
|
+
end
|
data/lib/meta_search.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module MetaSearch
|
|
2
|
+
NUMBERS = [:integer, :float, :decimal]
|
|
3
|
+
STRINGS = [:string, :text, :binary]
|
|
4
|
+
DATES = [:date]
|
|
5
|
+
TIMES = [:datetime, :timestamp, :time]
|
|
6
|
+
BOOLEANS = [:boolean]
|
|
7
|
+
ALL_TYPES = NUMBERS + STRINGS + DATES + TIMES + BOOLEANS
|
|
8
|
+
|
|
9
|
+
# Change this only if you know what you're doing. It's here for your protection.
|
|
10
|
+
MAX_JOIN_DEPTH = 5
|
|
11
|
+
|
|
12
|
+
DEFAULT_WHERES = [
|
|
13
|
+
['equals', 'eq', {:validator => Proc.new {|param| !param.blank? || (param == false)}}],
|
|
14
|
+
['does_not_equal', 'ne', 'not_eq', {:types => ALL_TYPES, :predicate => :not_eq}],
|
|
15
|
+
['contains', 'like', 'matches', {:types => STRINGS, :predicate => :matches, :formatter => '"%#{param}%"'}],
|
|
16
|
+
['does_not_contain', 'nlike', 'not_matches', {:types => STRINGS, :predicate => :does_not_match, :formatter => '"%#{param}%"'}],
|
|
17
|
+
['starts_with', 'sw', {:types => STRINGS, :predicate => :matches, :formatter => '"#{param}%"'}],
|
|
18
|
+
['does_not_start_with', 'dnsw', {:types => STRINGS, :predicate => :does_not_match, :formatter => '"#{param}%"'}],
|
|
19
|
+
['ends_with', 'ew', {:types => STRINGS, :predicate => :matches, :formatter => '"%#{param}"'}],
|
|
20
|
+
['does_not_end_with', 'dnew', {:types => STRINGS, :predicate => :does_not_match, :formatter => '"%#{param}"'}],
|
|
21
|
+
['greater_than', 'gt', {:types => (NUMBERS + DATES + TIMES), :predicate => :gt}],
|
|
22
|
+
['less_than', 'lt', {:types => (NUMBERS + DATES + TIMES), :predicate => :lt}],
|
|
23
|
+
['greater_than_or_equal_to', 'gte', 'gteq', {:types => (NUMBERS + DATES + TIMES), :predicate => :gteq}],
|
|
24
|
+
['less_than_or_equal_to', 'lte', 'lteq', {:types => (NUMBERS + DATES + TIMES), :predicate => :lteq}],
|
|
25
|
+
['in', {:types => ALL_TYPES, :predicate => :in}],
|
|
26
|
+
['not_in', 'ni', 'not_in', {:types => ALL_TYPES, :predicate => :not_in}],
|
|
27
|
+
['is_true', {:types => BOOLEANS, :skip_compounds => true}],
|
|
28
|
+
['is_false', {:types => BOOLEANS, :skip_compounds => true, :formatter => Proc.new {|param| !param}}],
|
|
29
|
+
['is_present', {:types => (ALL_TYPES - BOOLEANS), :predicate => :not_eq_all, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| [nil, '']}}],
|
|
30
|
+
['is_blank', {:types => (ALL_TYPES - BOOLEANS), :predicate => :eq_any, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| [nil, '']}}],
|
|
31
|
+
['is_null', {:types => ALL_TYPES, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| nil}}],
|
|
32
|
+
['is_not_null', {:types => ALL_TYPES, :predicate => :not_eq, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| nil}}]
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
RELATION_METHODS = [
|
|
36
|
+
# Query construction
|
|
37
|
+
:joins, :includes, :select, :order, :where, :having, :group,
|
|
38
|
+
# Results, debug, array methods
|
|
39
|
+
:to_a, :all, :length, :size, :to_sql, :debug_sql, :paginate, :page,
|
|
40
|
+
:find_each, :first, :last, :each, :arel, :in_groups_of, :group_by,
|
|
41
|
+
# Calculations
|
|
42
|
+
:count, :average, :minimum, :maximum, :sum
|
|
43
|
+
]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
require 'active_record'
|
|
47
|
+
require 'active_support'
|
|
48
|
+
require 'action_view'
|
|
49
|
+
require 'action_controller'
|
|
50
|
+
require 'meta_search/searches/active_record'
|
|
51
|
+
require 'meta_search/helpers'
|
|
52
|
+
|
|
53
|
+
I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'meta_search', 'locale', '*.yml')]
|
|
54
|
+
|
|
55
|
+
ActiveRecord::Base.send(:include, MetaSearch::Searches::ActiveRecord)
|
|
56
|
+
ActionView::Helpers::FormBuilder.send(:include, MetaSearch::Helpers::FormBuilder)
|
|
57
|
+
ActionController::Base.helper(MetaSearch::Helpers::UrlHelper)
|
|
58
|
+
ActionController::Base.helper(MetaSearch::Helpers::FormHelper)
|
data/meta_search.gemspec
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Generated by jeweler
|
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = 'meta_search_hub'
|
|
8
|
+
s.version = "1.1.3"
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.authors = ["Ernie Miller"]
|
|
12
|
+
s.date = "2012-02-02"
|
|
13
|
+
s.description = "\n Allows simple search forms to be created against an AR3 model\n and its associations, has useful view helpers for sort links\n and multiparameter fields as well.\n "
|
|
14
|
+
s.email = "ernie@metautonomo.us"
|
|
15
|
+
s.extra_rdoc_files = [
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.rdoc"
|
|
18
|
+
]
|
|
19
|
+
s.files = [
|
|
20
|
+
".document",
|
|
21
|
+
".gitmodules",
|
|
22
|
+
"CHANGELOG",
|
|
23
|
+
"Gemfile",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"README.rdoc",
|
|
26
|
+
"Rakefile",
|
|
27
|
+
"VERSION",
|
|
28
|
+
"lib/meta_search.rb",
|
|
29
|
+
"lib/meta_search/builder.rb",
|
|
30
|
+
"lib/meta_search/exceptions.rb",
|
|
31
|
+
"lib/meta_search/helpers.rb",
|
|
32
|
+
"lib/meta_search/helpers/form_builder.rb",
|
|
33
|
+
"lib/meta_search/helpers/form_helper.rb",
|
|
34
|
+
"lib/meta_search/helpers/url_helper.rb",
|
|
35
|
+
"lib/meta_search/locale/en.yml",
|
|
36
|
+
"lib/meta_search/method.rb",
|
|
37
|
+
"lib/meta_search/model_compatibility.rb",
|
|
38
|
+
"lib/meta_search/searches/active_record.rb",
|
|
39
|
+
"lib/meta_search/utility.rb",
|
|
40
|
+
"lib/meta_search/where.rb",
|
|
41
|
+
"meta_search.gemspec",
|
|
42
|
+
"test/fixtures/companies.yml",
|
|
43
|
+
"test/fixtures/company.rb",
|
|
44
|
+
"test/fixtures/data_type.rb",
|
|
45
|
+
"test/fixtures/data_types.yml",
|
|
46
|
+
"test/fixtures/developer.rb",
|
|
47
|
+
"test/fixtures/developers.yml",
|
|
48
|
+
"test/fixtures/developers_projects.yml",
|
|
49
|
+
"test/fixtures/note.rb",
|
|
50
|
+
"test/fixtures/notes.yml",
|
|
51
|
+
"test/fixtures/project.rb",
|
|
52
|
+
"test/fixtures/projects.yml",
|
|
53
|
+
"test/fixtures/schema.rb",
|
|
54
|
+
"test/helper.rb",
|
|
55
|
+
"test/locales/es.yml",
|
|
56
|
+
"test/locales/flanders.yml",
|
|
57
|
+
"test/test_search.rb",
|
|
58
|
+
"test/test_view_helpers.rb"
|
|
59
|
+
]
|
|
60
|
+
s.homepage = "http://metautonomo.us/projects/metasearch/"
|
|
61
|
+
s.post_install_message = "\n*** Thanks for installing MetaSearch! ***\nBe sure to check out http://metautonomo.us/projects/metasearch/ for a\nwalkthrough of MetaSearch's features, and click the donate button if\nyou're feeling especially appreciative. It'd help me justify this\n\"open source\" stuff to my lovely wife. :)\n\n"
|
|
62
|
+
s.require_paths = ["lib"]
|
|
63
|
+
s.rubygems_version = "1.8.15"
|
|
64
|
+
s.summary = "Object-based searching (and more) for simply creating search forms."
|
|
65
|
+
|
|
66
|
+
if s.respond_to? :specification_version then
|
|
67
|
+
s.specification_version = 3
|
|
68
|
+
|
|
69
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
70
|
+
s.add_runtime_dependency(%q<activerecord>, ["~> 3.1"])
|
|
71
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.1"])
|
|
72
|
+
s.add_runtime_dependency(%q<polyamorous>, ["~> 0.5.0"])
|
|
73
|
+
s.add_runtime_dependency(%q<actionpack>, ["~> 3.1"])
|
|
74
|
+
s.add_development_dependency(%q<shoulda>, ["~> 2.11"])
|
|
75
|
+
else
|
|
76
|
+
s.add_dependency(%q<activerecord>, ["~> 3.1"])
|
|
77
|
+
s.add_dependency(%q<activesupport>, ["~> 3.1"])
|
|
78
|
+
s.add_dependency(%q<polyamorous>, ["~> 0.5.0"])
|
|
79
|
+
s.add_dependency(%q<actionpack>, ["~> 3.1"])
|
|
80
|
+
s.add_dependency(%q<shoulda>, ["~> 2.11"])
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
s.add_dependency(%q<activerecord>, ["~> 3.1"])
|
|
84
|
+
s.add_dependency(%q<activesupport>, ["~> 3.1"])
|
|
85
|
+
s.add_dependency(%q<polyamorous>, ["~> 0.5.0"])
|
|
86
|
+
s.add_dependency(%q<actionpack>, ["~> 3.1"])
|
|
87
|
+
s.add_dependency(%q<shoulda>, ["~> 2.11"])
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
initech:
|
|
2
|
+
name : Initech
|
|
3
|
+
id : 1
|
|
4
|
+
created_at: 1999-02-19 08:00
|
|
5
|
+
updated_at: 1999-02-19 08:00
|
|
6
|
+
|
|
7
|
+
aos:
|
|
8
|
+
name: Advanced Optical Solutions
|
|
9
|
+
id : 2
|
|
10
|
+
created_at: 2004-02-01 08:00
|
|
11
|
+
updated_at: 2004-02-01 08:00
|
|
12
|
+
|
|
13
|
+
mission_data:
|
|
14
|
+
name: Mission Data
|
|
15
|
+
id : 3
|
|
16
|
+
created_at: 1996-09-21 08:00
|
|
17
|
+
updated_at: 1996-09-21 08:00
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Company < ActiveRecord::Base
|
|
2
|
+
has_many :developers
|
|
3
|
+
has_many :developer_notes, :through => :developers, :source => :notes
|
|
4
|
+
has_many :slackers, :class_name => "Developer", :conditions => {:slacker => true}
|
|
5
|
+
has_many :notes, :as => :notable
|
|
6
|
+
has_many :data_types
|
|
7
|
+
|
|
8
|
+
scope :backwards_name, lambda {|name| where(:name => name.reverse)}
|
|
9
|
+
scope :with_slackers_by_name_and_salary_range,
|
|
10
|
+
lambda {|name, low, high|
|
|
11
|
+
joins(:slackers).where(:developers => {:name => name, :salary => low..high})
|
|
12
|
+
}
|
|
13
|
+
search_methods :backwards_name, :backwards_name_as_string, :if => proc {|s| s.options[:user] != 'blocked'}
|
|
14
|
+
search_methods :with_slackers_by_name_and_salary_range,
|
|
15
|
+
:splat_param => true, :type => [:string, :integer, :integer]
|
|
16
|
+
attr_unsearchable :updated_at, :if => proc {|s| s.options[:user] == 'blocked' || !s.options[:user]}
|
|
17
|
+
assoc_unsearchable :notes, :if => proc {|s| s.options[:user] == 'blocked' || !s.options[:user]}
|
|
18
|
+
|
|
19
|
+
def self.backwards_name_as_string(name)
|
|
20
|
+
name.reverse
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<% 1.upto(9) do |n| %>
|
|
2
|
+
dt_<%= n %>:
|
|
3
|
+
company_id: <%= n % 3 + 1 %>
|
|
4
|
+
str : This string has <%= n %> exclamation points<%= '!' * n %>
|
|
5
|
+
txt : <%= 'This is some text that may or may not repeat based on the value of n.' * n %>
|
|
6
|
+
int : <%= n ** 3 %>
|
|
7
|
+
flt : <%= n.to_f / 2.0 %>
|
|
8
|
+
dec : <%= n.to_f ** (n + 0.1) %>
|
|
9
|
+
dtm : <%= (Time.local(2009, 12, 24) + 86400 * n).in_time_zone.to_s(:db) %>
|
|
10
|
+
tms : <%= (Time.local(2009, 12, 24) + 86400 * n).in_time_zone.to_s(:db) %>
|
|
11
|
+
tim : <%= Time.local(2000, 01, 01, n+8, n).in_time_zone.to_s(:db) %>
|
|
12
|
+
dat : <%= (Date.new(2009, 12, 24) + n).strftime("%Y-%m-%d") %>
|
|
13
|
+
bin : <%= "BLOB#{n}" * n %>
|
|
14
|
+
bln : <%= n % 2 > 0 ? true : false %>
|
|
15
|
+
<% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Developer < ActiveRecord::Base
|
|
2
|
+
belongs_to :company
|
|
3
|
+
has_and_belongs_to_many :projects
|
|
4
|
+
has_many :notes, :as => :notable
|
|
5
|
+
|
|
6
|
+
attr_searchable :name, :salary, :if => proc {|s| !s.options[:user] || s.options[:user] == 'privileged'}
|
|
7
|
+
assoc_searchable :notes, :projects, :company, :if => proc {|s| !s.options[:user] || s.options[:user] == 'privileged'}
|
|
8
|
+
|
|
9
|
+
scope :sort_by_salary_and_name_asc, order('salary ASC, name ASC')
|
|
10
|
+
scope :sort_by_salary_and_name_desc, order('salary DESC, name DESC')
|
|
11
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
peter:
|
|
2
|
+
id : 1
|
|
3
|
+
company_id: 1
|
|
4
|
+
name : Peter Gibbons
|
|
5
|
+
salary : 100000
|
|
6
|
+
slacker : true
|
|
7
|
+
|
|
8
|
+
michael:
|
|
9
|
+
id : 2
|
|
10
|
+
company_id: 1
|
|
11
|
+
name : Michael Bolton
|
|
12
|
+
salary : 70000
|
|
13
|
+
slacker : false
|
|
14
|
+
|
|
15
|
+
samir:
|
|
16
|
+
id : 3
|
|
17
|
+
company_id: 1
|
|
18
|
+
name : Samir Nagheenanajar
|
|
19
|
+
salary : 65000
|
|
20
|
+
slacker : false
|
|
21
|
+
|
|
22
|
+
herb:
|
|
23
|
+
id : 4
|
|
24
|
+
company_id: 2
|
|
25
|
+
name : Herb Myers
|
|
26
|
+
salary : 50000
|
|
27
|
+
slacker : false
|
|
28
|
+
|
|
29
|
+
dude:
|
|
30
|
+
id : 5
|
|
31
|
+
company_id: 2
|
|
32
|
+
name : Some Dude
|
|
33
|
+
salary : 84000
|
|
34
|
+
slacker : true
|
|
35
|
+
|
|
36
|
+
ernie:
|
|
37
|
+
id : 6
|
|
38
|
+
company_id: 3
|
|
39
|
+
name : Ernie Miller
|
|
40
|
+
salary : 45000
|
|
41
|
+
slacker : true
|
|
42
|
+
|
|
43
|
+
someone:
|
|
44
|
+
id : 7
|
|
45
|
+
company_id: 3
|
|
46
|
+
name : Someone Else
|
|
47
|
+
salary : 70000
|
|
48
|
+
slacker : true
|
|
49
|
+
|
|
50
|
+
another:
|
|
51
|
+
id : 8
|
|
52
|
+
company_id: 3
|
|
53
|
+
name : Another Guy
|
|
54
|
+
salary : 80000
|
|
55
|
+
slacker : false
|
|
56
|
+
|
|
57
|
+
forgetful:
|
|
58
|
+
id : 9
|
|
59
|
+
company_id: 3
|
|
60
|
+
name : Forgetful Notetaker
|
|
61
|
+
salary : 40000
|
|
62
|
+
slacker : false
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<% 1.upto(3) do |d| %>
|
|
2
|
+
y2k_<%= d %>:
|
|
3
|
+
developer_id: <%= d %>
|
|
4
|
+
project_id : 1
|
|
5
|
+
<% end %>
|
|
6
|
+
|
|
7
|
+
virus:
|
|
8
|
+
developer_id: 2
|
|
9
|
+
project_id : 2
|
|
10
|
+
|
|
11
|
+
<% 1.upto(8) do |d| %>
|
|
12
|
+
awesome_<%= d %>:
|
|
13
|
+
developer_id: <%= d %>
|
|
14
|
+
project_id : 3
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
metasearch:
|
|
18
|
+
developer_id: 6
|
|
19
|
+
project_id : 4
|
|
20
|
+
|
|
21
|
+
<% 4.upto(8) do |d| %>
|
|
22
|
+
another_<%= d %>:
|
|
23
|
+
developer_id: <%= d %>
|
|
24
|
+
project_id : 5
|
|
25
|
+
<% end %>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
peter:
|
|
2
|
+
notable_type: Developer
|
|
3
|
+
notable_id : 1
|
|
4
|
+
note : A straight shooter with upper management written all over him.
|
|
5
|
+
|
|
6
|
+
michael:
|
|
7
|
+
notable_type: Developer
|
|
8
|
+
notable_id : 2
|
|
9
|
+
note : Doesn't like the singer of the same name. The nerve!
|
|
10
|
+
|
|
11
|
+
samir:
|
|
12
|
+
notable_type: Developer
|
|
13
|
+
notable_id : 3
|
|
14
|
+
note : Naga.... Naga..... Not gonna work here anymore anyway.
|
|
15
|
+
|
|
16
|
+
herb:
|
|
17
|
+
notable_type: Developer
|
|
18
|
+
notable_id : 4
|
|
19
|
+
note : Will show you what he's doing.
|
|
20
|
+
|
|
21
|
+
dude:
|
|
22
|
+
notable_type: Developer
|
|
23
|
+
notable_id : 5
|
|
24
|
+
note : Nothing of note.
|
|
25
|
+
|
|
26
|
+
ernie:
|
|
27
|
+
notable_type: Developer
|
|
28
|
+
notable_id : 6
|
|
29
|
+
note : Complete slacker. Should probably be fired.
|
|
30
|
+
|
|
31
|
+
someone:
|
|
32
|
+
notable_type: Developer
|
|
33
|
+
notable_id : 7
|
|
34
|
+
note : Just another developer.
|
|
35
|
+
|
|
36
|
+
another:
|
|
37
|
+
notable_type: Developer
|
|
38
|
+
notable_id : 8
|
|
39
|
+
note : Placing a note in this guy's file for insubordination.
|
|
40
|
+
|
|
41
|
+
initech:
|
|
42
|
+
notable_type: Company
|
|
43
|
+
notable_id : 1
|
|
44
|
+
note : Innovation + Technology!
|
|
45
|
+
|
|
46
|
+
aos:
|
|
47
|
+
notable_type: Company
|
|
48
|
+
notable_id : 2
|
|
49
|
+
note : Advanced solutions of an optical nature.
|
|
50
|
+
|
|
51
|
+
mission_data:
|
|
52
|
+
notable_type: Company
|
|
53
|
+
notable_id : 3
|
|
54
|
+
note : Best design + development shop in the 'ville.
|
|
55
|
+
|
|
56
|
+
y2k:
|
|
57
|
+
notable_type: Project
|
|
58
|
+
notable_id : 1
|
|
59
|
+
note : It may have already passed but that's no excuse to be unprepared!
|
|
60
|
+
|
|
61
|
+
virus:
|
|
62
|
+
notable_type: Project
|
|
63
|
+
notable_id : 2
|
|
64
|
+
note : It could bring the company to its knees.
|
|
65
|
+
|
|
66
|
+
awesome:
|
|
67
|
+
notable_type: Project
|
|
68
|
+
notable_id : 3
|
|
69
|
+
note : This note is AWESOME!!!
|
|
70
|
+
|
|
71
|
+
metasearch:
|
|
72
|
+
notable_type: Project
|
|
73
|
+
notable_id : 4
|
|
74
|
+
note : A complete waste of the developer's time.
|
|
75
|
+
|
|
76
|
+
another:
|
|
77
|
+
notable_type: Project
|
|
78
|
+
notable_id : 5
|
|
79
|
+
note : This is another project note.
|