Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add where_object query to PaperTrail::Version. #380

Merged
merged 1 commit into from
Jun 27, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/paper_trail/serializers/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ def load(string)
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
6 changes: 6 additions & 0 deletions lib/paper_trail/serializers/yaml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ def load(string)
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
16 changes: 15 additions & 1 deletion lib/paper_trail/version_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ def timestamp_sort_order(direction = 'asc')
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
Expand Down Expand Up @@ -190,7 +204,7 @@ def previous

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
Expand Down
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def using_mysql?

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?
Expand Down
33 changes: 33 additions & 0 deletions test/unit/serializers/json_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions test/unit/serializers/yaml_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 30 additions & 0 deletions test/unit/version_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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