### Defining a Rubanok Processor with Parameter Mapping Source: https://github.com/palkan/rubanok/blob/master/README.md Illustrates how to create a Rubanok processor by mapping incoming parameters to specific data transformations. This example shows mapping a search query parameter 'q' to a `search` method on the input data. ```ruby class CourseSessionsProcessor < Rubanok::Processor # You can map keys map :q do |q:| # `raw` is an accessor for input data raw.search(q) end end ``` -------------------------------- ### Rubanok Installation via Gemfile Source: https://github.com/palkan/rubanok/blob/master/README.md Shows how to add the Rubanok gem to a Rails project's Gemfile for installation. ```ruby gem "rubanok" ``` -------------------------------- ### Define Default Input Transformation with `prepare` Source: https://context7.com/palkan/rubanok/llms.txt The `prepare` method defines a transformation that executes before any rules are applied, but only if at least one rule matches. It's useful for initializing default data structures or performing setup transformations. If the input already matches the prepared structure, `prepare` returns nil. ```ruby class SearchQueryProcessor < Rubanok::Processor # Prepare is called before first matching rule prepare do next if raw&.dig(:query, :bool) { query: { bool: { must: [], filter: [] } } } end map :term do |term מד:| raw[:query][:bool][:must] << { match: { content: term } } raw end map :category do |category מד:| raw[:query][:bool][:filter] << { term: { category: category } } raw end map :date_from, :date_to do |date_from מד:, date_to מד:| raw[:query][:bool][:filter] << { range: { created_at: { gte: date_from, lte: date_to } } } raw end end # Without matching params, prepare is NOT called SearchQueryProcessor.call(nil, {}) # => nil # With matching params, prepare initializes the structure SearchQueryProcessor.call(nil, { term: "ruby", category: "programming" }) # => { # query: { # bool: { # must: [{ match: { content: "ruby" } }], # filter: [{ term: { category: "programming" } }] # } # } # } # When input already has the structure, prepare returns nil (no change) existing = { query: { bool: { must: [], filter: [] } } } SearchQueryProcessor.call(existing, { term: "rails" }) # => { query: { bool: { must: [{ match: { content: "rails" } }], filter: [] } } } ``` -------------------------------- ### Default Transformation with `prepare` Method in Ruby Source: https://github.com/palkan/rubanok/blob/master/README.md Demonstrates how to use the `prepare` method in a Rubanok Processor to define default transformations before any rules are applied. The `prepare` block can return a new initial value for the raw input or nil if no transformation is needed. It shows conditional execution and example calls. ```Ruby class CourseSearchQueryProcessor < Rubanok::Processor prepare do next if raw&.dig(:query, :bool) {query: {bool: {filters: []}}} end map :ids do |ids:| raw.dig(:query, :bool, :filters) << {terms: {id: ids}} raw end end ``` ```Ruby CourseSearchQueryProcessor.call(nil, {ids: [1]}) #=> {query {bool: {filters: [{terms: {ids: [1]}}]}}} ``` ```Ruby CourseSearchQueryProcessor.call({ids: [1]}) #=> {query {bool: {filters: [{terms: {ids: [1]}}]}}} ``` -------------------------------- ### Testing Rubanok Processors with RSpec Source: https://github.com/palkan/rubanok/blob/master/README.md Provides examples of how to test Rubanok processors in isolation using RSpec. It shows how to test the processor's logic directly and how to use the `have_rubanok_processed` matcher in controller tests to verify that the correct processing is applied. ```Ruby # For example, with RSpec RSpec.describe CourseSessionsProcessor do let(:input) { CourseSession.all } let(:params) { {} } subject { described_class.call(input, params) } specify "searching" do params[:q] = "wood" expect(subject).to eq input.search("wood") end end ``` ```Ruby RSpec.describe CourseSessionController do subject { get :index } specify do expect { subject }.to have_rubanok_processed(CourseSession.all) .with(CourseSessionsProcessor) end end ``` ```Ruby # To use `have_rubanok_processed` matcher you must add the following line to your `spec_helper.rb` / `rails_helper.rb` # require "rubanok/rspec" ``` -------------------------------- ### Accessing Processed Parameters in Rails Controller Source: https://github.com/palkan/rubanok/blob/master/README.md Shows how to use the `rubanok_process` and `rubanok_scope` methods within a Rails controller to process data and retrieve the parameters recognized by a Rubanok processor. `rubanok_scope` can be used to get a hash of recognized parameters, useful for displaying filter states. ```Ruby class CourseSessionController < ApplicationController def index @sessions = rubanok_process(CourseSession.all) @sessions_filter = rubanok_scope( params.permit(:q, :role_id), with: CourseSessionProcessor ) @sessions_filter = rubanok_scope #=> equals to rubanok_scope(params, with: implicit_rubanok_class) end end ``` -------------------------------- ### Using rubanok_scope for Active Filters in Views Source: https://context7.com/palkan/rubanok/llms.txt The `rubanok_scope` helper retrieves projected parameters recognized by the processor, which is useful for displaying active filters in views. It can be used directly in controllers to get recognized parameters or with explicit parameters and processor classes. It's also available as a view helper to check for present filters. ```ruby # app/controllers/products_controller.rb class ProductsController < ApplicationController def index @products = rubanok_process(Product.all) # Get params recognized by the processor @active_filters = rubanok_scope # Or with explicit params/processor @active_filters = rubanok_scope( params.permit(:q, :category, :min_price, :max_price), with: ProductsProcessor ) end end ``` ```erb # app/views/products/index.html.erb <% if rubanok_scope[:q].present? %>
Searching for: <%= rubanok_scope[:q] %>
<% end %> <% if rubanok_scope[:category].present? %>Category: <%= rubanok_scope[:category] %>
<% end %> <%= link_to "Clear filters", products_path %> ``` -------------------------------- ### prepare - Define default input transformation Source: https://context7.com/palkan/rubanok/llms.txt The prepare method defines a transformation that runs before any rules are applied, but only if at least one rule matches. ```APIDOC ## prepare ### Description Initializes default data structures or performs setup transformations before processing rules are executed. ### Method N/A (DSL Method) ### Parameters - **block** (Proc) - Required - The transformation logic that returns the initialized structure or nil. ``` -------------------------------- ### Rubanok Processor and Scope Usage in Ruby Source: https://github.com/palkan/rubanok/blob/master/CHANGELOG.md Demonstrates how to define a custom processor using Rubanok's DSL for mapping and matching parameters. It also shows how to use `Process.project` to filter parameters and `rubanok_scope` within a Rails controller to obtain filtered parameters, optionally specifying a processor. ```ruby class PostsProcessor < Rubanok::Processor map(:q) { block } match(:page, :per_page, activate_on: :page) { block } end PostsProcessor.project(q: "search_me", filter: "smth", page: 2) # => { q: "search_me", page: 2 } class PostsController < ApplicationController def index @filter_params = rubanok_scope # or @filter_params = rubanok_scope params.require(:filter), with: PostsProcessor # ... end end ``` -------------------------------- ### Basic Rubanok Usage in Rails Controller Source: https://github.com/palkan/rubanok/blob/master/README.md Demonstrates how to integrate Rubanok into a Rails controller to process data using a defined processor. It shows the transformation from traditional chained query methods to a more concise Rubanok-based approach. ```ruby class CourseSessionController < ApplicationController def index @sessions = rubanok_process( # pass input CourseSession.all, # pass params params, # provide a processor to use with: CourseSessionsProcessor ) end end ``` ```ruby class CourseSessionController < ApplicationController def index @sessions = rubanok_process(CourseSession.all) end end ``` -------------------------------- ### Release Gem and Push Tags Source: https://github.com/palkan/rubanok/blob/master/RELEASING.md This command uses the `gem-release` tool to publish the gem to RubyGems. It also includes pushing tags to GitHub, which is crucial for version tracking. ```shell gem release -t git push --tags ``` -------------------------------- ### Inferring Processors with rubanok_process Source: https://context7.com/palkan/rubanok/llms.txt Demonstrates how to use the `rubanok_process` helper in a controller to automatically infer and apply a processor class. The processor is inferred based on the controller name (e.g., `Admin::PostsController` infers `Admin::PostsProcessor`). You can override this inference by defining an `implicit_rubanok_class` method. ```ruby class Admin::PostsController < ApplicationController def index # Infers Admin::PostsProcessor @posts = rubanok_process(Post.all) end # Override inference logic def implicit_rubanok_class PostsProcessor end end ``` -------------------------------- ### Apply Transformations with Processor.call Source: https://context7.com/palkan/rubanok/llms.txt The main entry point for applying transformation rules to data. It accepts input data and a parameters hash, returning the transformed result or the original input if no rules match. ```ruby class PostsProcessor < Rubanok::Processor map :q do |q:| raw.where("title ILIKE ?", "%#{q}%") end map :status do |status:| raw.where(status: status) end end # Basic usage posts = PostsProcessor.call(Post.all, { q: "ruby", status: "published" }) # Without matching params, input is returned unchanged posts = PostsProcessor.call(Post.all, {}) # Can omit input for non-database transformations result = MyProcessor.call({ ids: [1, 2, 3] }) ``` -------------------------------- ### Rubanok Processor with 'match' for Conditional Transformations Source: https://github.com/palkan/rubanok/blob/master/README.md Demonstrates the use of the `match` method in a Rubanok processor for conditional transformations based on parameter values. It includes handling specific values ('course_id', 'type') and a default case, along with input validation. ```ruby class CourseSessionsProcessor < Rubanok::Processor SORT_ORDERS = %w[asc desc].freeze SORTABLE_FIELDS = %w[id name created_at].freeze match :sort_by, :sort do having "course_id", "desc" do raw.joins(:courses).order("courses.id desc nulls last") end having "course_id", "asc" do raw.joins(:courses).order("courses.id asc nulls first") end # Match any value for the second arg having "type" do |sort: "asc"| # Prevent SQL injections raise "Possible injection: #{sort}" unless SORT_ORDERS.include?(sort) raw.joins(:course_type).order("course_types.name #{sort}") end # Match any value default do |sort_by:, sort: "asc"| raise "Possible injection: #{sort}" unless SORT_ORDERS.include?(sort) raise "The field is not sortable: #{sort_by}" unless SORTABLE_FIELDS.include?(sort_by) raw.order(sort_by => sort) end end # strict matching; if Processor will not match parameter, it will raise Rubanok::UnexpectedInputError # You can handle it in controller, for example, with sending 422 Unprocessable Entity to client match :filter, fail_when_no_matches: true do having "active" do raw.active end having "finished" do raw.finished end end end ``` -------------------------------- ### Rubanok Processor with Nested Processors Source: https://github.com/palkan/rubanok/blob/master/README.md Demonstrates how to define nested processors within a Rubanok processor using the `.process` method. This is useful for handling nested parameters, such as filtering by status within a broader filter context. ```ruby class CourseSessionsProcessor < Rubanok::Processor process :filter do match :status do having "draft" do raw.where(draft: true) end having "deleted" do raw.where.not(deleted_at: nil) end end # You can also use .map or even .process here end end ``` -------------------------------- ### Controlling Rule Activation with `activate_on` and `activate_always` Source: https://github.com/palkan/rubanok/blob/master/README.md Explains how to control the activation of rules in Rubanok processors. `activate_always: true` ensures a rule is always applied, using default values if necessary. `activate_on` specifies a key that must be present for the rule to activate, making it optional rather than required. ```Ruby # Always apply the rule; use default values for keyword args map :page, :per_page, activate_always: true do |page: 1, per_page: 2| raw.page(page).per(per_page) end ``` ```Ruby # Only require `sort_by` to be preset to activate sorting rule match :sort_by, :sort, activate_on: :sort_by do # ... end ``` -------------------------------- ### Push Code to GitHub Source: https://github.com/palkan/rubanok/blob/master/RELEASING.md This command pushes the local changes, including the version bump, to the remote GitHub repository. It's essential for CI to pass before releasing. ```shell git push ``` -------------------------------- ### Extract Recognized Parameters with `Processor.project` Source: https://context7.com/palkan/rubanok/llms.txt The `Processor.project` class method filters a given hash of parameters, returning only those that the processor is configured to recognize. This is useful for cleaning up input or constructing URLs based on filter state. ```ruby class OrdersProcessor < Rubanok::Processor map :q do |q מד:| raw.search(q) end map :status do |status מד:| raw.where(status: status) end map :page, :per_page, activate_always: true do |page: 1, per_page: 20| raw.page(page).per(per_page) end end # Get only recognized params all_params = { q: "test", status: "pending", unrelated: "value", page: 2 } OrdersProcessor.project(all_params) # => { q: "test", status: "pending", page: 2 } # Works with string keys too OrdersProcessor.project({ "q" => "search", "foo" => "bar" }) # => { q: "search" } ``` -------------------------------- ### Rubanok Processor with Multiple Parameter Mapping Source: https://github.com/palkan/rubanok/blob/master/README.md Shows a Rubanok processor mapping multiple parameters, 'page' and 'per_page', to a `paginate` method. It includes a default value for 'per_page' to handle cases where the parameter is not provided. ```ruby class CourseSessionsProcessor < Rubanok::Processor DEFAULT_PAGE_SIZE = 25 map :page, :per_page do |page:, per_page: DEFAULT_PAGE_SIZE| raw.paginate(page: page, per_page: per_page) end end ``` -------------------------------- ### Processor.project - Extract recognized parameters Source: https://context7.com/palkan/rubanok/llms.txt The project class method filters a hash to return only the parameters recognized by the processor. ```APIDOC ## Processor.project ### Description Extracts only the parameters that the processor is configured to handle from a given input hash. ### Parameters - **params** (Hash) - Required - The input hash containing potential parameters. ### Response - **result** (Hash) - A filtered hash containing only recognized parameters. ``` -------------------------------- ### Rubanok Global Configuration Options Source: https://context7.com/palkan/rubanok/llms.txt Rubanok offers global configuration options to control the behavior of rules, such as ignoring empty parameter values and handling unmatched parameters. These global settings can be overridden on a per-rule basis within processor classes. ```ruby # config/initializers/rubanok.rb # Ignore empty parameter values by default (default: true) # When true, params like { q: "" } or { q: nil } won't activate rules Rubanok.ignore_empty_values = true # Raise error when match rule has no matching clause (default: false) # When true, unmatched values raise Rubanok::UnexpectedInputError Rubanok.fail_when_no_matches = false # Per-rule configuration overrides global settings class StrictProcessor < Rubanok::Processor # This rule activates even for empty strings map :required_field, ignore_empty_values: false do |required_field:| raw.where(field: required_field) end # This match raises on invalid values regardless of global setting match :type, fail_when_no_matches: true do having "a" do raw.type_a end having "b" do raw.type_b end end end ``` -------------------------------- ### Processor.call Source: https://context7.com/palkan/rubanok/llms.txt The main entry point for applying transformation rules defined in a processor class to a dataset. ```APIDOC ## Processor.call ### Description Applies transformation rules to input data (e.g., ActiveRecord relation) based on a provided parameters hash. ### Method POST (Conceptual) ### Endpoint Processor.call(input_data, params) ### Parameters #### Request Body - **input_data** (Object) - Optional - The data structure to be transformed (e.g., ActiveRecord relation). - **params** (Hash) - Required - The parameters used to trigger specific transformation rules. ### Request Example { "q": "ruby", "status": "published" } ### Response #### Success Response (200) - **result** (Object) - The transformed data structure. ``` -------------------------------- ### Define Value-Matching Rules with match Source: https://context7.com/palkan/rubanok/llms.txt The 'match' method creates rules based on specific parameter values. It supports 'having' clauses for specific combinations, 'default' fallbacks, and strict validation. ```ruby class ArticlesProcessor < Rubanok::Processor match :filter do having "published" do raw.where(published: true) end having "draft" do raw.where(published: false) end end match :sort_by, :sort, activate_on: :sort_by do having "created_at", "desc" do raw.order(created_at: :desc) end default do |sort_by:, sort: "asc"| raw.order(sort_by => sort) end end end ``` -------------------------------- ### Bump Gem Version and Commit Source: https://github.com/palkan/rubanok/blob/master/RELEASING.md This snippet shows how to commit a version bump for a gem. It involves updating the version number in the `version.rb` file and then committing this change. ```shell git commit -m "Bump 1.