Merge branch 'where_object_method'; close #377

This commit is contained in:
Ben Atkins 2014-06-27 11:08:24 -04:00
commit a628c9c266
6 changed files with 128 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,21 @@ 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 = {})
raise ArgumentError, 'expected to receive a Hash' unless args.is_a?(Hash)
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 +205,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

@ -40,5 +40,49 @@ describe PaperTrail::Version do
end
end
end
describe "Class" do
describe :where_object do
it { PaperTrail::Version.should respond_to(:where_object) }
context "invalid arguments" do
it "should raise an error" do
expect { PaperTrail::Version.where_object(:foo) }.to raise_error(ArgumentError)
expect { PaperTrail::Version.where_object([]) }.to raise_error(ArgumentError)
end
end
context "valid arguments", :versioning => true do
let(:widget) { Widget.new }
let(:name) { Faker::Name.first_name }
let(:int) { rand(10) + 1 }
before do
widget.update_attributes!(:name => name, :an_integer => int)
widget.update_attributes!(:name => 'foobar', :an_integer => 100)
widget.update_attributes!(:name => Faker::Name.last_name, :an_integer => 15)
end
context "`serializer == YAML`" do
specify { PaperTrail.serializer == PaperTrail::Serializers::YAML }
it "should be able to locate versions according to their `object` contents" do
PaperTrail::Version.where_object(:name => name).should == [widget.versions[1]]
PaperTrail::Version.where_object(:an_integer => 100).should == [widget.versions[2]]
end
end
context "`serializer == JSON`" do
before { PaperTrail.serializer = PaperTrail::Serializers::JSON }
specify { PaperTrail.serializer == PaperTrail::Serializers::JSON }
it "should be able to locate versions according to their `object` contents" do
PaperTrail::Version.where_object(:name => name).should == [widget.versions[1]]
PaperTrail::Version.where_object(:an_integer => 100).should == [widget.versions[2]]
end
end
end
end
end
end
end

View File

@ -37,4 +37,39 @@ 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
matches = PaperTrail::Serializers::JSON.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, "Val 1")
assert matches.instance_of?(Arel::Nodes::Matches)
assert_equal matches.right, "%\"arg1\":\"Val 1\"%"
end
end
context "when value is `null`" do
should 'construct correct WHERE query' do
matches = PaperTrail::Serializers::JSON.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, nil)
assert matches.instance_of?(Arel::Nodes::Matches)
assert_equal matches.right, "%\"arg1\":null%"
end
end
context "when value is a number" do
should 'construct correct WHERE query' do
grouping = PaperTrail::Serializers::JSON.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, -3.5)
assert grouping.instance_of?(Arel::Nodes::Grouping)
matches = grouping.select { |v| v.instance_of?(Arel::Nodes::Matches) }
# Numeric arguments need to ensure that they match for only the number, not the beginning
# of a #, so it uses an Grouping matcher (See notes on `PaperTrail::Serializers::JSON`)
assert_equal matches.first.right, "%\"arg1\":-3.5,%"
assert_equal matches.last.right, "%\"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
matches = PaperTrail::Serializers::YAML.where_object_condition(
PaperTrail::Version.arel_table[:object], :arg1, "Val 1")
assert matches.instance_of?(Arel::Nodes::Matches)
assert_equal matches.right, "%\narg1: Val 1\n%"
end
end
end