Trouble with getting correct context
Add modifier using API, override the modifier apply.

Would use 

m =  ob.modifiers.new(name="Foo", type='DECIMATE')
m.ratio = decimate


to add a modifier, and override the context of modifier apply operator, just as you have done for material link remove.

bpy.ops.object.modifier_apply({"object" : ob}, apply_as='DATA', modifier=m.name)


Questionable whether you want to add and apply modifier for each material slot, check your indenting

Alternatively can deselect all,  set as active and select the active object in loop.  To set the context to each individual object. Suggest is the Equivalent of you "simply applying script to default cube"

Eg for 2.8

bpy.ops.object.select_all(action='DESELECT')
ob.select_set(True)
context.view_layer.objects.active = ob


to give the loop object context.

However would recommend using as few operators as possible...  see Python performance with Blender operators

Note incorrect context is a common cause of problems

https://blender.stackexchange.com/search?q=context+incorrect