courier 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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