mirror of
https://github.com/we-promise/sure.git
synced 2026-04-08 23:04:49 +00:00
* Add full import/export support for rules with versioned JSON schema This commit implements comprehensive import/export functionality for rules, allowing users to back up and restore their rule definitions. Key features: - Export rules to both CSV and NDJSON formats with versioned schema (v1) - Import rules from CSV with full support for nested conditions and actions - UUID to name mapping for categories and merchants for portability - Support for compound conditions with sub-conditions - Comprehensive test coverage for export and import functionality - UI integration for rules import in the imports interface Technical details: - Extended Family::DataExporter to generate rules.csv and include rules in all.ndjson - Created RuleImport model following the existing Import STI pattern - Added migration for rule-specific columns in import_rows table - Implemented serialization helpers to map UUIDs to human-readable names - Added i18n support for the new import option - Included versioning in NDJSON export to support future schema evolution The implementation ensures rules can be safely exported from one family and imported into another, even when category/merchant IDs differ, by mapping between names and IDs during export/import. * Fix AR migration version * Mention support for rules export * Rabbit suggestion * Fix tests * Missed schema.rb * Fix sample CSV download for rule import * Fix parsing in Rules import * Fix tests * Rule import message i18n * Export tag names, not UUIDs * Make sure tags are created if needed at import * Avoid test errors when running in parallel --------- Co-authored-by: Claude <noreply@anthropic.com>
75 lines
1.7 KiB
Ruby
75 lines
1.7 KiB
Ruby
class Rule::Condition < ApplicationRecord
|
|
belongs_to :rule, touch: true, optional: -> { where.not(parent_id: nil) }
|
|
belongs_to :parent, class_name: "Rule::Condition", optional: true, inverse_of: :sub_conditions
|
|
|
|
has_many :sub_conditions, -> { order(:created_at, :id) }, class_name: "Rule::Condition", foreign_key: :parent_id, dependent: :destroy, inverse_of: :parent
|
|
|
|
validates :condition_type, presence: true
|
|
validates :operator, presence: true
|
|
validates :value, presence: true, unless: -> { compound? || operator == "is_null" }
|
|
|
|
accepts_nested_attributes_for :sub_conditions, allow_destroy: true
|
|
|
|
# We don't store rule_id on sub_conditions, so "walk up" to the parent rule
|
|
def rule
|
|
parent&.rule || super
|
|
end
|
|
|
|
def compound?
|
|
condition_type == "compound"
|
|
end
|
|
|
|
def apply(scope)
|
|
if compound?
|
|
build_compound_scope(scope)
|
|
else
|
|
filter.apply(scope, operator, value)
|
|
end
|
|
end
|
|
|
|
def prepare(scope)
|
|
if compound?
|
|
sub_conditions.reduce(scope) { |s, sub| sub.prepare(s) }
|
|
else
|
|
filter.prepare(scope)
|
|
end
|
|
end
|
|
|
|
def value_display
|
|
if value.present?
|
|
if options
|
|
options.find { |option| option.last == value }&.first
|
|
else
|
|
value
|
|
end
|
|
else
|
|
""
|
|
end
|
|
end
|
|
|
|
def options
|
|
filter.options
|
|
end
|
|
|
|
def operators
|
|
filter.operators
|
|
end
|
|
|
|
def filter
|
|
rule.registry.get_filter!(condition_type)
|
|
end
|
|
|
|
private
|
|
def build_compound_scope(scope)
|
|
if operator == "or"
|
|
combined_scope = sub_conditions
|
|
.map { |sub| sub.apply(scope) }
|
|
.reduce { |acc, s| acc.or(s) }
|
|
|
|
combined_scope || scope
|
|
else
|
|
sub_conditions.reduce(scope) { |s, sub| sub.apply(s) }
|
|
end
|
|
end
|
|
end
|