Skip to content

Commit

Permalink
Temp commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Exterm1nate committed Nov 28, 2024
1 parent 4d20d35 commit d5383ce
Show file tree
Hide file tree
Showing 44 changed files with 547 additions and 95 deletions.
2 changes: 1 addition & 1 deletion app/models/active_fields/field/date_time.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class DateTime < ActiveFields.config.field_base_class
validates :required, exclusion: [nil]
validates :max, comparison: { greater_than_or_equal_to: :min }, allow_nil: true, if: :min
validates :precision,
comparison: { greater_than_or_equal_to: 0, less_than_or_equal_to: Casters::DateTimeCaster::MAX_PRECISION },
comparison: { greater_than_or_equal_to: 0, less_than_or_equal_to: ActiveFields::MAX_DATETIME_PRECISION },
allow_nil: true

# If precision is set after attributes that depend on it, deserialization will work correctly,
Expand Down
2 changes: 1 addition & 1 deletion app/models/active_fields/field/date_time_array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class DateTimeArray < ActiveFields.config.field_base_class

validates :max, comparison: { greater_than_or_equal_to: :min }, allow_nil: true, if: :min
validates :precision,
comparison: { greater_than_or_equal_to: 0, less_than_or_equal_to: Casters::DateTimeCaster::MAX_PRECISION },
comparison: { greater_than_or_equal_to: 0, less_than_or_equal_to: ActiveFields::MAX_DATETIME_PRECISION },
allow_nil: true

# If precision is set after attributes that depend on it, deserialization will work correctly,
Expand Down
1 change: 1 addition & 0 deletions lib/active_fields.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require_relative "active_fields/version"
require_relative "active_fields/constants"
require_relative "active_fields/engine"

module ActiveFields
Expand Down
4 changes: 1 addition & 3 deletions lib/active_fields/casters/date_time_caster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
module ActiveFields
module Casters
class DateTimeCaster < BaseCaster
MAX_PRECISION = RUBY_VERSION >= "3.2" ? 9 : 6 # AR max precision is 6 for old Rubies

def serialize(value)
value = value.iso8601 if value.is_a?(Date)
casted_value = caster.serialize(value)
Expand All @@ -28,7 +26,7 @@ def caster
# Use maximum precision by default to prevent the caster from truncating useful time information
# before precision is applied later
def precision
[options[:precision], MAX_PRECISION].compact.min
[options[:precision], ActiveFields::MAX_DATETIME_PRECISION].compact.min
end

def apply_precision(value)
Expand Down
6 changes: 6 additions & 0 deletions lib/active_fields/constants.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module ActiveFields
# Ruby supports up to 9 fractional seconds digits, but most DBs (and PostgreSQL as well) support only 6. So we use 6.
MAX_DATETIME_PRECISION = 6
end
50 changes: 38 additions & 12 deletions lib/active_fields/finders/base_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ def search(operator:, value:)

private

def cte_name = "active_values_for_field"
def cte_name = ActiveFields.config.value_class.table_name

def active_values_cte
ActiveFields.config.value_class.with(cte_name => active_field.active_values).from(cte_name)
ActiveFields.config.value_class.with(cte_name => active_field.active_values)
end

def value_field_jsonb
Expand All @@ -41,23 +41,49 @@ def casted_value_field(to)
Arel::Nodes::NamedFunction.new("CAST", [value_field_text.as(to)])
end

def is(target, value)
Arel::Nodes::InfixOperation.new("IS", target, Arel::Nodes.build_quoted(value))
end

# rubocop:disable Naming/PredicateName
def is_not(target, value)
Arel::Nodes::InfixOperation.new("IS NOT", target, Arel::Nodes.build_quoted(value))
end
# rubocop:enable Naming/PredicateName

def value_jsonb_path_exists(jsonpath, vars = nil)
Arel::Nodes::NamedFunction.new(
"jsonb_path_exists",
[value_field_jsonb, *[jsonpath, vars&.to_json].compact.map { Arel::Nodes.build_quoted(_1) }],
)
end

def eq(target, value)
if value.is_a?(TrueClass) || value.is_a?(FalseClass) || value.is_a?(NilClass)
Arel::Nodes::InfixOperation.new("IS", target, Arel::Nodes.build_quoted(value))
else
target.eq(value)
end
end

def not_eq(target, value)
if value.is_a?(TrueClass) || value.is_a?(FalseClass) || value.is_a?(NilClass)
Arel::Nodes::InfixOperation.new("IS NOT", target, Arel::Nodes.build_quoted(value))
else
# Comparison with NULL always returns NULL.
# NOT NULL is always NULL as well.
# We expect them not to be equal to any provided value except TRUE, FALSE and NULL.
# So we should search for NULL values too.
target.not_eq(value).or(target.eq(nil))
end
end

def gt(target, value)
target.gt(value)
end

def gteq(target, value)
target.gteq(value)
end

def lt(target, value)
target.lt(value)
end

def lteq(target, value)
target.lteq(value)
end

def operator_not_found!(operator)
raise ArgumentError, "invalid search operator `#{operator.inspect}` for `#{self.class.name}`"
end
Expand Down
6 changes: 3 additions & 3 deletions lib/active_fields/finders/boolean_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ class BooleanFinder < BaseFinder
def search(operator:, value:)
value = Casters::BooleanCaster.new.deserialize(value)

case operator
case operator.to_s
when "=", "eq"
active_values_cte.where(is(casted_value_field("boolean"), value))
active_values_cte.where(eq(casted_value_field("boolean"), value))
when "!=", "not_eq"
active_values_cte.where(is_not(casted_value_field("boolean"), value))
active_values_cte.where(not_eq(casted_value_field("boolean"), value))
else
operator_not_found!(operator)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fields/finders/date_array_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def search(operator:, value:)
caster = Casters::DateCaster.new
value = caster.serialize(caster.deserialize(value))

case operator
case operator.to_s
when "include"
active_values_cte.where(
value_jsonb_path_exists("$[*] ? (@.date() == $value.date())", { value: value }),
Expand Down
18 changes: 9 additions & 9 deletions lib/active_fields/finders/date_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ class DateFinder < BaseFinder
def search(operator:, value:)
value = Casters::DateCaster.new.deserialize(value)

case operator
case operator.to_s
when "=", "eq"
active_values_cte.where(casted_value_field("date").eq(value))
active_values_cte.where(eq(casted_value_field("date"), value))
when "!=", "not_eq"
active_values_cte.where(casted_value_field("date").not_eq(value))
active_values_cte.where(not_eq(casted_value_field("date"), value))
when ">", "gt"
active_values_cte.where(casted_value_field("date").gt(value))
when "=>", "gte"
active_values_cte.where(casted_value_field("date").gteq(value))
active_values_cte.where(gt(casted_value_field("date"), value))
when ">=", "gteq", "gte"
active_values_cte.where(gteq(casted_value_field("date"), value))
when "<", "lt"
active_values_cte.where(casted_value_field("date").lt(value))
when "<=", "lte"
active_values_cte.where(casted_value_field("date").lteq(value))
active_values_cte.where(lt(casted_value_field("date"), value))
when "<=", "lteq", "lte"
active_values_cte.where(lteq(casted_value_field("date"), value))
else
operator_not_found!(operator)
end
Expand Down
5 changes: 3 additions & 2 deletions lib/active_fields/finders/date_time_array_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ module ActiveFields
module Finders
class DateTimeArrayFinder < BaseFinder
def search(operator:, value:)
caster = Casters::DateTimeCaster.new(precision: active_field.precision)
precision = [active_field.precision, ActiveFields::MAX_DATETIME_PRECISION].compact.min
caster = Casters::DateTimeCaster.new(precision: precision)
value = caster.serialize(caster.deserialize(value))

case operator
case operator.to_s
when "include"
active_values_cte.where(
value_jsonb_path_exists("$[*] ? (@.timestamp_tz() == $value.timestamp_tz())", { value: value }),
Expand Down
21 changes: 11 additions & 10 deletions lib/active_fields/finders/date_time_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ module ActiveFields
module Finders
class DateTimeFinder < BaseFinder
def search(operator:, value:)
value = Casters::DateTimeCaster.new.deserialize(value)
precision = [active_field.precision, ActiveFields::MAX_DATETIME_PRECISION].compact.min
value = Casters::DateTimeCaster.new(precision: precision).deserialize(value)

case operator
case operator.to_s
when "=", "eq"
active_values_cte.where(casted_value_field("timestamp").eq(value))
active_values_cte.where(eq(casted_value_field("timestamp"), value))
when "!=", "not_eq"
active_values_cte.where(casted_value_field("timestamp").not_eq(value))
active_values_cte.where(not_eq(casted_value_field("timestamp"), value))
when ">", "gt"
active_values_cte.where(casted_value_field("timestamp").gt(value))
when "=>", "gte"
active_values_cte.where(casted_value_field("timestamp").gteq(value))
active_values_cte.where(gt(casted_value_field("timestamp"), value))
when ">=", "gteq", "gte"
active_values_cte.where(gteq(casted_value_field("timestamp"), value))
when "<", "lt"
active_values_cte.where(casted_value_field("timestamp").lt(value))
when "<=", "lte"
active_values_cte.where(casted_value_field("timestamp").lteq(value))
active_values_cte.where(lt(casted_value_field("timestamp"), value))
when "<=", "lteq", "lte"
active_values_cte.where(lteq(casted_value_field("timestamp"), value))
else
operator_not_found!(operator)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fields/finders/decimal_array_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def search(operator:, value:)
caster = Casters::DecimalCaster.new(precision: active_field.precision)
value = caster.serialize(caster.deserialize(value))

case operator
case operator.to_s
when "include"
active_values_cte.where(
value_jsonb_path_exists("$[*] ? (@.number() == $value.number())", { value: value }),
Expand Down
20 changes: 10 additions & 10 deletions lib/active_fields/finders/decimal_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ module ActiveFields
module Finders
class DecimalFinder < BaseFinder
def search(operator:, value:)
value = Casters::DecimalCaster.new.deserialize(value)
value = Casters::DecimalCaster.new(precision: active_field.precision).deserialize(value)

case operator
case operator.to_s
when "=", "eq"
active_values_cte.where(casted_value_field("decimal").eq(value))
active_values_cte.where(eq(casted_value_field("decimal"), value))
when "!=", "not_eq"
active_values_cte.where(casted_value_field("decimal").not_eq(value))
active_values_cte.where(not_eq(casted_value_field("decimal"), value))
when ">", "gt"
active_values_cte.where(casted_value_field("decimal").gt(value))
when "=>", "gte"
active_values_cte.where(casted_value_field("decimal").gteq(value))
active_values_cte.where(gt(casted_value_field("decimal"), value))
when ">=", "gteq", "gte"
active_values_cte.where(gteq(casted_value_field("decimal"), value))
when "<", "lt"
active_values_cte.where(casted_value_field("decimal").lt(value))
when "<=", "lte"
active_values_cte.where(casted_value_field("decimal").lteq(value))
active_values_cte.where(lt(casted_value_field("decimal"), value))
when "<=", "lteq", "lte"
active_values_cte.where(lteq(casted_value_field("decimal"), value))
else
operator_not_found!(operator)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fields/finders/enum_array_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def search(operator:, value:)
caster = Casters::EnumCaster.new
value = caster.serialize(caster.deserialize(value))

case operator
case operator.to_s
when "include"
active_values_cte.where(
value_jsonb_path_exists("$[*] ? (@ == $value)", { value: value }),
Expand Down
6 changes: 3 additions & 3 deletions lib/active_fields/finders/enum_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ class EnumFinder < BaseFinder
def search(operator:, value:)
value = Casters::EnumCaster.new.deserialize(value)

case operator
case operator.to_s
when "=", "eq"
active_values_cte.where(casted_value_field("text").eq(value))
active_values_cte.where(eq(casted_value_field("text"), value))
when "!=", "not_eq"
active_values_cte.where(casted_value_field("text").not_eq(value))
active_values_cte.where(not_eq(casted_value_field("text"), value))
else
operator_not_found!(operator)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fields/finders/integer_array_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def search(operator:, value:)
caster = Casters::IntegerCaster.new
value = caster.serialize(caster.deserialize(value))

case operator
case operator.to_s
when "include"
active_values_cte.where(
value_jsonb_path_exists("$[*] ? (@ == $value)", { value: value }),
Expand Down
18 changes: 9 additions & 9 deletions lib/active_fields/finders/integer_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ class IntegerFinder < BaseFinder
def search(operator:, value:)
value = Casters::IntegerCaster.new.deserialize(value)

case operator
case operator.to_s
when "=", "eq"
active_values_cte.where(casted_value_field("integer").eq(value))
active_values_cte.where(eq(casted_value_field("bigint"), value))
when "!=", "not_eq"
active_values_cte.where(casted_value_field("integer").not_eq(value))
active_values_cte.where(not_eq(casted_value_field("bigint"), value))
when ">", "gt"
active_values_cte.where(casted_value_field("integer").gt(value))
when "=>", "gte"
active_values_cte.where(casted_value_field("integer").gteq(value))
active_values_cte.where(gt(casted_value_field("bigint"), value))
when ">=", "gteq", "gte"
active_values_cte.where(gteq(casted_value_field("bigint"), value))
when "<", "lt"
active_values_cte.where(casted_value_field("integer").lt(value))
when "<=", "lte"
active_values_cte.where(casted_value_field("integer").lteq(value))
active_values_cte.where(lt(casted_value_field("bigint"), value))
when "<=", "lteq", "lte"
active_values_cte.where(lteq(casted_value_field("bigint"), value))
else
operator_not_found!(operator)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_fields/finders/text_array_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def search(operator:, value:)
caster = Casters::TextCaster.new
value = caster.serialize(caster.deserialize(value))

case operator
case operator.to_s
when "include"
active_values_cte.where(
value_jsonb_path_exists("$[*] ? (@ == $value)", { value: value }),
Expand Down
20 changes: 15 additions & 5 deletions lib/active_fields/finders/text_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,29 @@ class TextFinder < BaseFinder
def search(operator:, value:)
value = Casters::TextCaster.new.deserialize(value)

case operator
case operator.to_s
when "=", "eq"
active_values_cte.where(casted_value_field("text").eq(value))
active_values_cte.where(eq(casted_value_field("text"), value))
when "!=", "not_eq"
active_values_cte.where(casted_value_field("text").not_eq(value))
active_values_cte.where(not_eq(casted_value_field("text"), value))
when "like"
active_values_cte.where(casted_value_field("text").matches(value, nil, true))
active_values_cte.where(like(casted_value_field("text"), value))
when "ilike"
active_values_cte.where(casted_value_field("text").matches(value, nil, false))
active_values_cte.where(ilike(casted_value_field("text"), value))
else
operator_not_found!(operator)
end
end

private

def like(target, value)
target.matches(value, nil, true)
end

def ilike(target, value)
target.matches(value, nil, false)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

<div>
<%= f.label :precision %>
<%= f.number_field :precision, min: 0, max: ActiveFields::Casters::DateTimeCaster::MAX_PRECISION, disabled: active_field.persisted? %>
<%= f.number_field :precision, min: 0, max: ActiveFields::MAX_DATETIME_PRECISION, disabled: active_field.persisted? %>
</div>

<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@

<div>
<%= f.label :precision %>
<%= f.number_field :precision, min: 0, max: ActiveFields::Casters::DateTimeCaster::MAX_PRECISION, disabled: active_field.persisted? %>
<%= f.number_field :precision, min: 0, max: ActiveFields::MAX_DATETIME_PRECISION, disabled: active_field.persisted? %>
</div>

<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

<div class="form-input">
<%= f.label :precision %>
<%= f.number_field :precision, min: 0, max: ActiveFields::Casters::DateTimeCaster::MAX_PRECISION, disabled: active_field.persisted? %>
<%= f.number_field :precision, min: 0, max: ActiveFields::MAX_DATETIME_PRECISION, disabled: active_field.persisted? %>
</div>

<div class="form-input">
Expand Down
Loading

0 comments on commit d5383ce

Please sign in to comment.