action_message_texter 0.0.1

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.
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: []