1

I am trying to find a design pattern (or maybe an algorithm) which will help me write these rules in a cleaner way. Any suggestions?

def get_rules(user, value):
    if 500 <= value < 5000 and not user.address:
        return [REQUEST_ADDRESS]

    if value >= 5000:
        if not user.address and not user.phone:
            return [REQUEST_ADDRESS, REQUEST_PHONE]
        if user.address and not user.phone:
            return [REQUEST_PHONE]
        if not user.address and user.phone:
            return [REQUEST_ADDRESS]

    # Potentially ~20 more conditions here based on various attributes of user
    return [STATES.REQUEST_NONE]

Note: I am not looking for a rules engine since I don't want to complicate my code by adding "business friendly" DSL in python. Python itself is a simple language to write these rules.

Interesting read: http://martinfowler.com/bliki/RulesEngine.html (but I am still trying to stay away from a "framework" to do this for me).

Community
  • 1
  • 1
zengr
  • 38,346
  • 37
  • 130
  • 192
  • My bad, updated the snippet. `amount` is nothing, its just `value` which can be a variable based on which I need take a decision. – zengr Mar 31 '15 at 21:47
  • why not build a list and add to that list the values you need to request? That eliminates a lot of cases. – Thom Wiggers Mar 31 '15 at 21:47
  • Sure, I will do that. But that won't reduce the if-else checks. – zengr Mar 31 '15 at 21:49
  • 1
    sure it will, because you don't need to check for 'if a and not b, if not a and not b', instead you'll have 'if not a, if not b, if not c'. Instead of all these combinations of cases you'll have `n` cases. – Thom Wiggers Mar 31 '15 at 21:50
  • I see what you are suggesting, that looks much cleaner I was hoping for a solution probably using polymorphism to get rid of if-else. Maybe I am overthinking it. – zengr Mar 31 '15 at 21:55

4 Answers4

2

You can use a dict in this case:

resdict = {(False, False): [REQUEST_ADDRESS, REQUEST_PHONE],
           (True, False): [REQUEST_PHONE],
           (False, True): [REQUEST_ADDRESS]}
return resdict[(user.address, user.phone)]

You can also use a list comprehension:

return [req for req, haveit in zip([REQUEST_ADDRESS, REQUEST_PHONE], [user.address, user.phone]) if not haveit]

Or a simpler list append:

res = []
if not user.address:
    res.append(REQUEST_ADDRESS)
if not user.phone:
    res.append(REQUEST_PHONE)
TheBlackCat
  • 9,791
  • 3
  • 24
  • 31
2

You're checking lots of different combinations with your "if a and not b else check not a and b else check not a and not b" strategy to figure out what combination of requests you need to send.

Instead, only check what you're missing:

missing = []
if not user.phone:
    missing.append(REQUEST_PHONE)
if not user.address:
    missing.append(REQUEST_ADDRESS)

return missing or [REQUEST_NONE]
Thom Wiggers
  • 6,938
  • 1
  • 39
  • 65
0

If I understood the question right, you have a list of attributes for the user. If one is false a REQUEST value schould be added to the list. Then this could help:

# define all your combinations here:
mapping = {'address': REQUEST_ADDRESS, 'phone': REQUEST_PHONE, …)

return [value for key, value in mapping.items()
        if not getattr(user, key, None)]
Klaus D.
  • 13,874
  • 5
  • 41
  • 48
0

Looks like your "rules" boil down to this: Request values for fields that are not present as attributes in the object user. I will assume that the mapping of attributes to requests can be arbitrary; you can represent it as a dictionary mapping, e.g. like this:

rulemap = { 
      "address": REQUEST_ADDRESS, 
      "phone": REQUEST_PHONE, 
      # etc.
    }

You can then get a list of the requests to issue by checking which of the keys in rulemap are not present as attributes in the object user:

return [ rulemap[fld] for fld in rulemap.keys() if fld not in user.__dict__ ]
alexis
  • 48,685
  • 16
  • 101
  • 161