lib/maily_herald/context.rb in maily_herald-0.0.1 vs lib/maily_herald/context.rb in maily_herald-0.8.0
- old
+ new
@@ -1,79 +1,225 @@
module MailyHerald
- class Context
- class Drop < Liquid::Drop
- def initialize attributes, item
- @attributes = attributes
- @item = item
- end
- def has_key?(name)
- name = name.to_sym
+ # Abstraction layer for accessing collections of Entities and their attributes.
+ # Information provided by scope is used while sending {MailyHerald::Mailing mailings}.
+ #
+ # {Context} defines following:
+ #
+ # * Entity scope - +ActiveRecord::Relation+, list of Entities that will be returned
+ # by {Context}.
+ # * Entity model name - deducted automatically from scope.
+ # * Entity attributes - Defined as procs that can be evaluated for every item of
+ # the scope (single Entity).
+ #
+ # * Entity email - defined as proc or string/symbol (email method name).
+ #
+ class Context
- @attributes.has_key? name
- end
+ # Context Attributes drop definition for Liquid
+ class Drop < Liquid::Drop
+ def initialize attrs
+ @attrs = attrs
+ end
- def invoke_drop name
- name = name.to_sym
+ def has_key?(name)
+ name = name.to_s
- #@attributes[name].try(:call, @item)
- @attributes[name].call(@item)
- end
+ @attrs.has_key? name
+ end
- alias :[] :invoke_drop
- end
+ def invoke_drop name
+ name = name.to_s
- attr_accessor :entity
+ if @attrs.has_key? name
+ if @attrs[name].is_a? Hash
+ Drop.new(@attrs[name])
+ else
+ @attrs[name].call
+ end
+ else
+ nil
+ end
+ end
- def scope &block
- if block_given?
- @scope = block
- else
- @scope.call
- end
- end
+ alias :[] :invoke_drop
+ end
- def destination &block
- if block_given?
- @destination = block
- else
- @destination
- end
- end
+ class Attributes
+ def initialize block
+ @attrs = {}
+ @node = @parent_node = @attrs
+ @block = block
+ end
- def attribute name, &block
- name = name.to_sym
+ def setup entity = nil, subscription = nil
+ if entity
+ @attrs["subscription"] = Proc.new{ subscription } if subscription
+ instance_exec entity, &@block
+ else
+ instance_eval &@block
+ end
+ end
- @attributes ||= {}
- if block_given?
- @attributes[name] = block
- else
- @attributes[name]
- end
- end
+ def attribute_group name, &block
+ @parent_node = @node
+ @parent_node[name.to_s] ||= {}
+ @node = @parent_node[name.to_s]
+ yield
+ @node = @parent_node
+ end
- def attribute_names
- @attributes.keys
- end
+ def attribute name, &block
+ @node[name.to_s] = block
+ end
- def model
- @scope.call.table.engine
- end
+ def for_drop
+ @attrs
+ end
- def extract_attributes hash = @attributes, collect = false
- hash.map do |k, v|
- v.is_a?(Hash) ? extract_attributes(v, (k == "items_attributes")) : (collect ? v : nil)
- end.compact.flatten
- end
+ def method_missing(m, *args, &block)
+ true
+ end
+ end
- def each &block
- @scope.call.each do |item|
- drop = Drop.new(@attributes, item)
- block.call(item, drop)
- end
- end
+ # Friendly name of the {Context}.
+ #
+ # Displayed ie. in the Web UI.
+ attr_accessor :title
- def drop_for item
- Drop.new(@attributes, item)
- end
- end
+ # Identification name of the {Context}.
+ #
+ # This can be then used in {MailyHerald.context} method to fetch the {Context}.
+ #
+ # @see MailyHerald.context
+ attr_reader :name
+
+ attr_writer :destination
+
+ # Creates {Context} and sets its name.
+ def initialize name
+ @name = name
+ end
+
+ # Defines or returns Entity scope - collection of Entities.
+ #
+ # If block passed, it is saved as scope proc. Block has to return
+ # +ActiveRecord::Relation+ containing Entity objects that will belong to scope.
+ #
+ # If no block given, scope proc is called and Entity collection returned.
+ def scope &block
+ if block_given?
+ @scope = block
+ else
+ @scope.call
+ end
+ end
+
+ # Fetches the Entity model class based on scope.
+ def model
+ @model ||= @scope.call.klass
+ end
+
+ # Entity email address.
+ #
+ # Can be eitner +Proc+ or attribute name (string, symbol).
+ #
+ # If block passed, it is saved as destination proc. Block has to:
+ #
+ # * accept single Entity object,
+ # * return Entity email.
+ #
+ # If no block given, +destination+ attribute is returned (a string, symbol or proc).
+ def destination &block
+ if block_given?
+ @destination = block
+ else
+ @destination
+ end
+ end
+
+ # Returns Entity email attribute name only if it is not defined as a proc.
+ def destination_attribute
+ @destination unless @destination.respond_to?(:call)
+ end
+
+ # Fetches Entity's email address based on {Context} destination definition.
+ def destination_for entity
+ destination_attribute ? entity.send(@destination) : @destination.call(entity)
+ end
+
+ # Simply filter Entity scope by email.
+ #
+ # If destination is provided in form of Entity attribute name (not the proc),
+ # this method creates the scope filtered by `query` email using SQL LIKE.
+ #
+ # @param query [String] email address which is being searched.
+ # @return [ActiveRecord::Relation] collection filtered by email address
+ def scope_like query
+ if destination_attribute
+ scope.where("#{model.table_name}.#{destination_attribute} LIKE (?)", "%#{query}%")
+ end
+ end
+
+ # Returns Entity collection scope with joined {MailyHerald::Subscription}.
+ #
+ # @param list [List, Fixnum, String] {MailyHerald::List} reference
+ # @param mode [:inner, :outer] SQL JOIN mode
+ def scope_with_subscription list, mode = :inner
+ list_id = case list
+ when List
+ list.id
+ when Fixnum
+ list
+ when String
+ list.to_i
+ else
+ raise ArgumentError
+ end
+
+ join_mode = case mode
+ when :outer
+ "LEFT OUTER JOIN"
+ else
+ "INNER JOIN"
+ end
+
+ subscription_fields_select = Subscription.columns.collect{|c| "#{Subscription.table_name}.#{c.name} AS maily_subscription_#{c.name}"}.join(", ")
+
+ scope.select("#{model.table_name}.*, #{subscription_fields_select}").joins(
+ "#{join_mode} #{Subscription.table_name} ON #{Subscription.table_name}.entity_id = #{model.table_name}.id AND #{Subscription.table_name}.entity_type = '#{model.base_class.to_s}' AND #{Subscription.table_name}.list_id = '#{list_id}'"
+ )
+ end
+
+ # Sepcify or return {Context} attributes.
+ #
+ # Defines Entity attributes that can be accessed using this Context.
+ # Attributes defined this way are then accesible in Liquid templates
+ # in Generic Mailer ({MailyHerald::Mailer#generic}).
+ #
+ # If block passed, it is used to create Context Attributes.
+ #
+ # If no block given, current attributes are returned.
+ def attributes &block
+ if block_given?
+ @attributes = Attributes.new block
+ else
+ @attributes
+ end
+ end
+
+ # Obtains {Context} attributes in a form of (nested) +Hash+ which
+ # values are procs each returning single Entity attribute value.
+ def attributes_list
+ attributes = @attributes.dup
+ attributes.setup
+ attributes.for_drop
+ end
+
+ # Returns Liquid drop created from Context attributes.
+ def drop_for entity, subscription
+ attributes = @attributes.dup
+ attributes.setup entity, subscription
+ Drop.new(attributes.for_drop)
+ end
+ end
end