module Rooler class Rule < ActiveRecord::Base # A rule is a map between email templates and objects of a specific class. A rule checks those objects to dermine if the parent email # should be sent. A rule can check a specific object on demand, or check the entire class at a certain frequency. # # The conditions which determine if a rule is met or not is actually defined in the objects class. The rule stores an instance method # name, which it can call on a specific object (on demand), and a class method name to search the class for matching objects belongs_to :template has_many :deliveries scope :ready_to_be_checked, -> {where("last_checked_at IS NULL OR check_frequency IS NULL OR (last_checked_at + check_frequency*'1 second'::interval) < now()")} serialize :method_params validate :valid_klass_name validate :valid_klass_finder_method # processes this rule. Check entire class using class method. For each positive result add object to delivery queue. def process results = find_undelivered_by_klass.each {|result| add_delivery_to_queue(result)} self.update_column(:last_checked_at, Time.now) return results end def clear_non_applicable_deliveries self.deliveries.where(deliverable_id: no_longer_applicable_delivery_ids).destroy_all end private # sends klass_method to klass. If the saved rule contains any params, send those as well. def find_by_klass if self.method_params klass.send(self.klass_finder_method, self.method_params) else klass.send(self.klass_finder_method) end end def find_by_klass_ids results = find_by_klass if results.respond_to?(:id) results.pluck(:id) elsif results.respond_to?(:map) results.map(&:id) else raise "Cannot determine object ids" end end # Try to exclude any objects already delivered. If the result of the klass method is an active record relation, try to chain on a where clause. # otherwise iterate through as an array and use reject. def find_undelivered_by_klass results = find_by_klass if results.respond_to?(:where) results.where.not(id: already_delivered_ids) elsif results.respond_to?(:reject) results.reject {|o| already_delivered_ids.include?(o.id)} else raise "cannot determine which objects where already delivered" end end def already_delivered_ids self.deliveries.pluck(:deliverable_id) #just getting the deliverable id lets the finder class and the result class be different end def no_longer_applicable_delivery_ids already_delivered_ids - find_by_klass_ids end # create record in deliveries table for found objects (unless one already exists) def add_delivery_to_queue(object) if deliveries.create(deliverable: object) return object else return nil end end def klass @klass ||= self.klass_name.try(:constantize) rescue nil end def valid_klass_name unless klass errors.add(:klass_name, "Couldn't constantize class name") end end def valid_klass_finder_method unless klass.respond_to? self.klass_finder_method errors.add(:klass_finder_method, "Class finder method doesn't exist") end end end end