mongory 0.3.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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +84 -0
  4. data/.yardopts +7 -0
  5. data/CHANGELOG.md +246 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +517 -0
  9. data/Rakefile +12 -0
  10. data/examples/README.md +41 -0
  11. data/examples/benchmark.rb +44 -0
  12. data/lib/generators/mongory/install/install_generator.rb +42 -0
  13. data/lib/generators/mongory/install/templates/initializer.rb.erb +83 -0
  14. data/lib/generators/mongory/matcher/matcher_generator.rb +56 -0
  15. data/lib/generators/mongory/matcher/templates/matcher.rb.erb +92 -0
  16. data/lib/generators/mongory/matcher/templates/matcher_spec.rb.erb +17 -0
  17. data/lib/mongory/converters/abstract_converter.rb +122 -0
  18. data/lib/mongory/converters/condition_converter.rb +74 -0
  19. data/lib/mongory/converters/data_converter.rb +26 -0
  20. data/lib/mongory/converters/key_converter.rb +63 -0
  21. data/lib/mongory/converters/value_converter.rb +47 -0
  22. data/lib/mongory/converters.rb +7 -0
  23. data/lib/mongory/matchers/README.md +57 -0
  24. data/lib/mongory/matchers/abstract_matcher.rb +153 -0
  25. data/lib/mongory/matchers/abstract_multi_matcher.rb +109 -0
  26. data/lib/mongory/matchers/abstract_operator_matcher.rb +46 -0
  27. data/lib/mongory/matchers/and_matcher.rb +65 -0
  28. data/lib/mongory/matchers/array_record_matcher.rb +88 -0
  29. data/lib/mongory/matchers/elem_match_matcher.rb +47 -0
  30. data/lib/mongory/matchers/eq_matcher.rb +37 -0
  31. data/lib/mongory/matchers/every_matcher.rb +41 -0
  32. data/lib/mongory/matchers/exists_matcher.rb +48 -0
  33. data/lib/mongory/matchers/field_matcher.rb +123 -0
  34. data/lib/mongory/matchers/gt_matcher.rb +29 -0
  35. data/lib/mongory/matchers/gte_matcher.rb +29 -0
  36. data/lib/mongory/matchers/hash_condition_matcher.rb +55 -0
  37. data/lib/mongory/matchers/in_matcher.rb +52 -0
  38. data/lib/mongory/matchers/literal_matcher.rb +123 -0
  39. data/lib/mongory/matchers/lt_matcher.rb +29 -0
  40. data/lib/mongory/matchers/lte_matcher.rb +29 -0
  41. data/lib/mongory/matchers/ne_matcher.rb +29 -0
  42. data/lib/mongory/matchers/nin_matcher.rb +51 -0
  43. data/lib/mongory/matchers/not_matcher.rb +32 -0
  44. data/lib/mongory/matchers/or_matcher.rb +60 -0
  45. data/lib/mongory/matchers/present_matcher.rb +52 -0
  46. data/lib/mongory/matchers/regex_matcher.rb +61 -0
  47. data/lib/mongory/matchers.rb +176 -0
  48. data/lib/mongory/mongoid.rb +19 -0
  49. data/lib/mongory/query_builder.rb +187 -0
  50. data/lib/mongory/query_matcher.rb +66 -0
  51. data/lib/mongory/query_operator.rb +28 -0
  52. data/lib/mongory/rails.rb +15 -0
  53. data/lib/mongory/utils/debugger.rb +123 -0
  54. data/lib/mongory/utils/rails_patch.rb +22 -0
  55. data/lib/mongory/utils/singleton_builder.rb +31 -0
  56. data/lib/mongory/utils.rb +75 -0
  57. data/lib/mongory/version.rb +5 -0
  58. data/lib/mongory-rb.rb +3 -0
  59. data/lib/mongory.rb +116 -0
  60. data/mongory.gemspec +40 -0
  61. data/sig/mongory.rbs +4 -0
  62. metadata +108 -0
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongory
4
+ module Utils
5
+ # Debugger provides an internal tracing system for query matching.
6
+ # It tracks matcher evaluation in a tree structure and provides tree-like format output.
7
+ #
8
+ # It captures each matcher evaluation with indentation and visualizes the hierarchy,
9
+ # helping developers understand nested matcher flows (e.g. `$and`, `$or`, etc).
10
+ #
11
+ # Usage:
12
+ # Debugger.instance.with_indent { ... } # wraps around matcher logic
13
+ # Debugger.instance.display # prints trace tree after execution
14
+ #
15
+ # Note:
16
+ # The trace is recorded in post-order (leaf nodes enter first),
17
+ # and `reorder_traces_for_display` is used to transform it into a
18
+ # pre-order format suitable for display.
19
+ #
20
+ # This class is a singleton and should be accessed via `Debugger.instance`.
21
+ #
22
+ # @example Enable debugging
23
+ # Debugger.instance.enable
24
+ # Mongory::QueryBuilder.new(...).filter(...)
25
+ #
26
+ class Debugger < SingletonBuilder
27
+ include Singleton
28
+
29
+ def initialize
30
+ super(self.class.name)
31
+ @indent_level = -1
32
+ @trace_entries = []
33
+ end
34
+
35
+ # Enables debug mode by aliasing `match?` to `debug_match`.
36
+ # @return [void]
37
+ def enable
38
+ Matchers::AbstractMatcher.alias_method :match?, :debug_match
39
+ end
40
+
41
+ # Disables debug mode by restoring `match?` to `regular_match`.
42
+ # @return [void]
43
+ def disable
44
+ Matchers::AbstractMatcher.alias_method :match?, :regular_match
45
+ end
46
+
47
+ # Wraps a matcher evaluation block with indentation control.
48
+ #
49
+ # It increments the internal indent level before the block,
50
+ # and decrements it after. The yielded block's return value
51
+ # is used as trace content and pushed onto the trace_entries.
52
+ #
53
+ # @yieldreturn [String] the trace content to log
54
+ # @return [Object] the result of the block
55
+ def with_indent
56
+ @indent_level += 1
57
+ display_string = yield
58
+ @trace_entries << TraceEntry.new(display_string, @indent_level)
59
+ ensure
60
+ @indent_level -= 1
61
+ end
62
+
63
+ # Prints the visualized trace tree to STDOUT.
64
+ #
65
+ # This processes the internal trace_entries (post-order) into
66
+ # a structured pre-order list that represents nested matcher evaluation.
67
+ # @return [void]
68
+ def display
69
+ reorder_traces_for_display(@trace_entries).each do |trace|
70
+ puts trace.formatted
71
+ end
72
+
73
+ @trace_entries.clear
74
+ nil
75
+ end
76
+
77
+ # Recursively reorders trace lines by indentation level to produce a
78
+ # display-friendly structure where parents precede children.
79
+ #
80
+ # The original trace is built in post-order (leaf first), and this method
81
+ # transforms it into a pre-order structure suitable for tree display.
82
+ #
83
+ # @param traces [Array<TraceEntry>] the raw trace lines (leaf-first)
84
+ # @param level [Integer] current processing level
85
+ # @return [Array<TraceEntry>] reordered trace lines for display
86
+ def reorder_traces_for_display(traces, level = 0)
87
+ result = []
88
+ group = []
89
+ traces.each do |trace|
90
+ if trace.level == level
91
+ result << trace
92
+ result.concat reorder_traces_for_display(group, level + 1)
93
+ group = []
94
+ else
95
+ group << trace
96
+ end
97
+ end
98
+
99
+ result
100
+ end
101
+
102
+ def clear
103
+ # Clears the internal trace buffer.
104
+ # This is useful when re-running a query and avoiding stale trace output.
105
+ # @return [void]
106
+ @trace_entries.clear
107
+ end
108
+
109
+ # @private
110
+ # A trace entry representing a single matcher output at a given indentation level.
111
+ #
112
+ # @!attribute text
113
+ # @return [String] the string to be printed
114
+ # @!attribute level
115
+ # @return [Integer] the indentation level of this entry
116
+ TraceEntry = Struct.new(:text, :level) do
117
+ def formatted
118
+ "#{' ' * level}#{text}"
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongory
4
+ module Utils
5
+ # Only loaded when Rails is present
6
+ module RailsPatch
7
+ # Use Object#present? which defined in Rails.
8
+ # @param target[Object]
9
+ # @return [Boolean]
10
+ def is_present?(target)
11
+ target.present?
12
+ end
13
+
14
+ # Use Object#blank? which defined in Rails.
15
+ # @param target[Object]
16
+ # @return [Boolean]
17
+ def is_blank?(target)
18
+ target.blank?
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongory
4
+ module Utils
5
+ # A singleton placeholder object used to represent special sentinel values.
6
+ #
7
+ # Used in situations where `nil` is a valid value and cannot be used as a marker.
8
+ # Typically used for internal constants like `NOTHING` or `KEY_NOT_FOUND`.
9
+ #
10
+ # @example
11
+ # NOTHING = SingletonBuilder.new('NOTHING')
12
+ # value == NOTHING # => true if placeholder
13
+ class SingletonBuilder
14
+ # @param label [String] a human-readable label for the marker
15
+ def initialize(label, &block)
16
+ @label = label
17
+ instance_eval(&block) if block_given?
18
+ end
19
+
20
+ # @return [String] formatted label
21
+ def inspect
22
+ "#<#{@label}>"
23
+ end
24
+
25
+ # @return [String] formatted label
26
+ def to_s
27
+ "#<#{@label}>"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative 'utils/singleton_builder'
5
+ require_relative 'utils/debugger'
6
+
7
+ module Mongory
8
+ # Utility helpers shared across Mongory internals.
9
+ #
10
+ # Includes blank checking, present checking,
11
+ # and class-level instance method caching.
12
+ module Utils
13
+ # When included, also extends the including class with ClassMethods.
14
+ # And record which class include this module.
15
+ #
16
+ # @param base [Class, Module]
17
+ def self.included(base)
18
+ base.extend(ClassMethods)
19
+ super
20
+ included_classes << base
21
+ end
22
+
23
+ # Where to record classes that include Utils.
24
+ #
25
+ # @return [Array]
26
+ def self.included_classes
27
+ @included_classes ||= []
28
+ end
29
+
30
+ # Checks if an object is "present".
31
+ # Inverse of {#is_blank?}.
32
+ #
33
+ # @param obj [Object]
34
+ # @return [Boolean]
35
+ def is_present?(obj)
36
+ !is_blank?(obj)
37
+ end
38
+
39
+ # Determines whether an object is considered "blank".
40
+ # Nil, false, empty string/array/hash are blank.
41
+ #
42
+ # @param obj [Object]
43
+ # @return [Boolean]
44
+ def is_blank?(obj)
45
+ case obj
46
+ when false, nil
47
+ true
48
+ when Hash, Array, String
49
+ obj.empty?
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ # Class-level methods injected via Utils.
56
+ module ClassMethods
57
+ # Defines a lazily-evaluated, memoized instance method.
58
+ #
59
+ # @param name [Symbol] the method name
60
+ # @yield block to compute the value
61
+ # @return [void]
62
+ #
63
+ # @example
64
+ # define_instance_cache_method(:expensive_thing) { compute_something }
65
+ def define_instance_cache_method(name, &block)
66
+ instance_key = :"@#{name}"
67
+ define_method(name) do
68
+ return instance_variable_get(instance_key) if instance_variable_defined?(instance_key)
69
+
70
+ instance_variable_set(instance_key, instance_exec(&block))
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongory
4
+ VERSION = '0.3.0'
5
+ end
data/lib/mongory-rb.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mongory'
data/lib/mongory.rb ADDED
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'date'
5
+ require 'singleton'
6
+ require_relative 'mongory/version'
7
+ require_relative 'mongory/utils'
8
+ require_relative 'mongory/matchers'
9
+ require_relative 'mongory/query_matcher'
10
+ require_relative 'mongory/query_builder'
11
+ require_relative 'mongory/query_operator'
12
+ require_relative 'mongory/converters'
13
+ require_relative 'mongory/rails' if defined?(Rails::Railtie)
14
+ require_relative 'mongory/mongoid' if defined?(Mongoid)
15
+
16
+ # Main namespace for Mongory DSL and configuration.
17
+ #
18
+ # Provides access to core converters, query construction, and optional
19
+ # integrations with frameworks like Rails or Mongoid.
20
+ #
21
+ # @example Basic usage
22
+ # Mongory.build_query(records).where(age: { :$gt => 18 })
23
+ #
24
+ # @example Enabling DSL snippets
25
+ # Mongory.enable_symbol_snippets!
26
+ # Mongory.register(Array)
27
+ module Mongory
28
+ class Error < StandardError; end
29
+ class TypeError < Error; end
30
+
31
+ # Yields Mongory for configuration and freezes key components.
32
+ #
33
+ # @example Configure converters
34
+ # Mongory.configure do |mc|
35
+ # mc.data_converter.configure do |dc|
36
+ # dc.register(MyType) { transform(self) }
37
+ # end
38
+ #
39
+ # mc.condition_converter.key_converter.configure do |kc|
40
+ # kc.register(MyKeyType) { normalize_key(self) }
41
+ # end
42
+ #
43
+ # mc.condition_converter.value_converter.configure do |vc|
44
+ # vc.register(MyValueType) { cast_value(self) }
45
+ # end
46
+ # end
47
+ #
48
+ # @yieldparam self [Mongory]
49
+ # @return [void]
50
+ def self.configure
51
+ yield self
52
+ data_converter.freeze
53
+ condition_converter.freeze
54
+ Matchers.freeze
55
+ end
56
+
57
+ # Returns the data converter instance.
58
+ #
59
+ # @return [Converters::DataConverter]
60
+ def self.data_converter
61
+ Converters::DataConverter.instance
62
+ end
63
+
64
+ # Returns the condition converter instance.
65
+ #
66
+ # @return [Converters::ConditionConverter]
67
+ def self.condition_converter
68
+ Converters::ConditionConverter.instance
69
+ end
70
+
71
+ # Returns the debugger instance.
72
+ #
73
+ # @return [Utils::Debugger]
74
+ def self.debugger
75
+ Utils::Debugger.instance
76
+ end
77
+
78
+ # Builds a new query over the given record set.
79
+ #
80
+ # @param records [Enumerable] any enumerable object (Array, AR::Relation, etc.)
81
+ # @return [QueryBuilder] a new query builder
82
+ def self.build_query(records)
83
+ QueryBuilder.new(records)
84
+ end
85
+
86
+ # Registers a class to support `.mongory` query DSL.
87
+ # This injects a `#mongory` method into the given class.
88
+ #
89
+ # @param klass [Class] the class to register (e.g., Array, ActiveRecord::Relation)
90
+ # @return [void]
91
+ def self.register(klass)
92
+ klass.include(ClassExtention)
93
+ end
94
+
95
+ # Enables symbol snippets like `:age.gte` or `:name.regex`
96
+ # by dynamically defining methods on `Symbol` for query operators.
97
+ #
98
+ # Skips operators that already exist to avoid patching Mongoid or other gems.
99
+ #
100
+ # @return [void]
101
+ def self.enable_symbol_snippets!
102
+ Matchers.enable_symbol_snippets!
103
+ end
104
+
105
+ # Adds a `#mongory` method to the target class via inclusion.
106
+ #
107
+ # Typically used internally by `Mongory.register(...)`.
108
+ module ClassExtention
109
+ # Returns a query builder scoped to `self`.
110
+ #
111
+ # @return [QueryBuilder]
112
+ def mongory
113
+ Mongory::QueryBuilder.new(self)
114
+ end
115
+ end
116
+ end
data/mongory.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/mongory/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'mongory'
7
+ spec.version = Mongory::VERSION
8
+ spec.authors = ['koten0224']
9
+ spec.email = ['koten0224@gmail.com']
10
+
11
+ spec.summary = 'MongoDB-like in-memory query DSL for Ruby'
12
+ spec.description = 'A Mongo-like in-memory query DSL for Ruby'
13
+ spec.homepage = 'https://koten0224.github.io/mongory-rb/'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.6.0'
16
+
17
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
+ spec.metadata['rubygems_mfa_required'] = 'true'
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/koten0224/mongory-rb'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/koten0224/mongory-rb/blob/main/CHANGELOG.md'
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w(bin/ test/ spec/ features/ .git .github appveyor Gemfile))
29
+ end
30
+ end
31
+ spec.bindir = 'exe'
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ['lib']
34
+
35
+ # Uncomment to register a new dependency of your gem
36
+ # spec.add_dependency 'example-gem', '~> 1.0'
37
+
38
+ # For more information and examples about making a new gem, check out our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ end
data/sig/mongory.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Mongory
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongory
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - koten0224
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-04-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Mongo-like in-memory query DSL for Ruby
14
+ email:
15
+ - koten0224@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - ".rubocop.yml"
22
+ - ".yardopts"
23
+ - CHANGELOG.md
24
+ - CODE_OF_CONDUCT.md
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - examples/README.md
29
+ - examples/benchmark.rb
30
+ - lib/generators/mongory/install/install_generator.rb
31
+ - lib/generators/mongory/install/templates/initializer.rb.erb
32
+ - lib/generators/mongory/matcher/matcher_generator.rb
33
+ - lib/generators/mongory/matcher/templates/matcher.rb.erb
34
+ - lib/generators/mongory/matcher/templates/matcher_spec.rb.erb
35
+ - lib/mongory-rb.rb
36
+ - lib/mongory.rb
37
+ - lib/mongory/converters.rb
38
+ - lib/mongory/converters/abstract_converter.rb
39
+ - lib/mongory/converters/condition_converter.rb
40
+ - lib/mongory/converters/data_converter.rb
41
+ - lib/mongory/converters/key_converter.rb
42
+ - lib/mongory/converters/value_converter.rb
43
+ - lib/mongory/matchers.rb
44
+ - lib/mongory/matchers/README.md
45
+ - lib/mongory/matchers/abstract_matcher.rb
46
+ - lib/mongory/matchers/abstract_multi_matcher.rb
47
+ - lib/mongory/matchers/abstract_operator_matcher.rb
48
+ - lib/mongory/matchers/and_matcher.rb
49
+ - lib/mongory/matchers/array_record_matcher.rb
50
+ - lib/mongory/matchers/elem_match_matcher.rb
51
+ - lib/mongory/matchers/eq_matcher.rb
52
+ - lib/mongory/matchers/every_matcher.rb
53
+ - lib/mongory/matchers/exists_matcher.rb
54
+ - lib/mongory/matchers/field_matcher.rb
55
+ - lib/mongory/matchers/gt_matcher.rb
56
+ - lib/mongory/matchers/gte_matcher.rb
57
+ - lib/mongory/matchers/hash_condition_matcher.rb
58
+ - lib/mongory/matchers/in_matcher.rb
59
+ - lib/mongory/matchers/literal_matcher.rb
60
+ - lib/mongory/matchers/lt_matcher.rb
61
+ - lib/mongory/matchers/lte_matcher.rb
62
+ - lib/mongory/matchers/ne_matcher.rb
63
+ - lib/mongory/matchers/nin_matcher.rb
64
+ - lib/mongory/matchers/not_matcher.rb
65
+ - lib/mongory/matchers/or_matcher.rb
66
+ - lib/mongory/matchers/present_matcher.rb
67
+ - lib/mongory/matchers/regex_matcher.rb
68
+ - lib/mongory/mongoid.rb
69
+ - lib/mongory/query_builder.rb
70
+ - lib/mongory/query_matcher.rb
71
+ - lib/mongory/query_operator.rb
72
+ - lib/mongory/rails.rb
73
+ - lib/mongory/utils.rb
74
+ - lib/mongory/utils/debugger.rb
75
+ - lib/mongory/utils/rails_patch.rb
76
+ - lib/mongory/utils/singleton_builder.rb
77
+ - lib/mongory/version.rb
78
+ - mongory.gemspec
79
+ - sig/mongory.rbs
80
+ homepage: https://koten0224.github.io/mongory-rb/
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ allowed_push_host: https://rubygems.org
85
+ rubygems_mfa_required: 'true'
86
+ homepage_uri: https://koten0224.github.io/mongory-rb/
87
+ source_code_uri: https://github.com/koten0224/mongory-rb
88
+ changelog_uri: https://github.com/koten0224/mongory-rb/blob/main/CHANGELOG.md
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 2.6.0
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.1.6
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: MongoDB-like in-memory query DSL for Ruby
108
+ test_files: []