courier 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class Courier::Config
4
+ attr_accessor :services_hash, :templates_hash, :services_order
5
+
6
+ def initialize
7
+ self.services_order=[]
8
+ self.services_hash={}
9
+ self.templates_hash={}
10
+ end
11
+
12
+ def services *services
13
+ return services_order if services.empty?
14
+ raise 'Список сервисов уже определен' unless services_order.empty?
15
+
16
+ self.services_order = services.map { |s|
17
+ service = class_of_service(s).new
18
+ services_hash[service.name] = service
19
+ }
20
+ end
21
+
22
+ def get_service name
23
+ name=name.to_sym
24
+ services_hash[name] or raise "No such service '#{name}'"
25
+ end
26
+
27
+ def template name, *sets
28
+ raise "Values (#{sets.count}) and services counts (#{services_order.count}) not much" unless services_order.count==sets.count
29
+ template = Courier::Template::Base.new(:name=>name)
30
+ raise "Such template is already defined #{name}" if templates_hash.has_key? template.name
31
+ sets.each_with_index do |val, index|
32
+ service = services_order[index] or "Too many values (#{index}), no such services"
33
+ template.set(service, val)
34
+ end
35
+
36
+ templates_hash[template.name] = template
37
+ end
38
+
39
+ def get_template key
40
+ templates_hash[key.to_sym] or raise "No such template '#{key}'"
41
+ end
42
+
43
+ def class_of_service(name)
44
+ if name.is_a? Symbol
45
+ "Courier::Service::#{name.to_s.classify}".constantize
46
+ else
47
+ name
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ module Courier
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ # Моделька в помощь тем сервисам, что хотят иметь сохраненные сообщения для последующей доставки
5
+
6
+ class Courier::Message < ActiveRecord::Base
7
+ set_table_name 'courier_messages'
8
+
9
+ belongs_to :owner, :polymorphic=>true
10
+ belongs_to :service #, :polymorphic=>true
11
+ belongs_to :template #, :polymorphic=>true
12
+
13
+ serialize :options, Hash
14
+
15
+ scope :fresh, where(:state=>:fresh)
16
+
17
+ validates_presence_of :owner, :service, :template
18
+
19
+ state_machine :state, :initial => :fresh do
20
+ state :fresh
21
+ state :delivered
22
+ event :set_delivered do
23
+ transition :fresh => :delivered
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ # # Courier::Sender (extenstion для AR-модели пользователя-отправителя)
5
+
6
+ # Подключается через `acts_as_sender` дает модели метод `message(key, options)`. Пример вызова:
7
+
8
+ # @@@
9
+ # user.notify :comments_in_my_plan, :comments=>@comments, :level=>:success
10
+ # user.notify_failure :cant_import_plans, :provider=>:facebook
11
+ # @@@
12
+
13
+
14
+ module Courier::Owner
15
+ def has_courier
16
+ has_one :courier, :as => :owner, :dependent => :destroy, :class_name=>'Courier::OwnerSetting'
17
+ include InstanceMethods
18
+
19
+ after_create do
20
+ create_courier
21
+ end
22
+ end
23
+
24
+ module InstanceMethods
25
+ def message(template_key, args={})
26
+ template = Courier.template(template_key)
27
+ Courier.config.services_order.each do |service|
28
+ service.message(self, template, args) if courier.enabled?(template, service, args)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Индивидуальные настройки для каждого пользователя. Матрица пересечения сервис-шаблон для каждого владельца.
4
+
5
+
6
+ class Courier::OwnerSetting < ActiveRecord::Base
7
+ set_table_name 'courier_owner_setting'
8
+
9
+ belongs_to :owner, :polymorphic=>true
10
+
11
+ serialize :settings, Hash
12
+
13
+ before_validation do
14
+ self.settings||={}
15
+ end
16
+
17
+ validates_presence_of :owner
18
+ validates_uniqueness_of :owner_id, :scope=>:owner_type
19
+
20
+ def settings_of_template(template)
21
+ template = Courier.template(template) if template.is_a? Symbol
22
+ settings[template.name]||={}
23
+ end
24
+
25
+ def set(template, service, val=nil)
26
+ service = Courier.service(service) if service.is_a? Symbol
27
+ raise 'Cant use value as argument when block given' if block_given? and val
28
+ settings_of_template(template)[service.name] = block_given? ? yield : val
29
+ end
30
+
31
+ def get(template, service)
32
+ service = Courier.service(service) if service.is_a? Symbol
33
+ template = Courier.template(template) if template.is_a? Symbol
34
+ val = settings_of_template(template)[service.name]
35
+ val || template.get(service)
36
+ end
37
+
38
+ def enabled?(template, service, args={})
39
+ get(template, service)==:on
40
+ end
41
+
42
+ def disabled?(template, service, args={})
43
+ get(template, service)==:off
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class Courier::Service::ActiveMailer < Courier::Service::Base
4
+ end
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Модель класса осуществляющего способ доставки.
4
+
5
+ # * name - уникальное название (littlesms, actionmailer)
6
+ # * type (класс)
7
+
8
+ # ## Например:
9
+
10
+ # * Courier::Service::SMS::LittleSMS
11
+ # * Courier::Service::Email::ActionMailer
12
+ # * Courier::Service::Email::MailChimp
13
+ # * Courier::Service::Twitter::Grackle
14
+ # * Courier::Service::Flash::GritterNotice
15
+
16
+ # доставщики подключаются через команду
17
+ # Courier.register_service(SMS::LittleSMS, Email::ActionMailer,.. )
18
+
19
+ require 'ostruct'
20
+
21
+ class Courier::Service::Base
22
+ class << self
23
+ def inherited(subclass)
24
+ subclass.instance_variable_set('@config', OpenStruct.new)
25
+ super
26
+ end
27
+
28
+ def configure
29
+ yield @config
30
+ end
31
+
32
+ def config
33
+ @config
34
+ end
35
+ end
36
+
37
+ def check_args owner, template, args
38
+ args[:owner] ||=owner
39
+ args[:text] ||=template.get_text(args)
40
+ args[:service]||=self
41
+ end
42
+
43
+ def message(owner, template, args)
44
+ check_args owner, template, args
45
+ courier_messages.create! :owner=>owner, :template=>template, :options=>args
46
+ end
47
+
48
+ def name
49
+ self.class.name.demodulize.underscore.to_sym
50
+ end
51
+
52
+ def deliver!
53
+ raise 'inherit my class and implement me'
54
+ end
55
+ end
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class Courier::Service::Facebook < Courier::Service::Base
4
+ def initialize
5
+ raise "No Koala defined. Add gem 'koala' to your Gemfile. " unless defined? Koala
6
+ super
7
+ end
8
+
9
+ def check_args owner, template, args
10
+ args[:to]|='me'
11
+ args[:attachment]||={}
12
+ super
13
+ end
14
+
15
+ def deliver!
16
+ cache={}
17
+ courier_messages.fresh.each do |message|
18
+ message.owner.respond_to?(:facebook_token) or
19
+ raise "method facebook_token is not defined in your owner's model #{owner.class}"
20
+ token = message.owner.facebook_token or raise "owner's facebook_token is empty"
21
+ graph = cache[token] ||= Koala::Facebook::GraphAPI.new(token)
22
+ graph.put_wall_post(message.options[:text], message.options[:attachment], message.options[:to]) and
23
+ message.set_delivered
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class Courier::Service::GritterNotice < Courier::Service::Base
4
+ def initialize
5
+ raise "No GritterNotices. Add gem 'gritter_notices' to Gemfile." unless defined? GritterNotices
6
+ super
7
+ end
8
+
9
+ def message(owner, template, options)
10
+ options[:scope]=[:courier,:messages,:gritter_notice] unless options[:scope]
11
+ options[:text]=I18n::translate(name, options )
12
+ owner.gritter_notice template.name, options
13
+ end
14
+
15
+ def deliver!
16
+ # Nothng to do, it's realtime delivered
17
+ end
18
+ end
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ class Courier::Template::Base
3
+
4
+ attr_accessor :name, :defaults
5
+
6
+ def initialize(args)
7
+ self.name = args[:name].to_sym or raise 'no template name defined'
8
+ self.defaults={}
9
+ end
10
+
11
+ def get_text(args)
12
+ args[:scope]=[:courier,:messages,args[:service].name] unless args[:scope]
13
+ args[:cascade]=true unless args.has_key? :cascade
14
+ I18n::translate(name, args )
15
+ end
16
+
17
+ def get(service)
18
+ service = Courier.service(service) if service.is_a?(Symbol)
19
+ name = service.name.to_sym
20
+ raise "Not defined default value for #{service} in template #{self}" unless defaults.has_key? name
21
+ defaults[name]
22
+ end
23
+
24
+ def set(service, val)
25
+ service = Courier.service(service) if service.is_a?(Symbol)
26
+ defaults[service.name.to_sym] = check_val(val)
27
+ end
28
+
29
+ def key
30
+ name
31
+ end
32
+
33
+ private
34
+
35
+ def check_val(val)
36
+ raise "Value must be :on or :off" unless val==:on or val==:off
37
+ val
38
+ end
39
+
40
+
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class CourierGenerator < Rails::Generators::Base
5
+ desc "This generator creates an initializer file at config/initializers/courier.rb and migration file."
6
+
7
+ include Rails::Generators::Migration
8
+
9
+ def self.source_root
10
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
11
+ end
12
+
13
+ def self.next_migration_number(dirname)
14
+ if ActiveRecord::Base.timestamped_migrations
15
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
16
+ else
17
+ "%.3d" % (current_migration_number(dirname) + 1)
18
+ end
19
+ end
20
+
21
+ def create_migration_file
22
+ migration_template 'migration.rb', 'db/migrate/create_courier_tables.rb'
23
+ end
24
+
25
+ def create_initializer_file
26
+ copy_file "courier.rb", "config/initializers/courier.rb"
27
+ # create_file "config/initializers/courier.rb", "# Add initialization content here"
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ Courier.init do |d|
2
+ raise 'Make your own settings'
3
+ # d.services :active_mailer, :gritter_notice, :facebook
4
+ # d.template :import_complete, :off, :on, :off
5
+ # d.template :avatar_loaded, :on, :off, :on
6
+ # d.template :weekly_subscription, :on, :on, :on
7
+ end
@@ -0,0 +1,36 @@
1
+ # -*- coding: utf-8 -*-
2
+ class CreateCourierTables < ActiveRecord::Migration
3
+ def self.up
4
+ create_table :courier_owner_setting, :force => true do |t|
5
+ t.integer :owner_id, :null => false
6
+ t.string :owner_type, :null => false
7
+ t.text :settings, :null => false
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :courier_owner_setting, [:owner_id, :owner_type], :unique=>true
12
+
13
+ # create_table :courier_templates, :force => true do |t|
14
+ # t.string :key, :null => false, :unique=>true
15
+ # t.text :settings, :null => false
16
+ # t.timestamps
17
+ # end
18
+
19
+ create_table :courier_messages, :force => true do |t|
20
+ t.integer :owner_id, :null => false
21
+ t.string :owner_type, :null => false
22
+ t.integer :template_id, :null => false
23
+ t.integer :service_id, :null => false
24
+ t.string :state, :null => false
25
+ t.text :options, :null => false
26
+ t.timestamp :delivered_at
27
+ t.timestamps
28
+ end
29
+
30
+ add_index :courier_messages, [:service_id, :state]
31
+ end
32
+
33
+ def self.down
34
+ # drop_table :gritter_notices
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Courier::Config do
4
+ before do
5
+ subject.services :active_mailer, :facebook
6
+ end
7
+
8
+ describe '#services' do
9
+ it 'initialize services by name and preserve order' do
10
+ subject.services.first.should be_kind_of(Courier::Service::ActiveMailer)
11
+ subject.services.last.should be_kind_of(Courier::Service::Facebook)
12
+ end
13
+
14
+ it 'return ordered list of services when called with not args' do
15
+ services=[1,2,3]
16
+ subject.should_receive(:services_order) { services }
17
+ subject.services.should == services
18
+ end
19
+ end
20
+
21
+ describe '#get_service'
22
+
23
+ describe '#template' do
24
+ it 'sets default values for template' do
25
+ subject.template :import_complete, :on, :off
26
+ subject.get_template(:import_complete).should be_kind_of(Courier::Template::Base)
27
+ end
28
+
29
+ it 'raise errors when services counts not much' do
30
+ expect { subject.template :import_complete, :on, :off, :off }.to raise_error
31
+ end
32
+
33
+ it 'raise errors when value unknown' do
34
+ expect { subject.template :import_complete, :on, :bad_value }.to raise_error
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Courier::Message do
5
+
6
+ it { should belong_to(:owner) }
7
+ it { should belong_to(:template) }
8
+ it { should belong_to(:service) }
9
+
10
+ it { should validate_presence_of(:owner) }
11
+ it { should validate_presence_of(:service) }
12
+ it { should validate_presence_of(:template) }
13
+
14
+ describe do
15
+ before do
16
+ pending
17
+ end
18
+ end
19
+ end