Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Build scopes with NSPredicate instances, or a predicate format string…

… and args.
  • Loading branch information...
commit 41940e9d3a75b0118f34035e209894a59f236794 1 parent c152499
@alloy authored
View
94 app/predicate.rb
@@ -8,65 +8,67 @@ def inspect
end
module MotionData
- module Predicate
- def and(predicate)
- Compound.andPredicateWithSubpredicates([self, predicate])
- end
+ class Predicate < NSPredicate
+ module Ext
+ def and(predicate)
+ CompoundPredicate.andPredicateWithSubpredicates([self, predicate])
+ end
- def or(predicate)
- Compound.orPredicateWithSubpredicates([self, predicate])
+ def or(predicate)
+ CompoundPredicate.orPredicateWithSubpredicates([self, predicate])
+ end
end
- class Comparison < NSComparisonPredicate
- include Predicate
- end
+ include Predicate::Ext
+ end
- class Compound < NSCompoundPredicate
- include Predicate
- end
+ class ComparisonPredicate < NSComparisonPredicate
+ include Predicate::Ext
+ end
- class ComparableKeyPathExpression
- module Mixin
- def keyPath(keyPath)
- ComparableKeyPathExpression.new(keyPath)
- end
- alias_method :key, :keyPath
+ class CompoundPredicate < NSCompoundPredicate
+ include Predicate::Ext
+ end
+
+ class ComparableKeyPathExpression
+ module Mixin
+ def value(keyPath)
+ ComparableKeyPathExpression.new(keyPath)
end
+ end
- attr_reader :expression, :comparisonOptions
+ attr_reader :expression, :comparisonOptions
- def initialize(keyPath)
- @expression = NSExpression.expressionForKeyPath(keyPath.to_s)
- @comparisonOptions = 0
- end
+ def initialize(keyPath)
+ @expression = NSExpression.expressionForKeyPath(keyPath.to_s)
+ @comparisonOptions = 0
+ end
- def caseInsensitive; @comparisonOptions |= NSCaseInsensitivePredicateOption; self; end
- def diacriticInsensitive; @comparisonOptions |= NSDiacriticInsensitivePredicateOption; self; end
- def localeSensitive; @comparisonOptions |= NSLocaleSensitivePredicateOption; self; end
+ def caseInsensitive; @comparisonOptions |= NSCaseInsensitivePredicateOption; self; end
+ def diacriticInsensitive; @comparisonOptions |= NSDiacriticInsensitivePredicateOption; self; end
+ def localeSensitive; @comparisonOptions |= NSLocaleSensitivePredicateOption; self; end
- def <(value); comparisonWith(value, type:NSLessThanPredicateOperatorType); end
- def >(value); comparisonWith(value, type:NSGreaterThanPredicateOperatorType); end
- def <=(value); comparisonWith(value, type:NSLessThanOrEqualToPredicateOperatorType); end
- def >=(value); comparisonWith(value, type:NSGreaterThanOrEqualToPredicateOperatorType); end
- def !=(value); comparisonWith(value, type:NSNotEqualToPredicateOperatorType); end
- def ==(value); comparisonWith(value, type:NSEqualToPredicateOperatorType); end
+ def <(value); comparisonWith(value, type:NSLessThanPredicateOperatorType); end
+ def >(value); comparisonWith(value, type:NSGreaterThanPredicateOperatorType); end
+ def <=(value); comparisonWith(value, type:NSLessThanOrEqualToPredicateOperatorType); end
+ def >=(value); comparisonWith(value, type:NSGreaterThanOrEqualToPredicateOperatorType); end
+ def !=(value); comparisonWith(value, type:NSNotEqualToPredicateOperatorType); end
+ def ==(value); comparisonWith(value, type:NSEqualToPredicateOperatorType); end
- def between?(min, max); comparisonWith([min, max], type:NSBetweenPredicateOperatorType); end
- def include?(value); comparisonWith(value, type:NSContainsPredicateOperatorType); end
- def in?(value); comparisonWith(value, type:NSInPredicateOperatorType); end
- def beginsWith?(string); comparisonWith(string, type:NSBeginsWithPredicateOperatorType); end
- def endsWith?(string); comparisonWith(string, type:NSEndsWithPredicateOperatorType); end
+ def between?(min, max); comparisonWith([min, max], type:NSBetweenPredicateOperatorType); end
+ def include?(value); comparisonWith(value, type:NSContainsPredicateOperatorType); end
+ def in?(value); comparisonWith(value, type:NSInPredicateOperatorType); end
+ def beginsWith?(string); comparisonWith(string, type:NSBeginsWithPredicateOperatorType); end
+ def endsWith?(string); comparisonWith(string, type:NSEndsWithPredicateOperatorType); end
- private
+ private
- def comparisonWith(value, type:comparisonType)
- value = NSExpression.expressionForConstantValue(value)
- Comparison.predicateWithLeftExpression(@expression,
- rightExpression:value,
- modifier:NSDirectPredicateModifier,
- type:comparisonType,
- options:@comparisonOptions)
- end
+ def comparisonWith(value, type:comparisonType)
+ ComparisonPredicate.predicateWithLeftExpression(@expression,
+ rightExpression:NSExpression.expressionForConstantValue(value),
+ modifier:NSDirectPredicateModifier,
+ type:comparisonType,
+ options:@comparisonOptions)
end
end
end
View
23 app/scope.rb
@@ -1,7 +1,5 @@
module MotionData
class Scope
- include Predicate::ComparableKeyPathExpression::Mixin
-
attr_reader :target, :predicate, :sortDescriptors, :context
def initWithTarget(target)
@@ -15,19 +13,28 @@ def initWithTarget(target, predicate:predicate, sortDescriptors:sortDescriptors,
self
end
- # Add finder conditions as a hash of requirements, a Scope, or a NSPredicate.
+ # Add finder conditions as a hash of requirements, a Scope, a NSPredicate,
+ # or a predicate format string with an optional list of arguments.
#
# The conditions are added using `AND`.
- def where(conditions)
+ def where(conditions, *formatArguments)
predicate = case conditions
when Hash
- Predicate::Compound.andPredicateWithSubpredicates(conditions.map do |kp, value|
- keyPath(kp) == value
+ CompoundPredicate.andPredicateWithSubpredicates(conditions.map do |keyPath, value|
+ ComparableKeyPathExpression.new(keyPath) == value
end)
- when NSPredicate
- conditions
when Scope
conditions.predicate
+ when Predicate::Ext
+ # this is one of the MotionData predicate subclasses which mixes in the Ext module.
+ conditions
+ when NSPredicate
+ conditions.extend(Predicate::Ext)
+ conditions
+ when String
+ conditions = NSPredicate.predicateWithFormat(conditions, argumentArray:formatArguments)
+ conditions.extend(Predicate::Ext)
+ conditions
end
predicate = @predicate.and(predicate) if @predicate
View
51 spec/predicate_spec.rb
@@ -1,102 +1,101 @@
module MotionData
- describe Predicate::ComparableKeyPathExpression do
- extend Predicate::ComparableKeyPathExpression::Mixin
+ describe ComparablevaluePathExpression do
+ extend ComparablevaluePathExpression::Mixin
it "returns a expression for the left-hand side of a comparison" do
- key(:property).expression.keyPath.should == 'property'
- keyPath('property.subproperty').expression.keyPath.should == 'property.subproperty'
+ value(:property).expression.valuePath.should == 'property'
+ valuePath('property.subproperty').expression.valuePath.should == 'property.subproperty'
end
it "returns comparison predicates" do
{
- (key(:amount) < 42) => 'amount < 42',
- (key(:amount) > 42) => 'amount > 42',
- (key(:amount) <= 42) => 'amount <= 42',
- (key(:amount) >= 42) => 'amount >= 42',
- (key(:amount) != 42) => 'amount != 42',
- (key(:amount) == 42) => 'amount == 42',
+ (value(:amount) < 42) => 'amount < 42',
+ (value(:amount) > 42) => 'amount > 42',
+ (value(:amount) <= 42) => 'amount <= 42',
+ (value(:amount) >= 42) => 'amount >= 42',
+ (value(:amount) != 42) => 'amount != 42',
+ (value(:amount) == 42) => 'amount == 42',
}.each do |predicate, format|
predicate.predicateFormat.should == format
end
end
it "returns a `between` predicate" do
- predicate = key(:amount).between?(21, 42)
+ predicate = value(:amount).between?(21, 42)
predicate.predicateFormat.should == 'amount BETWEEN {21, 42}'
end
it "returns a `in` predicate" do
- predicate = key(:amount).in?([21, 42])
+ predicate = value(:amount).in?([21, 42])
predicate.predicateFormat.should == 'amount IN {21, 42}'
end
it "returns a `contains` predicate" do
- predicate = key(:amount).include?(42)
+ predicate = value(:amount).include?(42)
predicate.predicateFormat.should == 'amount CONTAINS 42'
end
it "returns a `in collection` predicate" do
- predicate = key(:amount).in?([21, 42])
+ predicate = value(:amount).in?([21, 42])
predicate.predicateFormat.should == 'amount IN {21, 42}'
end
it "returns a `begin's with` predicate" do
- predicate = key(:name).beginsWith?('bob')
+ predicate = value(:name).beginsWith?('bob')
predicate.predicateFormat.should == 'name BEGINSWITH "bob"'
end
it "returns a `end's with` predicate" do
- predicate = key(:name).endsWith?('bob')
+ predicate = value(:name).endsWith?('bob')
predicate.predicateFormat.should == 'name ENDSWITH "bob"'
end
-
it "by default returns that no comparison options should be used" do
- key(:property).comparisonOptions.should == 0
+ value(:property).comparisonOptions.should == 0
end
it "adds the option to perform a case-insensitive comparison" do
- expression = key(:name)
+ expression = value(:name)
expression.caseInsensitive.object_id.should == expression.object_id
expression.comparisonOptions.should == NSCaseInsensitivePredicateOption
end
it "adds the option to perform a diacritic-insensitive comparison" do
- expression = key(:name)
+ expression = value(:name)
expression.diacriticInsensitive.object_id.should == expression.object_id
expression.comparisonOptions.should == NSDiacriticInsensitivePredicateOption
end
it "adds the option to perform a locale-sensitive comparison" do
- expression = key(:name)
+ expression = value(:name)
expression.localeSensitive.object_id.should == expression.object_id
expression.comparisonOptions.should == NSLocaleSensitivePredicateOption
end
it "combines comparison options" do
- options = key(:name).caseInsensitive.diacriticInsensitive.localeSensitive.comparisonOptions
+ options = value(:name).caseInsensitive.diacriticInsensitive.localeSensitive.comparisonOptions
options.should == NSCaseInsensitivePredicateOption |
NSDiacriticInsensitivePredicateOption |
NSLocaleSensitivePredicateOption
end
it "adds the accumulated comparison options to the comparison" do
- predicate = key(:name).caseInsensitive.diacriticInsensitive == 'bob'
+ predicate = value(:name).caseInsensitive.diacriticInsensitive == 'bob'
predicate.predicateFormat.should == 'name ==[cd] "bob"'
end
end
describe Predicate do
- extend Predicate::ComparableKeyPathExpression::Mixin
+ extend ComparablevaluePathExpression::Mixin
it "returns a compound `AND` predicate" do
- predicate = (key(:amount) < 42).and(key(:amount) > 42).and(key(:amount) != 21)
+ predicate = ( value(:amount) < 42 ).and( value(:amount) > 42 ).and( value(:amount) != 21 )
predicate.predicateFormat.should == '(amount < 42 AND amount > 42) AND amount != 21'
end
it "returns a compound `OR` predicate" do
- predicate = (key(:amount) < 42).or(key(:amount) > 42).or(key(:amount) != 21)
+ predicate = ( value(:amount) < 42 ).or( value(:amount) > 42 ).or( value(:amount) != 21 )
predicate.predicateFormat.should == '(amount < 42 OR amount > 42) OR amount != 21'
end
end
View
51 spec/scope_spec.rb
@@ -1,8 +1,6 @@
module MotionData
describe Scope do
- extend Predicate::ComparableKeyPathExpression::Mixin
-
it "initializes with a class target and current context" do
scope = Scope.alloc.initWithTarget(Author)
scope.target.should == Author
@@ -11,41 +9,60 @@ module MotionData
end
describe Scope, "when building a new scope by applying finder options" do
- extend Predicate::ComparableKeyPathExpression::Mixin
+ extend ComparableKeyPathExpression::Mixin
it "from a hash" do
scope1 = Scope.alloc.initWithTarget(Author)
scope2 = scope1.where(:name => 'bob', :amount => 42)
- scope2.should.not == scope1
scope2.predicate.predicateFormat.should == 'name == "bob" AND amount == 42'
scope3 = scope2.where(:enabled => true)
- scope3.should.not == scope2
scope3.predicate.predicateFormat.should == '(name == "bob" AND amount == 42) AND (enabled == 1)'
end
- it "from a predicate" do
+ it "from a scope" do
+ scope1 = Scope.alloc.initWithTarget(Author)
+
+ scope2 = scope1.where(( value(:name).caseInsensitive != 'bob' ).or( value(:amount) > 42 ))
+ scope3 = scope1.where(( value(:enabled) == true ).and( value('job.title') != nil ))
+
+ scope4 = scope3.where(scope2)
+ scope4.predicate.predicateFormat.should == '(enabled == 1 AND job.title != nil) AND (name !=[c] "bob" OR amount > 42)'
+ end
+
+ it "from a MotionData type predicate" do
+ scope1 = Scope.alloc.initWithTarget(Author)
+
+ scope2 = scope1.where( value(:name).beginsWith?('bob').or( value(:amount) > 42 ))
+ scope2.predicate.predicateFormat.should == 'name BEGINSWITH "bob" OR amount > 42'
+
+ scope3 = scope2.where( value(:enabled) == true )
+ scope3.predicate.predicateFormat.should == '(name BEGINSWITH "bob" OR amount > 42) AND enabled == 1'
+ end
+
+ it "from a normal NSPredicate" do
scope1 = Scope.alloc.initWithTarget(Author)
- #scope2 = scope1.where(( value(:name) != 'bob' ).or( value(:amount) > 42 ))
- scope2 = scope1.where(( key(:name) != 'bob' ).or( key(:amount) > 42 ))
- scope2.should.not == scope1
+ scope2 = scope1.where(NSPredicate.predicateWithFormat('name != %@ OR amount > %@', argumentArray:['bob', 42]))
scope2.predicate.predicateFormat.should == 'name != "bob" OR amount > 42'
- scope3 = scope2.where( key(:enabled) == true )
- scope3.should.not == scope2
+ scope3 = scope2.where(NSPredicate.predicateWithFormat('enabled == 1'))
scope3.predicate.predicateFormat.should == '(name != "bob" OR amount > 42) AND enabled == 1'
end
- it "from a scope" do
+ it "from a predicate string" do
scope1 = Scope.alloc.initWithTarget(Author)
- scope2 = scope1.where(( key(:name).caseInsensitive != 'bob' ).or( key(:amount) > 42 ))
- scope3 = scope1.where(( key(:enabled) == true ).and( key(:title) != nil ))
- scope4 = scope3.where(scope2)
- scope4.should.not == scope3
- scope4.predicate.predicateFormat.should == '(enabled == 1 AND title != nil) AND (name !=[c] "bob" OR amount > 42)'
+ scope2 = scope1.where('name != %@ OR amount > %@', 'bob', 42)
+ scope2.predicate.predicateFormat.should == 'name != "bob" OR amount > 42'
+
+ scope3 = scope2.where('enabled == 1')
+ scope3.predicate.predicateFormat.should == '(name != "bob" OR amount > 42) AND enabled == 1'
+ end
+
+ it "does not modify the original scopes" do
+
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.