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.
- 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
|