Skip to content

Commit

Permalink
feat(item_equality): add the ability to compare item objects
Browse files Browse the repository at this point in the history
  • Loading branch information
jimtng committed Apr 15, 2022
1 parent 3e1d424 commit 0f4608b
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 6 deletions.
@@ -1,6 +1,6 @@
---
layout: default
title: Type Comparisons
title: Comparisons
nav_order: 12
has_children: false
parent: Misc
Expand All @@ -9,7 +9,36 @@ grand_parent: Usage

# Item and State Type Comparisons

Some OpenHAB item types can accept different command types. For example, a Dimmer item can accept a command with an `OnOffType`, `IncreaseDecreaseType` or a `PercentType`. However, ultimately an item only stores its state in its native type, e.g. a Dimmer item's native type is PercentType.
Comparison of two items implicitly compares their states. For example:

```ruby
if Item1 == Item2
logger.info('The state of Item1 is the same as the state of Item2')
end
```

Comparing the actual items can be achieved using `#item` method of either operand:

```ruby
Item1.item == items['Item1'].item # This would always return true
Item1.item == Item2.item # This would always return false

# When Item1, Item2 and Item3 all have the same state:
[Item1, Item2].include?(Item3.item) # => false - Check for the existence of Item3 inside the array
[Item1, Item2].include?(Item3) # => true - State check
```

Using `#item` is not necessary for hash keys because Hash keys are indexed by their hash codes.

```ruby
# When Item1 and Item2 contain the same state:
hash = { Item1 => 'value1', Item2 => 'value2' }
hash[Item2] # => 'value2' this will correctly match Item2's value in the hash
```

Some OpenHAB item types can accept different command types. For example, a Dimmer item can accept a command
with an `OnOffType`, `IncreaseDecreaseType` or a `PercentType`. However, ultimately an item only stores its
state in its native type, e.g. a Dimmer item's native type is PercentType.

## Loose Type Comparisons

Expand Down
73 changes: 73 additions & 0 deletions features/items.feature
Expand Up @@ -311,3 +311,76 @@ Feature: items
And item 'MySwitch' state is changed to 'ON'
Then It should log "Label: bar" within 5 seconds

Scenario: Item objects can be compared in a case statement
Given a rule:
"""
logger.info case SwitchTwo.item
when DimmerTest then 'Matched DimmerTest'
when SwitchTest then 'Matched SwitchTest'
when SwitchTwo then 'Matched SwitchTwo'
end
"""
When item 'DimmerTest' state is changed to '0'
And I deploy the rule
Then It should log "Matched SwitchTwo" within 5 seconds

Scenario: Items matches array#include?
Given a rule:
"""
logger.info("SwitchTwo in array? #{[DimmerTest, SwitchTwo].include?(SwitchTwo.item)}")
logger.info("DimmerTest in array? #{[SwitchTwo, SwitchTest].include?(DimmerTest.item)}")
logger.info("SwitchTest in array? #{[DimmerTest, SwitchTwo].include?(SwitchTest.item)}")
"""
When item 'DimmerTest' state is changed to '0'
And I deploy the rule
Then It should log "SwitchTwo in array? true" within 5 seconds
And It should log "DimmerTest in array? false" within 5 seconds
And It should log "SwitchTest in array? false" within 5 seconds

Scenario: Items matches hash#key?
Given a rule:
"""
logger.info("SwitchTwo in hash? #{{DimmerTest => true, SwitchTwo => true}.key?(SwitchTwo)}")
logger.info("SwitchTwo.item in hash? #{{DimmerTest => true, SwitchTwo => true}.key?(SwitchTwo.item)}")
logger.info("DimmerTest in hash? #{{SwitchTwo => true, SwitchTest => true}.key?(DimmerTest)}")
logger.info("SwitchTest in hash? #{{DimmerTest => true, SwitchTwo => true}.key?(SwitchTest)}")
"""
When item 'DimmerTest' state is changed to '0'
And I deploy the rule
Then It should log "SwitchTwo in hash? true" within 5 seconds
And It should log "SwitchTwo.item in hash? true" within 5 seconds
And It should log "DimmerTest in hash? false" within 5 seconds
And It should log "SwitchTest in hash? false" within 5 seconds

Scenario: Items matches hash#value?
Given a rule:
"""
logger.info("SwitchTwo in value? #{{a: DimmerTest, b: SwitchTwo}.value?(SwitchTwo.item)}")
logger.info("DimmerTest in value? #{{a: SwitchTwo, b: SwitchTest}.value?(DimmerTest.item)}")
logger.info("SwitchTest in value? #{{a: DimmerTest, b: SwitchTwo}.value?(SwitchTest.item)}")
"""
When item 'DimmerTest' state is changed to '0'
And I deploy the rule
Then It should log "SwitchTwo in value? true" within 5 seconds
And It should log "DimmerTest in value? false" within 5 seconds
And It should log "SwitchTest in value? false" within 5 seconds

Scenario: Direct comparison of Items
Given a rule:
"""
logger.info("State comparison: #{SwitchTwo == SwitchTest}")
logger.info("Different Item objects comparison1: #{SwitchTwo.item == SwitchTest.item}")
logger.info("Different Item objects comparison2: #{SwitchTwo.item == SwitchTest}")
logger.info("Same Item object comparison 1: #{SwitchTwo.item == items['SwitchTwo'].item}")
logger.info("Same Item object comparison 2: #{SwitchTwo == items['SwitchTwo'].item}")
logger.info("Same Item object comparison 3: #{SwitchTwo.item == items['SwitchTwo']}")
"""
When I deploy the rule
Then It should log "State comparison: true" within 5 seconds
And It should log "Different Item objects comparison1: false" within 5 seconds
And It should log "Different Item objects comparison2: false" within 5 seconds
And It should log "Same Item object comparison 1: true" within 5 seconds
And It should log "Same Item object comparison 2: true" within 5 seconds
And It should log "Same Item object comparison 3: true" within 5 seconds


30 changes: 27 additions & 3 deletions lib/openhab/dsl/items/generic_item.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true

require 'openhab/dsl/items/metadata'
require 'openhab/dsl/items/persistence'
require 'openhab/dsl/items/semantics'
require 'delegate'
require 'forwardable'

require_relative 'metadata'
require_relative 'persistence'
require_relative 'semantics'

require_relative 'item_equality'

Expand Down Expand Up @@ -170,6 +173,17 @@ def eql?(other)
other.instance_of?(self.class) && hash == other.hash
end

#
# A method to indicate that item comparison is requested instead of state comparison
#
# Example: Item1.item == items['Item1'].item should return true
#
# See ItemEquality#==
#
def item
@item ||= GenericItemObject.new(self)
end

# @!method null?
# Check if the item state == +NULL+
# @return [Boolean]
Expand Down Expand Up @@ -201,5 +215,15 @@ def format_type_pre(command)
end
end
end

# A helper class to flag that item comparison is wanted instead of state comparison
# It is used by ItemEquality#===
class GenericItemObject < SimpleDelegator
extend Forwardable
include Log
include OpenHAB::DSL::Items::ItemEquality

def_delegator :__getobj__, :instance_of? # instance_of? is used by GenericItem#eql? to check for item equality
end
end
end
17 changes: 16 additions & 1 deletion lib/openhab/dsl/items/item_equality.rb
Expand Up @@ -22,14 +22,29 @@ module ItemEquality
# @return [Boolean]
#
def ==(other)
logger.trace("(#{self.class}) #{self} == #{other} (#{other.class})")
logger.trace("ItemEquality#== (#{self.class}) #{self} == #{other} (#{other.class})")
return eql?(other) if generic_item_object?(other)
return true if equal?(other) || eql?(other)
return true if !state? && other.nil?

return raw_state == other.raw_state if other.is_a?(GenericItem)

state == other
end

private

#
# Determines if an object equality check is required, based on whether
# either operand is a GenericItemObject
#
# @param [Object] other
#
# @return [Boolean]
#
def generic_item_object?(other)
other.is_a?(OpenHAB::DSL::GenericItemObject) || is_a?(OpenHAB::DSL::GenericItemObject)
end
end
end
end
Expand Down

0 comments on commit 0f4608b

Please sign in to comment.