courier 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.watchr +99 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +98 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/courier.gemspec +135 -0
- data/lib/courier.rb +46 -0
- data/lib/courier/config.rb +50 -0
- data/lib/courier/engine.rb +4 -0
- data/lib/courier/message.rb +26 -0
- data/lib/courier/owner.rb +32 -0
- data/lib/courier/owner_setting.rb +45 -0
- data/lib/courier/service/active_mailer.rb +4 -0
- data/lib/courier/service/base.rb +55 -0
- data/lib/courier/service/facebook.rb +26 -0
- data/lib/courier/service/gritter_notice.rb +18 -0
- data/lib/courier/template/base.rb +41 -0
- data/lib/generators/courier_generator.rb +29 -0
- data/lib/generators/templates/courier.rb +7 -0
- data/lib/generators/templates/migration.rb +36 -0
- data/spec/courier/config_spec.rb +37 -0
- data/spec/courier/message_spec.rb +19 -0
- data/spec/courier/owner_setting_spec.rb +43 -0
- data/spec/courier/owner_spec.rb +32 -0
- data/spec/courier/service/base_spec.rb +48 -0
- data/spec/courier/service/facebook_spec.rb +22 -0
- data/spec/courier/service/gritter_notice_spec.rb +20 -0
- data/spec/courier/template/base_spec.rb +25 -0
- data/spec/courier_spec.rb +25 -0
- data/spec/examples/example1_spec.rb +78 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/factories.rb +27 -0
- data/spec/support/gems_simulation.rb +11 -0
- data/spec/support/migration.rb +7 -0
- data/spec/support/mocks.rb +19 -0
- data/spec/support/user.rb +4 -0
- metadata +333 -0
@@ -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,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,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,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
|