action_message_texter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e0b1e9948866086af99648cfa73f11aa1d005511665b2994498ec9d5c4712ae6
4
+ data.tar.gz: daf2f869aadbaf8c0b88d60993e08bcb7d8054cb614453e15f8f1dad6725096d
5
+ SHA512:
6
+ metadata.gz: f1578f69ee484d7b6b8744724320b0a7a5a80582056ced7317dd14d4e0c77d97bbade833dfb014f693d4f05728f7edcd3a86735ba20f78150b93a3d461e073b7
7
+ data.tar.gz: 526a2b6307bab0833b18b9978eae1fff1b571cb779992180ed773396a9ec42a67efb7ad1c406870ee8d590677d91614ae5c6967bbeb75be3a8d2e82b60872147
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Arthur Li
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # ActionMessageTexter
2
+
3
+ 仿造Rails ActionMailer 打造的簡訊寄送模組,提供與Mailer一致的開發體驗
4
+
5
+
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'action_message_texter'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install action_message_texter
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Texter Generator
27
+ This generator will generate Texter at `app/texter`
28
+ ```bash
29
+ rails g action_message_texter:texter Devise sign_up_succeed
30
+ ```
31
+
32
+ ### Texter
33
+
34
+
35
+ ## Contributing
36
+ Contribution directions go here.
37
+
38
+ ## License
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ActionMessageTexter'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,127 @@
1
+ module ActionMessageTexter
2
+ class Base < AbstractController::Base
3
+ include Rescuable
4
+ include DeliveryMethods
5
+
6
+ include AbstractController::Logger
7
+ include AbstractController::Translation
8
+ include AbstractController::Callbacks
9
+ include AbstractController::Caching
10
+
11
+ # configure
12
+ class_attribute :default_params, default: {
13
+ to: nil, # who the message is destined for
14
+ deliver_at: nil # who to say the message send on
15
+ }
16
+
17
+ class << self
18
+ def default(value)
19
+ self.default_params = default_params.merge(value).freeze if value
20
+ default_params
21
+ end
22
+
23
+ # set default params by configuration
24
+ alias default_options= default
25
+
26
+ def texter_name
27
+ @texter_name ||= anonymous? ? 'anonymous' : name.underscore
28
+ end
29
+ attr_writer :texter_name
30
+
31
+ def deliver_message(message)
32
+ # Notification
33
+ ActiveSupport::Notifications.instrument('deliver.action_message_texter') do |payload|
34
+ payload[:content] = message.content
35
+ payload[:to] = message.to
36
+ payload[:deliver_at] = message.deliver_at || Time.now
37
+ payload[:texter] = name
38
+ yield # Let Message do the delivery actions
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # connect to MessageDelivery through method_missing
45
+ def method_missing(method_name, *args)
46
+ if action_methods.include?(method_name.to_s)
47
+ ActionMessageTexter::MessageDelivery.new(self, method_name, *args)
48
+ else
49
+ super
50
+ end
51
+ end
52
+ end
53
+
54
+ attr_internal :message
55
+
56
+ def initialize
57
+ super()
58
+ @_message_was_called = false
59
+ @_message = Message.new
60
+ end
61
+
62
+ def process(method_name, *args)
63
+ payload = {
64
+ messenger: self.class.name,
65
+ action: method_name,
66
+ args: args
67
+ }
68
+
69
+ # Notification
70
+ ActiveSupport::Notifications.instrument('process.action_message_texter', payload) do
71
+ super
72
+ @_message = NullMessage.new unless @_message_was_called
73
+ end
74
+ end
75
+
76
+ class NullMessage
77
+ end
78
+
79
+ # return Message instance
80
+ def sms(headers = {})
81
+ puts headers
82
+ @_message_was_called = true
83
+
84
+ headers = self.class.default_params.merge(headers)
85
+
86
+ message.content = render_content(headers)
87
+
88
+ message.to = recipient(headers)
89
+
90
+ message.deliver_at = headers.delete(:at)
91
+
92
+ message.other_options = headers
93
+
94
+ # TODO: set provider dynamic
95
+ wrap_delivery_behavior!(:base)
96
+
97
+ message
98
+ end
99
+
100
+ private
101
+
102
+ def render_content(headers)
103
+ content = headers.delete(:content) || default_i18n_context(instance_values.reject do |key, _value|
104
+ key.start_with?('_')
105
+ end)
106
+ unless content.present?
107
+ raise 'Message content cannot be nil, add content to sms method, or add I18n to generated message content'
108
+ end
109
+
110
+ content
111
+ end
112
+
113
+ def recipient(headers)
114
+ recipient = headers.delete(:to)
115
+ raise 'Message should has at least one recipient' unless recipient.present?
116
+
117
+ recipient
118
+ end
119
+
120
+ def default_i18n_context(interpolations = {})
121
+ texter_scope = self.class.texter_name.tr('/', '.')
122
+ I18n.t(action_name, **interpolations.symbolize_keys.merge(scope: [texter_scope]), default: nil)
123
+ end
124
+
125
+ ActiveSupport.run_load_hooks(:action_message_texter, self)
126
+ end
127
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_job'
4
+
5
+ module ActionMessageTexter
6
+ class DeliveryJob < ActiveJob::Base
7
+ queue_as { ActionMailer::Base.deliver_later_queue_name }
8
+
9
+ rescue_from StandardError, with: :handle_exception_with_texter_class
10
+
11
+ def perform(_texter, sms_method, delivery_method, *args)
12
+ texter_class.public_send(sms_method, *args).send(delivery_method)
13
+ end
14
+
15
+ private
16
+
17
+ def texter_class
18
+ if texter = Array(@serialized_arguments).first || Array(arguments).first
19
+ texter.constantize
20
+ end
21
+ end
22
+
23
+ def handle_exception_with_texter_class(exception)
24
+ if klass = texter_class
25
+ klass.handle_exception exception
26
+ else
27
+ raise exception
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ module ActionMessageTexter
2
+ module DeliveryMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ cattr_accessor :raise_delivery_errors, default: true
7
+ cattr_accessor :deliver_later_queue_name, default: :texters
8
+
9
+ class_attribute :delivery_methods, default: {}.freeze
10
+ class_attribute :delivery_method, default: :base
11
+
12
+ add_delivery_method(:base, SMSProvider::Base)
13
+ end
14
+
15
+ module ClassMethods
16
+ def add_delivery_method(symbol, klass, options = {})
17
+ class_attribute("#{symbol}_settings".to_sym) unless respond_to?("#{symbol}_settings".to_sym)
18
+ send("#{symbol}_settings=".to_sym, options)
19
+ self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
20
+ end
21
+
22
+ def wrap_delivery_behavior(message, method = nil, options = {})
23
+ method ||= delivery_method
24
+ message.delivery_handler = self
25
+ case method
26
+ when NilClass
27
+ raise 'Delivery method cannot be nil'
28
+ when Symbol
29
+ if klass = delivery_methods[method]
30
+ message.delivery_method(klass, (send("#{method}_settings") || {}).merge(options))
31
+ else
32
+ raise "Invalid delivery method #{method.inspect}"
33
+ end
34
+ else
35
+ message.delivery_method(method)
36
+ end
37
+ message.raise_delivery_errors = raise_delivery_errors
38
+ end
39
+ end
40
+
41
+ def wrap_delivery_behavior!(*args) # :nodoc:
42
+ self.class.wrap_delivery_behavior(message, *args)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ require 'action_message_texter'
2
+
3
+ module ActionMessageTexter
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace ActionMessageTexter
6
+ config.eager_load_namespaces << ActionMessageTexter
7
+
8
+ initializer 'action_message_texter.logger' do
9
+ ActiveSupport.on_load(:action_message_texter) { self.logger ||= Rails.logger }
10
+ end
11
+
12
+ config.after_initialize do |app|
13
+ ActiveSupport.on_load(:action_message_txter) do
14
+ options = app.config.action_short_message
15
+ options.each { |k, v| send("#{k}=", v) }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module ActionMessageTexter
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ # delivery log
4
+ def deliver(event)
5
+ info do
6
+ message = event.payload[:content]
7
+ to = event.payload[:to]
8
+ deliver_at = event.payload[:deliver_at]
9
+ texter = event.payload[:texter]
10
+ "#{texter}: Delivered message [#{message}] to #{to} at #{deliver_at}"
11
+ end
12
+ end
13
+
14
+ # process message log
15
+ def process(event)
16
+ debug do
17
+ messenger = event.payload[:messenger]
18
+ action = event.payload[:action]
19
+ "#{messenger}##{action}: processed short Message in #{event.duration.round(1)}ms"
20
+ end
21
+ end
22
+
23
+ def logger
24
+ ActionMessageTexter::Base.logger || Rails.logger
25
+ end
26
+ end
27
+ end
28
+
29
+ ActionMessageTexter::LogSubscriber.attach_to :action_message_texter
@@ -0,0 +1,42 @@
1
+ module ActionMessageTexter
2
+ class Message
3
+ attr_accessor :content, :to, :deliver_at, :delivery_options, :delivery_handler, :other_options,
4
+ :raise_delivery_errors
5
+
6
+ attr_writer :delivery_method
7
+
8
+ # TODO: create registred interceptor and observer
9
+
10
+ def initialize
11
+ @raise_delivery_errors = true
12
+ end
13
+
14
+ def deliver
15
+ # inform_interceptors
16
+ if delivery_handler
17
+ delivery_handler.deliver_message(self) { do_delivery }
18
+ else
19
+ do_delivery
20
+ end
21
+ # inform_observers
22
+ self
23
+ end
24
+
25
+ def delivery_method(method = nil, _settings = {})
26
+ if method
27
+ # TODO: get provider from configuration
28
+ @delivery_method = SMSProvider::Base.new
29
+ else
30
+ @delivery_method
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def do_delivery
37
+ delivery_method.deliver!(self)
38
+ rescue StandardError => e
39
+ raise e if raise_delivery_errors
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ module ActionMessageTexter
2
+ # 分派工作
3
+ class MessageDelivery < Delegator
4
+ def initialize(texter_class, action, *args)
5
+ @texter_class = texter_class
6
+ @action = action
7
+ @args = args
8
+ end
9
+
10
+ def deliver_now
11
+ processed_messenger.handle_exceptions do
12
+ message.deliver
13
+ end
14
+ end
15
+
16
+ def deliver_later(options = {})
17
+ enqueue_delivery :deliver_now, options
18
+ end
19
+
20
+ def __getobj__
21
+ @message ||= processed_messenger.message
22
+ end
23
+
24
+ def __setobj__(message)
25
+ @message = message
26
+ end
27
+
28
+ def message
29
+ __getobj__
30
+ end
31
+
32
+ # 是否送出
33
+ def processed?
34
+ @processed_messenger || @message
35
+ end
36
+
37
+ private
38
+
39
+ def processed_messenger
40
+ @processed_messenger ||= @texter_class.new.tap do |messenger|
41
+ messenger.process @action, *@args
42
+ end
43
+ end
44
+
45
+ def enqueue_delivery(delivery_method, options = {})
46
+ args = @texter_class.name, @action.to_s, delivery_method.to_s, *@args
47
+ ::ActionMessageTexter::DeliveryJob.set(options).perform_later(*args)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMessageTexter
4
+ module Rescuable
5
+ extend ActiveSupport::Concern
6
+ include ActiveSupport::Rescuable
7
+
8
+ class_methods do
9
+ def handle_exception(exception)
10
+ rescue_with_handler(exception) || raise(exception)
11
+ end
12
+ end
13
+
14
+ def handle_exceptions
15
+ yield
16
+ rescue StandardError => e
17
+ rescue_with_handler(e) || raise
18
+ end
19
+
20
+ private
21
+
22
+ def process(*)
23
+ handle_exceptions do
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module ActionMessageTexter
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'action_message_texter/engine'
2
+ require 'action_message_texter/delivery_methods'
3
+ require 'action_message_texter/log_subscriber'
4
+ require 'action_message_texter/message_delivery'
5
+ require 'action_message_texter/message'
6
+ require 'action_message_texter/rescuable'
7
+ require 'action_message_texter/delivery_job'
8
+ require 'sms_provider/base'
9
+
10
+ module ActionMessageTexter
11
+ extend ::ActiveSupport::Autoload
12
+
13
+ autoload :Base
14
+ autoload :DeliveryMethods
15
+ autoload :DeliveryJob
16
+ autoload :LogSubscriber
17
+ autoload :MessageDelivery
18
+ autoload :Recuable
19
+ autoload :Message
20
+ end
@@ -0,0 +1,34 @@
1
+ module ActionMessageTexter
2
+ module Generators
3
+ class TexterGenerator < Rails::Generators::NamedBase
4
+ require 'yaml'
5
+ source_root File.expand_path(__dir__)
6
+
7
+ argument :actions, type: :array, default: [], banner: 'method method'
8
+
9
+ check_class_collision suffix: 'Texter'
10
+
11
+ def create_message_file
12
+ template '../templates/texter.rb', File.join('app/texter', "#{file_name}_texter.rb")
13
+ end
14
+
15
+ def create_locale_yml
16
+ @yaml = {}
17
+ default_locale = I18n.default_locale.to_s
18
+ scope_name = "#{file_name}_texter"
19
+ @yaml[default_locale] = {}
20
+ @yaml[default_locale][scope_name] = {}
21
+ actions.each do |action|
22
+ @yaml[default_locale][scope_name][action] = "#{action} message"
23
+ end
24
+ template '../templates/I18n.yml.rb', File.join('config/locales/texter', "#{file_name}_texter.yml")
25
+ end
26
+
27
+ protected
28
+
29
+ def file_name
30
+ @_file_name ||= super.gsub(/_texter|Texter/i, '')
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1 @@
1
+ <%= @yaml.to_yaml %>
@@ -0,0 +1,7 @@
1
+ class <%="#{class_name}"%>Texter < ActionMessageTexter::Base
2
+ <%actions.each do |action| %>
3
+ def <%=action%>
4
+ sms(to: "+886987654321")
5
+ end
6
+ <%end%>
7
+ end
@@ -0,0 +1,7 @@
1
+ module SMSProvider
2
+ class Base
3
+ def deliver!(message)
4
+ puts message
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action_message_texter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Arthur Li
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.0.0
27
+ description: SMS/Text Message gem, allow your Rails send short message to phone
28
+ email:
29
+ - z22919456@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/action_message_texter.rb
38
+ - lib/action_message_texter/base.rb
39
+ - lib/action_message_texter/delivery_job.rb
40
+ - lib/action_message_texter/delivery_methods.rb
41
+ - lib/action_message_texter/engine.rb
42
+ - lib/action_message_texter/log_subscriber.rb
43
+ - lib/action_message_texter/message.rb
44
+ - lib/action_message_texter/message_delivery.rb
45
+ - lib/action_message_texter/rescuable.rb
46
+ - lib/action_message_texter/version.rb
47
+ - lib/generators/action_message_texter/texter_generator.rb
48
+ - lib/generators/templates/i18n.yml.rb
49
+ - lib/generators/templates/texter.rb
50
+ - lib/sms_provider/base.rb
51
+ homepage: http://github.com/z22919456/action_message_texter
52
+ licenses:
53
+ - MIT
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubygems_version: 3.1.6
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: SMS/Text Message gem, allow your Rails send short message to phone
74
+ test_files: []