Add where_object query to PaperTrail::Version.

This commit is contained in:
Jacob Evelyn 2014-06-07 10:42:17 -04:00
parent 3a237d1bd1
commit 703ec9ae88
7 changed files with 112 additions and 1 deletions

View File

@ -12,6 +12,25 @@ module PaperTrail
def dump(object)
ActiveSupport::JSON.encode object
end
# Returns a SQL condition to be used to match the given field and value in
# the serialized object.
def where_object_condition(arel_field, field, value)
# Convert to JSON to handle strings and nulls correctly.
json_value = value.to_json
# If the value is a number, we need to ensure that we find the next
# character too, which is either `,` or `}`, to ensure that searching
# for the value 12 doesn't yield false positives when the value is
# 123.
if value.is_a? Numeric
arel_field.matches("%\"#{field}\":#{json_value},%").
or(
arel_field.matches("%\"#{field}\":#{json_value}}%"))
else
arel_field.matches("%\"#{field}\":#{json_value}%")
end
end
end
end
end

View File

@ -12,6 +12,12 @@ module PaperTrail
def dump(object)
::YAML.dump object
end
# Returns a SQL condition to be used to match the given field and value in
# the serialized object.
def where_object_condition(arel_field, field, value)
arel_field.matches("%\n#{field}: #{value}\n%")
end
end
end
end

View File

@ -68,6 +68,20 @@ module PaperTrail
end
end
# Performs an attribute search on the serialized object by invoking the
# identically-named method in the serializer being used.
def where_object(**args)
arel_field = arel_table[:object]
where_conditions = args.map do |field, value|
PaperTrail.serializer.where_object_condition(arel_field, field, value)
end.reduce do |condition1, condition2|
condition1.and(condition2)
end
where(where_conditions)
end
def primary_key_is_int?
@primary_key_is_int ||= columns_hash[primary_key].type == :integer
rescue
@ -190,7 +204,7 @@ module PaperTrail
def index
table = self.class.arel_table unless @index
@index ||=
@index ||=
if self.class.primary_key_is_int?
sibling_versions.select(table[self.class.primary_key]).order(table[self.class.primary_key].asc).index(self)
else

View File

@ -11,6 +11,7 @@ end
require File.expand_path("../dummy/config/environment.rb", __FILE__)
require "rails/test_help"
require 'minitest/mock'
require 'shoulda'
require 'ffaker'
require 'database_cleaner' if using_mysql?

View File

@ -37,4 +37,37 @@ class JSONTest < ActiveSupport::TestCase
end
end
context '`where_object` class method' do
context "when value is a string" do
should 'construct correct WHERE query' do
sql = PaperTrail::Serializers::JSON.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, "Val 1").
to_sql
assert sql.include?("LIKE '%\"arg1\":\"Val 1\"%'")
end
end
context "when value is `null`" do
should 'construct correct WHERE query' do
sql = PaperTrail::Serializers::JSON.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, nil).
to_sql
assert sql.include?("LIKE '%\"arg1\":null%'")
end
end
context "when value is a number" do
should 'construct correct WHERE query' do
sql = PaperTrail::Serializers::JSON.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, -3.5).
to_sql
assert_equal sql,
"(\"versions\".\"object\" LIKE '%\"arg1\":-3.5,%' OR "\
"\"versions\".\"object\" LIKE '%\"arg1\":-3.5}%')"
end
end
end
end

View File

@ -37,4 +37,12 @@ class YamlTest < ActiveSupport::TestCase
end
end
context '`where_object` class method' do
should 'construct correct WHERE query' do
sql = PaperTrail::Serializers::YAML.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, "Val 1").
to_sql
assert sql.include?("LIKE '%\narg1: Val 1\n%'")
end
end
end

View File

@ -98,4 +98,34 @@ class PaperTrail::VersionTest < ActiveSupport::TestCase
end
end
end
context "PaperTrail::Version.where_object" do
should "call `where_object` on the serializer" do
# Create some args to fake-query on.
args = { a: 1, b: "2", c: false, d: nil }
arel_field = PaperTrail::Version.arel_table[:object]
# Create a dummy value for us to return for each condition that can be
# chained together with other conditions with Arel's `and`.
chainable_dummy = arel_field.matches("")
# Mock a serializer to expect to receive `where_object_condition` with the
# correct args.
serializer = MiniTest::Mock.new
serializer.expect :where_object_condition, chainable_dummy, [arel_field, :a, 1]
serializer.expect :where_object_condition, chainable_dummy, [arel_field, :b, "2"]
serializer.expect :where_object_condition, chainable_dummy, [arel_field, :c, false]
serializer.expect :where_object_condition, chainable_dummy, [arel_field, :d, nil]
# Stub out PaperTrail.serializer to return our mock, and then make the
# query call.
PaperTrail.stub :serializer, serializer do
PaperTrail::Version.where_object(**args)
end
# Verify that our serializer mock received the correct
# `where_object_condition` calls.
assert serializer.verify
end
end
end