diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05756d7..d49b985 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby_version: [2.7.2, 3.0.0, 3.2.0] + ruby_version: [3.1.0, 3.2.0, 3.3.0] continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }} # Has to be top level to cache properly env: diff --git a/.ruby-version b/.ruby-version index 37c2961..0aec50e 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.2 +3.1.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bb0681..159bc8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.2.0 + +- Add functionality to purge certain `Object`-based method pollution which interferes with the workings of Serbea's `Pipeline` in pure Ruby. +- Ruby 3.1 is now the minimum required version. + ## 2.1.0 - Remove Active Support as a dependency diff --git a/docs/src/index.md b/docs/src/index.md index 6e6c3b8..c7f1fee 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -392,3 +392,7 @@ Pipelines "inherit" their calling context by using Ruby's `binding` feature. Tha Another interesting facet of Serbea pipelines is that they're forgiving by default. If a filter can't be found (either there's no method available to call the object itself nor is there a separate helper method), it will log a warning to STDERR and continue on. This is to make the syntax feel a bit more like HTML and CSS where you can make a mistake or encounter an unexpected error condition yet not crash the entire application. If you do want to crash your entire application (😜), you can set the configuration option: `Serbea::Pipeline.raise_on_missing_filters = true`. This will raise a `Serbea::FilterMissing` error if a filter can't be found. + +**Note:** if you find that a certain method doesn't work within a pipeline when writing plain Ruby, because that method has polluted the global `Object` set of instance methods, you can call `Serbea::Pipeline.polluted_method(:method_name_here)` to strip that off and allow filtering to work. It also accepts an array of symbols. + +**Note:** if you find that a certain filter is being called as a method directly on the value object, rather than a defined filter method itself, you can add that method name to a deny list via `Serbea::Pipeline.deny_value_method(:method_name_here)`. It also accepts an array of symbols. diff --git a/lib/serbea/pipeline.rb b/lib/serbea/pipeline.rb index ac1789c..297fd90 100644 --- a/lib/serbea/pipeline.rb +++ b/lib/serbea/pipeline.rb @@ -72,7 +72,27 @@ def self.value_methods_denylist @value_methods_denylist ||= Set.new end + def self.purge_class_pollution + @pollution_purged ||= begin + polluted_methods_list.each do |name| + define_method name do |*args, **kwargs| + filter(name, *args, **kwargs) + end + end + + true + end + end + + def self.polluted_method(name) + polluted_methods_list.merge Array(name) + end + def self.polluted_methods_list + @polluted_methods_list ||= Set.new(%i(select to_json)) + end + def initialize(binding, value) + self.class.purge_class_pollution @binding = binding @context = binding.receiver @value = value diff --git a/lib/version.rb b/lib/version.rb index 0259f11..068cc5b 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -1,3 +1,3 @@ module Serbea - VERSION = "2.1.0" + VERSION = "2.2.0" end \ No newline at end of file diff --git a/serbea.gemspec b/serbea.gemspec index 176e14d..f51c529 100644 --- a/serbea.gemspec +++ b/serbea.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/bridgetownrb/serbea" spec.license = "MIT" - spec.required_ruby_version = ">= 2.7" + spec.required_ruby_version = ">= 3.1" spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r!^(test|script|spec|features|docs|serbea-rails)/!) } spec.require_paths = ["lib"] diff --git a/test/test.rb b/test/test.rb index 0b6b67d..acc6520 100644 --- a/test/test.rb +++ b/test/test.rb @@ -246,6 +246,14 @@ def test_multiline(input_value) end end + def test_json_filter + pipe({a: 1, b: "2"}) do + select proc { _1 == :a } + to_h + to_json + end + end + def transform_this_way(input) input.join("=") end @@ -267,4 +275,7 @@ def test_join(input, delimeter) raise "Multi-line pipeline broken! #{pipeline_output}" unless pipeline_output == "VAL A=123 !!" +pipeline_output = PipelineTemplateTest.new.test_json_filter +raise "Unpolluted pipeline methods not working! #{pipeline_output}" unless pipeline_output == '{"a":1}' + puts "\nYay! Tests passed."