modern_searchlogic 1.0.1 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c1285376c99c453d6e9af0bb21b1458c4f34d5ddd7cdec3ce4e14ceae3d5d6b
4
- data.tar.gz: bce4a074d01473a88767f1ef8b11c77c8a127272a6dbd6907dc0d8977d489796
3
+ metadata.gz: 44a496fa5bb25e276f94a214cc3062ad69bf68d0bf07b0a38b70f154b27b5b2f
4
+ data.tar.gz: d45273f03b6ba86003fc9c5fe3c5d6f40a8c1eb1aaca43793f6c4e846e20b17b
5
5
  SHA512:
6
- metadata.gz: 8dbd45af5b7a11758a6e3323b62697f746e275033cbc492c669967fb9c570a3c2d805682c1635eff6d49d91a9302812dc9e879eb824322a6372164588d8968b9
7
- data.tar.gz: d68579d00786da93e3db22cfafeecc5440455f4e3f4a90e38ed7c5eb712ccac63a1123fabe22281e9bcb61355d11b5fe19903ec1a1bdf986cf8882559c66aebf
6
+ metadata.gz: 6e9ed70ffe2c01fff4b5283377127dd3910d7e6ead66504dd5242bd99179ce1494375644b5ce5251d868d7f2c8731ffd50bd644d445b8a78fb5b2a3d98f6d369
7
+ data.tar.gz: 75fdbeb934dec5345a106f29dcb886f853b142bc61fa32d74bd06a4602d75d88d980dfcc012780bb3a361b24d7d396a606519ce2ce61256d11320056d1382548
data/README.md CHANGED
@@ -17,6 +17,37 @@ The gem *might* work on Ruby 2, but it is only tested against Ruby 3.3.
17
17
  Just add the Gem to your gemspec, and the searchlogic methods will be available.
18
18
  Refer to the searchlogic documentation for more details.
19
19
 
20
+ ## Configuration
21
+
22
+ By default the gem installs itself into every model, by including `ModernSearchlogic::ActiveRecordMethods` into `ActiveRecord::Base` (and `ModernSearchlogic::Ordering` into `ActiveRecord::Relation`) once ActiveRecord loads.
23
+
24
+ To opt-out and wire things up yourself, set the following in `config/application.rb` (it must be set before ActiveRecord loads, so an initializer is too late):
25
+
26
+ ```ruby
27
+ config.modern_searchlogic.auto_include = false
28
+ ```
29
+
30
+ With that off, nothing is included anywhere, and you pick the pieces you want:
31
+
32
+ ```ruby
33
+ class User < ApplicationRecord
34
+ include ModernSearchlogic::ColumnConditions # username_like, age_gt, ascend_by_username, ...
35
+ include ModernSearchlogic::ScopeProcedure # scope_procedure :adults, -> { age_gte(18) }
36
+ include ModernSearchlogic::Searchable # User.search(:username_like => 'bob')
37
+ end
38
+ ```
39
+
40
+ The modules pull in whatever they depend on (`Searchable` brings `ColumnConditions`, which brings `ScopeTracking`, and so on), so including only the one you want is safe.
41
+ HOWEVER, `ModernSearchlogic::Ordering` patches `order` on relations rather than on a model, so if you want `Model.order(:ascend_by_username)` to work, you need:
42
+
43
+ ```ruby
44
+ ActiveSupport.on_load(:active_record) do
45
+ ActiveRecord::Relation.include(ModernSearchlogic::Ordering)
46
+ end
47
+ ```
48
+
49
+ Note that a module cannot be un-included from a class once it is in the ancestor chain, so `auto_include = false` is the only way to keep a model free of these methods.
50
+
20
51
  ## Contributing
21
52
 
22
53
  Optional, but required for running specs against Rails 3-5: a [Rails LTS](https://railslts.com) subscription.
@@ -1,12 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModernSearchlogic
2
4
  module ColumnConditions
5
+ extend ActiveSupport::Concern
6
+
3
7
  module ClassMethods
4
8
  def respond_to_missing?(method, *)
5
9
  super || valid_searchlogic_scope?(method)
6
10
  end
7
11
 
8
12
  def valid_searchlogic_scope?(method)
9
- return false if connection.tables.empty? || method =~ /^define_method_/ || abstract_class?
13
+ return false if abstract_class? || method =~ /^define_method_/ || !searchlogic_schema_ready?
10
14
 
11
15
  searchlogic_scope_dynamically_defined?(method) ||
12
16
  !!searchlogic_column_condition_method_block(method.to_s) ||
@@ -15,7 +19,7 @@ module ModernSearchlogic
15
19
 
16
20
  def dynamically_define_searchlogic_method(method)
17
21
  return true if searchlogic_scope_dynamically_defined?(method)
18
- return false unless searchlogic_scope = searchlogic_column_condition_method_block(method.to_s)
22
+ return false unless (searchlogic_scope = searchlogic_column_condition_method_block(method.to_s))
19
23
  singleton_class.__send__(:define_method, method, &searchlogic_scope[:block])
20
24
  self._dynamically_defined_searchlogic_scopes = self._dynamically_defined_searchlogic_scopes.merge(method => searchlogic_scope)
21
25
  true
@@ -33,6 +37,12 @@ module ModernSearchlogic
33
37
 
34
38
  private
35
39
 
40
+ def searchlogic_schema_ready?
41
+ return false if self == ActiveRecord::Base
42
+
43
+ @searchlogic_schema_ready ||= table_exists?
44
+ end
45
+
36
46
  def searchlogic_scope_dynamically_defined?(method)
37
47
  _dynamically_defined_searchlogic_scopes.key?(method)
38
48
  end
@@ -74,7 +84,7 @@ module ModernSearchlogic
74
84
  end
75
85
  end
76
86
 
77
- def searchlogic_active_record_alias(searchlogic_suffix, options = {}, &block)
87
+ def searchlogic_active_record_alias(searchlogic_suffix, &block)
78
88
  searchlogic_suffix_condition "_#{searchlogic_suffix}" do |column_name, *args|
79
89
  raise ArgumentError, "wrong number of arguments (0 for >= 1)" if args.empty?
80
90
  raise ArgumentError, "unsupported searchlogic suffix #{searchlogic_suffix} passed" unless [:in, :not_in].include?(searchlogic_suffix)
@@ -90,8 +100,8 @@ module ModernSearchlogic
90
100
 
91
101
  def searchlogic_suffix_condition_match(method_name)
92
102
  suffix_regexp = searchlogic_suffix_conditions.keys.join('|')
93
- if match = method_name.match(/\A(#{column_names_regexp}(?:_or_#{column_names_regexp})*)(#{suffix_regexp})\z/)
94
- options, method_block = searchlogic_suffix_conditions.fetch(match[2])
103
+ if (match = method_name.match(/\A(#{column_names_regexp}(?:_or_#{column_names_regexp})*)(#{suffix_regexp})\z/))
104
+ _options, method_block = searchlogic_suffix_conditions.fetch(match[2])
95
105
  column_names = match[1].split('_or_')
96
106
 
97
107
  arity = calculate_arity(method_block)
@@ -111,7 +121,7 @@ module ModernSearchlogic
111
121
 
112
122
  def searchlogic_prefix_match(method_name)
113
123
  prefix_regexp = searchlogic_prefix_conditions.keys.join('|')
114
- if match = method_name.match(/\A(#{prefix_regexp})(#{column_names_regexp})\z/)
124
+ if (match = method_name.match(/\A(#{prefix_regexp})(#{column_names_regexp})\z/))
115
125
  method_block = searchlogic_prefix_conditions.fetch(match[1])
116
126
 
117
127
  arity = calculate_arity(method_block)
@@ -123,7 +133,7 @@ module ModernSearchlogic
123
133
  instance_exec(match[2], *args, &method_block)
124
134
  end
125
135
  }
126
- elsif match = method_name.match(/\A(#{prefix_regexp})(#{association_names_regexp})_(\S+)\z/)
136
+ elsif (match = method_name.match(/\A(#{prefix_regexp})(#{association_names_regexp})_(\S+)\z/))
127
137
  prefix, association_name, rest = match.to_a.drop(1)
128
138
 
129
139
  searchlogic_association_finder_method(association_by_name.fetch(association_name.to_sym), prefix + rest)
@@ -131,13 +141,13 @@ module ModernSearchlogic
131
141
  end
132
142
 
133
143
  def searchlogic_association_suffix_match(method_name)
134
- if match = method_name.match(/\A(#{association_names_regexp})_(\S+)\z/)
144
+ if (match = method_name.match(/\A(#{association_names_regexp})_(\S+)\z/))
135
145
  searchlogic_association_finder_method(association_by_name.fetch(match[1].to_sym), match[2])
136
146
  end
137
147
  end
138
148
 
139
149
  def searchlogic_column_boolean_match(method_name)
140
- if match = method_name.match(/^not_(.*)/)
150
+ if (match = method_name.match(/^not_(.*)/))
141
151
  column_name = match[1]
142
152
  if boolean_column?(column_name)
143
153
  {arity: 0, block: lambda { where(column_name => false) }}
@@ -191,7 +201,7 @@ module ModernSearchlogic
191
201
  end
192
202
 
193
203
  def method_missing(method, *args, &block)
194
- return super if connection.tables.empty? || abstract_class?
204
+ return super if abstract_class? || !searchlogic_schema_ready?
195
205
  return super unless dynamically_define_searchlogic_method(method)
196
206
 
197
207
  __send__(method, *args, &block)
@@ -226,77 +236,73 @@ module ModernSearchlogic
226
236
  end
227
237
  end
228
238
 
229
- def self.included(base)
230
- base.extend ClassMethods
231
-
232
- base.class_eval do
233
- class_attribute :searchlogic_suffix_conditions
234
- self.searchlogic_suffix_conditions = {}
235
-
236
- class_attribute :searchlogic_prefix_conditions
237
- self.searchlogic_prefix_conditions = {}
238
-
239
- searchlogic_arel_alias :equals, :eq
240
- searchlogic_arel_alias :eq, :eq
241
- searchlogic_arel_alias :is, :eq
242
- searchlogic_arel_alias :does_not_equal, :not_eq
243
- searchlogic_arel_alias :ne, :not_eq
244
- searchlogic_arel_alias :not_eq, :not_eq
245
- searchlogic_arel_alias :not, :not_eq
246
- searchlogic_arel_alias :is_not, :not_eq
247
- searchlogic_arel_alias :greater_than, :gt
248
- searchlogic_arel_alias :gt, :gt
249
- searchlogic_arel_alias :less_than, :lt
250
- searchlogic_arel_alias :lt, :lt
251
- searchlogic_arel_alias :greater_than_or_equal_to, :gteq
252
- searchlogic_arel_alias :gte, :gteq
253
- searchlogic_arel_alias :less_than_or_equal_to, :lteq
254
- searchlogic_arel_alias :lte, :lteq
255
- searchlogic_active_record_alias :in do |column, values|
256
- has_nil = values.include?(nil)
257
- values = values.flatten.compact
258
- subs = [values]
259
- if has_nil
260
- subs << nil
261
- end
262
- where(column => searchlogic_extract_arel_compatible_value(subs))
263
- end
264
- searchlogic_active_record_alias :not_in do |column, values|
265
- values = searchlogic_extract_arel_compatible_value(values.flatten)
266
- query = values.map { "#{connection.quote_table_name(arel_table.name)}.#{connection.quote_column_name(column)} != ?" }.join(" AND ")
267
- where(query, *values)
268
- end
239
+ included do
240
+ class_attribute :_dynamically_defined_searchlogic_scopes
241
+ self._dynamically_defined_searchlogic_scopes = {}
242
+
243
+ class_attribute :searchlogic_suffix_conditions
244
+ self.searchlogic_suffix_conditions = {}
245
+
246
+ class_attribute :searchlogic_prefix_conditions
247
+ self.searchlogic_prefix_conditions = {}
248
+
249
+ searchlogic_arel_alias :equals, :eq
250
+ searchlogic_arel_alias :eq, :eq
251
+ searchlogic_arel_alias :is, :eq
252
+ searchlogic_arel_alias :does_not_equal, :not_eq
253
+ searchlogic_arel_alias :ne, :not_eq
254
+ searchlogic_arel_alias :not_eq, :not_eq
255
+ searchlogic_arel_alias :not, :not_eq
256
+ searchlogic_arel_alias :is_not, :not_eq
257
+ searchlogic_arel_alias :greater_than, :gt
258
+ searchlogic_arel_alias :gt, :gt
259
+ searchlogic_arel_alias :less_than, :lt
260
+ searchlogic_arel_alias :lt, :lt
261
+ searchlogic_arel_alias :greater_than_or_equal_to, :gteq
262
+ searchlogic_arel_alias :gte, :gteq
263
+ searchlogic_arel_alias :less_than_or_equal_to, :lteq
264
+ searchlogic_arel_alias :lte, :lteq
265
+ searchlogic_active_record_alias :in do |column, values|
266
+ has_nil = values.include?(nil)
267
+ values = values.flatten.compact
268
+ values << nil if has_nil
269
+ where(column => searchlogic_extract_arel_compatible_value(values))
270
+ end
271
+ searchlogic_active_record_alias :not_in do |column, values|
272
+ values = searchlogic_extract_arel_compatible_value(values.flatten)
273
+ query = values.map { "#{connection.quote_table_name(arel_table.name)}.#{connection.quote_column_name(column)} != ?" }.join(" AND ")
274
+ where(query, *values)
275
+ end
269
276
 
270
- searchlogic_arel_alias :like, :matches, :map_value => -> (val) { "%#{val}%" }
271
- searchlogic_arel_alias :begins_with, :matches, :map_value => -> (val) { "#{val}%" }
272
- searchlogic_arel_alias :ends_with, :matches, :map_value => -> (val) { "%#{val}" }
273
- searchlogic_arel_alias :not_like, :does_not_match, :map_value => -> (val) { "%#{val}%" }
274
- searchlogic_arel_alias :not_begin_with, :does_not_match, :map_value => -> (val) { "#{val}%" }
275
- searchlogic_arel_alias :not_end_with, :does_not_match, :map_value => -> (val) { "%#{val}" }
277
+ searchlogic_arel_alias :like, :matches, :map_value => ->(val) { "%#{val}%" }
278
+ searchlogic_arel_alias :begins_with, :matches, :map_value => ->(val) { "#{val}%" }
279
+ searchlogic_arel_alias :ends_with, :matches, :map_value => ->(val) { "%#{val}" }
280
+ searchlogic_arel_alias :not_like, :does_not_match, :map_value => ->(val) { "%#{val}%" }
281
+ searchlogic_arel_alias :not_begin_with, :does_not_match, :map_value => ->(val) { "#{val}%" }
282
+ searchlogic_arel_alias :not_end_with, :does_not_match, :map_value => ->(val) { "%#{val}" }
276
283
 
277
- searchlogic_suffix_condition '_blank' do |column_name|
278
- arel_table[column_name].eq(nil).or(arel_table[column_name].eq(''))
279
- end
284
+ searchlogic_suffix_condition '_blank' do |column_name|
285
+ arel_table[column_name].eq(nil).or(arel_table[column_name].eq(''))
286
+ end
280
287
 
281
- searchlogic_suffix_condition '_present' do |column_name|
282
- arel_table[column_name].not_eq(nil).and(arel_table[column_name].not_eq(''))
283
- end
288
+ searchlogic_suffix_condition '_present' do |column_name|
289
+ arel_table[column_name].not_eq(nil).and(arel_table[column_name].not_eq(''))
290
+ end
284
291
 
285
- null_matcher = lambda { |column_name| arel_table[column_name].eq(nil) }
286
- searchlogic_suffix_condition '_null', &null_matcher
287
- searchlogic_suffix_condition '_nil', &null_matcher
292
+ null_matcher = lambda { |column_name| arel_table[column_name].eq(nil) }
293
+ searchlogic_suffix_condition '_null', &null_matcher
294
+ searchlogic_suffix_condition '_nil', &null_matcher
288
295
 
289
- not_null_matcher = lambda { |column_name| arel_table[column_name].not_eq(nil) }
290
- searchlogic_suffix_condition '_not_null', &not_null_matcher
291
- searchlogic_suffix_condition '_not_nil', &not_null_matcher
296
+ not_null_matcher = lambda { |column_name| arel_table[column_name].not_eq(nil) }
297
+ searchlogic_suffix_condition '_not_null', &not_null_matcher
298
+ searchlogic_suffix_condition '_not_nil', &not_null_matcher
292
299
 
293
- searchlogic_prefix_condition 'descend_by_' do |column_name|
294
- order(arel_table[column_name].desc)
295
- end
300
+ searchlogic_prefix_condition 'descend_by_' do |column_name|
301
+ order(arel_table[column_name].desc)
302
+ end
296
303
 
297
- searchlogic_prefix_condition 'ascend_by_' do |column_name|
298
- order(arel_table[column_name].asc)
299
- end
304
+ searchlogic_prefix_condition 'ascend_by_' do |column_name|
305
+ order(arel_table[column_name].asc)
300
306
  end
301
307
  end
302
308
  end
@@ -1,13 +1,15 @@
1
1
  module ModernSearchlogic
2
2
  module DefaultScoping
3
+ extend ActiveSupport::Concern
4
+
3
5
  module ClassMethods
4
6
  def searchlogic_default_scope
5
- respond_to?(:scoped) ? scoped : all
7
+ if Rails::VERSION::MAJOR < 4
8
+ scoped
9
+ else
10
+ all
11
+ end
6
12
  end
7
13
  end
8
-
9
- def self.included(base)
10
- base.extend ClassMethods
11
- end
12
14
  end
13
15
  end
@@ -1,20 +1,21 @@
1
1
  module ModernSearchlogic
2
2
  module Ordering
3
- def self.included(base)
4
- base.module_eval do
5
- define_method(:order_with_modern_searchlogic) do |*args|
6
- args.reduce(self) do |scope, arg|
7
- expression = arg.to_s
8
- if expression.match(/^(ascend|descend)_by_(.*)/) && respond_to?(expression)
9
- scope.send(expression)
10
- else
11
- scope.order_without_modern_searchlogic(arg)
12
- end
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ define_method(:order_with_modern_searchlogic) do |*args|
7
+ args.reduce(self) do |scope, arg|
8
+ expression = arg.to_s
9
+ if expression.match(/^(ascend|descend)_by_(.*)/) && respond_to?(expression)
10
+ scope.send(expression)
11
+ else
12
+ scope.order_without_modern_searchlogic(arg)
13
13
  end
14
14
  end
15
- alias_method :order_without_modern_searchlogic, :order
16
- alias_method :order, :order_with_modern_searchlogic
17
15
  end
16
+
17
+ alias_method :order_without_modern_searchlogic, :order
18
+ alias_method :order, :order_with_modern_searchlogic
18
19
  end
19
20
  end
20
21
  end
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModernSearchlogic
2
4
  class Railtie < Rails::Railtie
3
- initializer 'modern_searchlogic.setup_activerecord' do
5
+ config.modern_searchlogic = ActiveSupport::OrderedOptions.new
6
+ config.modern_searchlogic.auto_include = true
7
+
8
+ initializer 'modern_searchlogic.setup_activerecord' do |app|
4
9
  ActiveSupport.on_load(:active_record) do
5
- ModernSearchlogic::ActiveRecordMethods.install
10
+ ModernSearchlogic::ActiveRecordMethods.install if app.config.modern_searchlogic.auto_include
6
11
  end
7
12
  end
8
13
  end
@@ -1,17 +1,19 @@
1
1
  module ModernSearchlogic
2
2
  module ScopeProcedure
3
- def self.included(base)
4
- base.singleton_class.class_eval do
5
- def scope_procedure(name, options = nil)
6
- if options.is_a?(Proc)
7
- define_singleton_method(name, &options)
8
- else
9
- define_singleton_method(name) do |*args|
10
- public_send(options, *args)
11
- end
3
+ extend ActiveSupport::Concern
4
+
5
+ include ScopeTracking
6
+
7
+ module ClassMethods
8
+ def scope_procedure(name, options = nil)
9
+ if options.is_a?(Proc)
10
+ define_singleton_method(name, &options)
11
+ else
12
+ define_singleton_method(name) do |*args|
13
+ public_send(options, *args)
12
14
  end
13
- self._defined_scopes << name.to_sym
14
15
  end
16
+ self._defined_scopes << name.to_sym
15
17
  end
16
18
  end
17
19
  end
@@ -1,5 +1,12 @@
1
1
  module ModernSearchlogic
2
2
  module ScopeTracking
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :_defined_scopes
7
+ self._defined_scopes = Set.new
8
+ end
9
+
3
10
  module ClassMethods
4
11
  def scope(name, body, &block)
5
12
  super(name, body, &block).tap do |*|
@@ -7,15 +14,5 @@ module ModernSearchlogic
7
14
  end
8
15
  end
9
16
  end
10
-
11
- def self.included(base)
12
- base.extend ClassMethods
13
- base.class_eval do
14
- class_attribute :_defined_scopes
15
- self._defined_scopes = Set.new
16
- class_attribute :_dynamically_defined_searchlogic_scopes
17
- self._dynamically_defined_searchlogic_scopes = {}
18
- end
19
- end
20
17
  end
21
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModernSearchlogic
2
4
  class Search
3
5
  def self.search(model_class, options = {})
@@ -2,14 +2,15 @@ require_relative 'search'
2
2
 
3
3
  module ModernSearchlogic
4
4
  module Searchable
5
+ extend ActiveSupport::Concern
6
+
7
+ include DefaultScoping
8
+ include ColumnConditions
9
+
5
10
  module ClassMethods
6
11
  def search(options = {})
7
12
  Search.search(self, options)
8
13
  end
9
14
  end
10
-
11
- def self.included(base)
12
- base.extend ClassMethods
13
- end
14
15
  end
15
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModernSearchlogic
4
- VERSION = "1.0.1"
4
+ VERSION = "1.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modern_searchlogic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Warner
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-09-23 00:00:00.000000000 Z
13
+ date: 2026-08-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails