sztywny-smsonrails 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/MIT-LICENSE +20 -0
- data/Manifest +101 -0
- data/README.rdoc +211 -0
- data/Rakefile +109 -0
- data/VERSION +1 -0
- data/app/controllers/admin/sms_on_rails/base_controller.rb +11 -0
- data/app/controllers/admin/sms_on_rails/drafts_controller.rb +75 -0
- data/app/controllers/admin/sms_on_rails/outbounds_controller.rb +117 -0
- data/app/controllers/admin/sms_on_rails/phone_carriers_controller.rb +85 -0
- data/app/controllers/admin/sms_on_rails/phone_numbers_controller.rb +101 -0
- data/app/controllers/sms_on_rails/creation_support.rb +99 -0
- data/app/controllers/sms_on_rails_controller.rb +14 -0
- data/app/helpers/admin/sms_on_rails/drafts_helper.rb +2 -0
- data/app/helpers/admin/sms_on_rails/phone_carriers_helper.rb +2 -0
- data/app/helpers/sms_on_rails/phone_numbers_helper.rb +9 -0
- data/app/helpers/sms_on_rails/sms_helper.rb +44 -0
- data/app/models/sms_on_rails/draft.rb +9 -0
- data/app/models/sms_on_rails/outbound.rb +17 -0
- data/app/models/sms_on_rails/phone_carrier.rb +14 -0
- data/app/models/sms_on_rails/phone_number.rb +8 -0
- data/app/views/admin/sms_on_rails/base/index.html.erb +5 -0
- data/app/views/admin/sms_on_rails/drafts/_show.html.erb +34 -0
- data/app/views/admin/sms_on_rails/drafts/edit.html.erb +36 -0
- data/app/views/admin/sms_on_rails/drafts/index.html.erb +32 -0
- data/app/views/admin/sms_on_rails/drafts/new.html.erb +34 -0
- data/app/views/admin/sms_on_rails/drafts/send_sms.html.erb +3 -0
- data/app/views/admin/sms_on_rails/drafts/show.html.erb +4 -0
- data/app/views/admin/sms_on_rails/outbounds/edit.html.erb +68 -0
- data/app/views/admin/sms_on_rails/outbounds/index.html.erb +37 -0
- data/app/views/admin/sms_on_rails/outbounds/new.html.erb +54 -0
- data/app/views/admin/sms_on_rails/outbounds/show.html.erb +69 -0
- data/app/views/admin/sms_on_rails/phone_carriers/edit.html.erb +24 -0
- data/app/views/admin/sms_on_rails/phone_carriers/index.html.erb +24 -0
- data/app/views/admin/sms_on_rails/phone_carriers/new.html.erb +22 -0
- data/app/views/admin/sms_on_rails/phone_carriers/show.html.erb +24 -0
- data/app/views/admin/sms_on_rails/phone_numbers/edit.html.erb +33 -0
- data/app/views/admin/sms_on_rails/phone_numbers/index.html.erb +28 -0
- data/app/views/admin/sms_on_rails/phone_numbers/new.html.erb +31 -0
- data/app/views/admin/sms_on_rails/phone_numbers/show.html.erb +32 -0
- data/app/views/layouts/sms_on_rails/basic.html.erb +26 -0
- data/app/views/sms_on_rails/_phone_carrier_form_item.html.erb +6 -0
- data/app/views/sms_on_rails/_send_sms.html.erb +33 -0
- data/app/views/sms_on_rails/index.html.erb +8 -0
- data/app/views/sms_on_rails/send_sms.html.erb +3 -0
- data/app/views/sms_on_rails/show.html.erb +29 -0
- data/config/routes.rb +19 -0
- data/db/data/fixtures/sms_phone_carriers.yml +110 -0
- data/db/migrate/sms_on_rails_carrier_tables.rb +9 -0
- data/db/migrate/sms_on_rails_model_tables.rb +48 -0
- data/db/migrate/sms_on_rails_phone_number_tables.rb +11 -0
- data/db/seed_data.rb +16 -0
- data/generators/sms_on_rails/USAGE +31 -0
- data/generators/sms_on_rails/commands/inserts.rb +63 -0
- data/generators/sms_on_rails/commands/timestamps.rb +33 -0
- data/generators/sms_on_rails/runners/add_all_models.rb +6 -0
- data/generators/sms_on_rails/runners/dependencies.rb +1 -0
- data/generators/sms_on_rails/runners/remove_all_models.rb +5 -0
- data/generators/sms_on_rails/runners/sms_on_rails_routes.rb +14 -0
- data/generators/sms_on_rails/sms_on_rails_generator.rb +255 -0
- data/generators/sms_on_rails/templates/configuration/clickatell.rb +6 -0
- data/generators/sms_on_rails/templates/configuration/email_gateway.rb +7 -0
- data/generators/sms_on_rails/templates/migrate/schema_migration.rb +15 -0
- data/generators/sms_on_rails/templates/migrate/sms_on_rails_update_phone_numbers.rb +40 -0
- data/generators/sms_on_rails/templates/phone_number_collision.rb +2 -0
- data/init.rb +3 -0
- data/install.rb +1 -0
- data/lib/sms_on_rails.rb +8 -0
- data/lib/sms_on_rails/activerecord_extensions/acts_as_deliverable.rb +92 -0
- data/lib/sms_on_rails/activerecord_extensions/acts_as_substitutable.rb +80 -0
- data/lib/sms_on_rails/activerecord_extensions/has_a_sms_service_provider.rb +101 -0
- data/lib/sms_on_rails/activerecord_extensions/lockable_record.rb +186 -0
- data/lib/sms_on_rails/all_models.rb +3 -0
- data/lib/sms_on_rails/model_support/draft.rb +178 -0
- data/lib/sms_on_rails/model_support/outbound.rb +136 -0
- data/lib/sms_on_rails/model_support/phone_carrier.rb +77 -0
- data/lib/sms_on_rails/model_support/phone_number.rb +248 -0
- data/lib/sms_on_rails/model_support/phone_number_associations.rb +13 -0
- data/lib/sms_on_rails/schema_helper.rb +51 -0
- data/lib/sms_on_rails/service_providers/base.rb +222 -0
- data/lib/sms_on_rails/service_providers/clickatell.rb +54 -0
- data/lib/sms_on_rails/service_providers/dummy.rb +21 -0
- data/lib/sms_on_rails/service_providers/email_gateway.rb +70 -0
- data/lib/sms_on_rails/service_providers/email_gateway_support/errors.rb +20 -0
- data/lib/sms_on_rails/service_providers/email_gateway_support/sms_mailer.rb +21 -0
- data/lib/sms_on_rails/service_providers/email_gateway_support/sms_mailer/sms_through_gateway.erb +6 -0
- data/lib/sms_on_rails/util/sms_error.rb +12 -0
- data/lib/smsonrails.rb +1 -0
- data/public/images/sms_on_rails/railsYoDawg.jpg +0 -0
- data/public/stylesheets/sms_on_rails.css +137 -0
- data/smsonrails.gemspec +159 -0
- data/sztywny-smsonrails.gemspec +148 -0
- data/tasks/sms_on_rails_tasks.rake +68 -0
- data/test/active_record_extensions/delivery_and_locking_test.rb +84 -0
- data/test/models/draft_test.rb +72 -0
- data/test/models/outbound_test.rb +89 -0
- data/test/models/phone_number_test.rb +131 -0
- data/test/run.rb +19 -0
- data/test/service_providers/abstract_test_support.rb +104 -0
- data/test/service_providers/clickatell_test.rb +37 -0
- data/test/service_providers/email_gateway_test.rb +30 -0
- data/test/test_helper.rb +24 -0
- data/uninstall.rb +1 -0
- metadata +193 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
class <%= migration_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
<%= SmsOnRails::SchemaHelper.create(*files) %>
|
4
|
+
|
5
|
+
<% if migration_name.downcase.include?('carrier') -%>
|
6
|
+
require "#{RAILS_ROOT}/vendor/plugins/smsonrails/db/seed_data.rb"
|
7
|
+
<% else %>
|
8
|
+
<%= migration_name %>
|
9
|
+
<% end -%>
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
<%= SmsOnRails::SchemaHelper.drop(*files) %>
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class SmsOnRailsUpdatePhoneNumbers < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
|
4
|
+
<%
|
5
|
+
existing_columns = ActiveRecord::Base.connection.columns(:phone_numbers).collect { |each| each.name }
|
6
|
+
columns = [
|
7
|
+
[:number, 't.string :number, :length => 20, :null => false'],
|
8
|
+
[:carrier_id, 't.integer :carrier_id, :default => nil'],
|
9
|
+
[:owner_id, 't.integer :owner_id, :default => nil'],
|
10
|
+
[:white_list, 't.boolean :white_list, :null => false, :default => false'],
|
11
|
+
[:do_not_send, 't.string :do_not_send, :length => 30, :default => nil'],
|
12
|
+
[:country_code,'t.string :country_code, :length => 2, :default => 1'],
|
13
|
+
].delete_if {|c| existing_columns.include?(c.first.to_s)}
|
14
|
+
-%>
|
15
|
+
change_table(:phone_numbers) do |t|
|
16
|
+
<% columns.each do |c| -%>
|
17
|
+
<%= c.last %>
|
18
|
+
<% end -%>
|
19
|
+
end
|
20
|
+
|
21
|
+
<%
|
22
|
+
existing_indexes = ActiveRecord::Base.connection.indexes(:phone_numbers)
|
23
|
+
index_names = existing_indexes.collect { |each| each.name }
|
24
|
+
new_indexes = [
|
25
|
+
[:uk_phone_numbers_number, 'add_index :phone_numbers, :unique => true']
|
26
|
+
].delete_if { |each| index_names.include?(each.first.to_s) }
|
27
|
+
-%>
|
28
|
+
<% new_indexes.each do |each| -%>
|
29
|
+
<%= each.last %>
|
30
|
+
<% end -%>
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.down
|
34
|
+
change_table(:phone_numbers) do |t|
|
35
|
+
<% unless columns.empty? -%>
|
36
|
+
t.remove <%= columns.collect { |each| ":#{each.first}" }.join(',') %>
|
37
|
+
<% end -%>
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/lib/sms_on_rails.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
Dir.glob(File.dirname(__FILE__) + '/sms_on_rails/activerecord_extensions/*.rb'){|f| require f}
|
4
|
+
|
5
|
+
files = Dir.glob(File.dirname(__FILE__) + '/sms_on_rails/util/*.rb')
|
6
|
+
files.concat Dir.glob(File.dirname(__FILE__) + '/sms_on_rails/service_providers/*.rb')
|
7
|
+
files.concat(Dir.glob(File.dirname(__FILE__) + '/sms_on_rails/model_support/*.rb'))
|
8
|
+
files.each {|f| require f }
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module SmsOnRails
|
2
|
+
module ActsAsDeliverable
|
3
|
+
|
4
|
+
def self.extended(base)#:nodoc:
|
5
|
+
base.send :class_inheritable_hash, :acts_as_deliverable_options
|
6
|
+
base.acts_as_deliverable_options = {
|
7
|
+
:retry_count => 3,
|
8
|
+
:max_messages => 30,
|
9
|
+
:fatal_exception => nil,
|
10
|
+
:locrec_options => {},
|
11
|
+
:error => "Unable to deliver.",
|
12
|
+
:already_processed_error => 'Already delivered'
|
13
|
+
}
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def acts_as_deliverable(options={})
|
19
|
+
class_eval do
|
20
|
+
acts_as_deliverable_options.update(options) if options
|
21
|
+
include SmsOnRails::ActsAsDeliverable::InstanceMethods
|
22
|
+
extend SmsOnRails::ActsAsDeliverable::SingletonMethods
|
23
|
+
lockable_record acts_as_deliverable_options[:locrec_options]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module SingletonMethods
|
29
|
+
# Deliver a list of deliverables
|
30
|
+
# === Parameters
|
31
|
+
# * +deliverables+ - a deliverable or an array of deliverables
|
32
|
+
# * +options+ - delivery options. Refer to individual delivery options
|
33
|
+
#
|
34
|
+
# All messages(including failed status) are delivered unless a fatal exception occurs
|
35
|
+
# Specify <tt> :fatal_exception => nil </tt> to override this behavior
|
36
|
+
def deliver(deliverables, options={})
|
37
|
+
[deliverables].flatten.each do |deliverable|
|
38
|
+
deliverable.deliver(options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module InstanceMethods
|
44
|
+
|
45
|
+
# Return true if delivery was successful
|
46
|
+
# If unsuccessful, only raise fatal exceptions, and return false otherwise
|
47
|
+
# === Options
|
48
|
+
# +fatal_exception+ - specify the fatal exception Class to throw. To dismiss all exceptions, set to nil.
|
49
|
+
# Defaults to acts_as_deliverable_options specified
|
50
|
+
# +error+ - add an error to the objects base if delivery failed
|
51
|
+
def deliver(options={})
|
52
|
+
deliver!(options)
|
53
|
+
rescue Exception => exc
|
54
|
+
log_delivery_error(exc)
|
55
|
+
fatal_exception = acts_as_deliverable_options.merge(options)[:fatal_exception]
|
56
|
+
raise exc if fatal_exception && exc.is_a?(fatal_exception)
|
57
|
+
self.errors.add_to_base(delivery_error_message(exc, options))
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Deliver and mark the status fields approriately
|
62
|
+
def deliver!(options={})
|
63
|
+
lock_record_and_execute { deliver_message(options) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def delivered?; already_processed?; end
|
67
|
+
|
68
|
+
# Deliver message should be overridden by the class to perform any
|
69
|
+
# functionality
|
70
|
+
def deliver_message(options={})
|
71
|
+
raise(acts_as_deliverable_options[:fatal_exception]||Exception).new "Overwrite deliver_message in base class to perform the actual delivery"
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
def delivery_error_message(exc, options)
|
76
|
+
err_msg = if exc.is_a?(SmsOnRails::LockableRecord::AlreadyProcessed)
|
77
|
+
options[:already_processed_error]|| acts_as_deliverable_options[:already_processed_error]
|
78
|
+
end || (options[:error]||acts_as_deliverable_options[:error])
|
79
|
+
err_msg
|
80
|
+
end
|
81
|
+
|
82
|
+
# Override this function to outbound errors
|
83
|
+
# differently to a log or with a different message
|
84
|
+
def log_delivery_error(exc)
|
85
|
+
logger.error "Delivery Error: #{exc}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
ActiveRecord::Base.extend SmsOnRails::ActsAsDeliverable unless defined?(ActiveRecord::Base.acts_as_deliverable_options)
|
92
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module SmsOnRails
|
2
|
+
module ActsAsSubstitutable
|
3
|
+
|
4
|
+
def self.extended(base)
|
5
|
+
base.send :class_inheritable_accessor, :acts_as_sub_options
|
6
|
+
base.acts_as_sub_options = {}
|
7
|
+
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def acts_as_substitutable(method, options={})
|
13
|
+
|
14
|
+
defaults = {:time => Proc.new {|rec| Time.now.to_s(:db) }}
|
15
|
+
|
16
|
+
class_eval do
|
17
|
+
acts_as_sub_options[method.to_sym] = defaults.merge(options||{})
|
18
|
+
def clear_substituted_params
|
19
|
+
acts_as_sub_options.keys.each{|method| instance_variable_set("@#{method}", nil)}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
acts_as_substitutable_instance_methods(method)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def acts_as_substitutable_instance_methods(method)
|
29
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
30
|
+
before_save :clear_substituted_params
|
31
|
+
|
32
|
+
def substituted_#{method}
|
33
|
+
return '' if self.#{method}.blank?
|
34
|
+
unless @sub_#{method}
|
35
|
+
@sub_#{method} = self.#{method}.dup
|
36
|
+
|
37
|
+
if (subs = @sub_#{method}.scan(substitution_#{method}_regex)).any?
|
38
|
+
subs.flatten.compact.each do |sub|
|
39
|
+
method = #{method}_sub_options[sub.downcase.to_sym]
|
40
|
+
val = if method.is_a?(Proc)
|
41
|
+
method.call(self)
|
42
|
+
elsif method
|
43
|
+
self.send method
|
44
|
+
end
|
45
|
+
@sub_#{method}.gsub!(sub_key_to_val(sub), val)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@sub_#{method}
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
def substitution_#{method}_regex
|
54
|
+
@@substitution_#{method}_regex ||=
|
55
|
+
Regexp.new #{method}_sub_options.keys.collect{|key|
|
56
|
+
sub_key_to_val(key, ['\\$(', ')\\$'])
|
57
|
+
}.join('|')
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def sub_key_to_val(key, surround = ['$', '$'])
|
62
|
+
|
63
|
+
val = surround.first
|
64
|
+
val << key.to_s.upcase
|
65
|
+
val << surround.last
|
66
|
+
val
|
67
|
+
end
|
68
|
+
|
69
|
+
def #{method}_sub_options
|
70
|
+
self.class.acts_as_sub_options[:#{method}]
|
71
|
+
end
|
72
|
+
EOS
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
ActiveRecord::Base.extend SmsOnRails::ActsAsSubstitutable unless defined?(ActiveRecord::Base.acts_as_sub_options)
|
79
|
+
|
80
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module SmsOnRails
|
2
|
+
module ServiceProviders
|
3
|
+
module HasASmsServiceProvider
|
4
|
+
|
5
|
+
def self.extended(base)
|
6
|
+
base.send :class_inheritable_hash, :sms_service_provider_options
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Add an accessor to the service provider
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# For example, if the provider was referenced by name by calling method 'sms_vendor'
|
15
|
+
# has_an_sms_service_provider :method => 'sms_vendor', :type => :name, :accessor_method => 'sms_service_provider'
|
16
|
+
# would define an instance method sms_service_provider and sms_service_provider_id that returns the appropriate
|
17
|
+
# instance and id respectively.
|
18
|
+
#
|
19
|
+
# Likewise if :type => :id or there is a column on the :id field, sms_service_provider_id would
|
20
|
+
# be defined
|
21
|
+
#
|
22
|
+
#
|
23
|
+
# ===Options
|
24
|
+
# <tt>:type</tt> the key field (either :id or :name). the one that actually stores the data
|
25
|
+
# The provider is referenced by provider_id by default
|
26
|
+
# <tt>:method</tt> the name of the method that should be invoked to get the wanted id or name
|
27
|
+
# <tt>:accessor_name</tt> the name of the new method accessor
|
28
|
+
def has_a_sms_service_provider(options={})
|
29
|
+
self.sms_service_provider_options = options||{}
|
30
|
+
|
31
|
+
sms_service_provider_options[:accessor_name]||= 'sms_service_provider'
|
32
|
+
|
33
|
+
accessors =
|
34
|
+
{ sms_service_provider_options[:accessor_name] + '_id' => :provider_id,
|
35
|
+
sms_service_provider_options[:accessor_name] + '_name'=> :name}
|
36
|
+
|
37
|
+
|
38
|
+
key_field = nil
|
39
|
+
if sms_service_provider_options[:type]
|
40
|
+
key_field = sms_service_provider_options[:accessor_name]
|
41
|
+
key_field << '_'
|
42
|
+
key_field << sms_service_provider_options[:type]
|
43
|
+
end
|
44
|
+
|
45
|
+
set_instance_code = accessors.inject('') do |code, field|
|
46
|
+
|
47
|
+
|
48
|
+
# column fields use write_attribute to update the data
|
49
|
+
# non column field look up the service providers real name
|
50
|
+
if respond_to?(:column_names) && self.column_names.include?(field.first)
|
51
|
+
key_field ||= field.first
|
52
|
+
code << "write_attribute(#{field.first.inspect}, (@provider.#{field.last} if @provider))"
|
53
|
+
else
|
54
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
55
|
+
def #{field.first}
|
56
|
+
if (p=#{sms_service_provider_options[:accessor_name]})
|
57
|
+
p.send #{field.last.inspect}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
EOS
|
61
|
+
end
|
62
|
+
|
63
|
+
#define the setter function to call the service_provider instance setter
|
64
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
65
|
+
def #{field.first}=(value)
|
66
|
+
self.#{sms_service_provider_options[:accessor_name]}=value
|
67
|
+
self.#{field.first}
|
68
|
+
end
|
69
|
+
EOS
|
70
|
+
code
|
71
|
+
end
|
72
|
+
|
73
|
+
#define the setter and getter codes for service provider
|
74
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
75
|
+
def #{sms_service_provider_options[:accessor_name]}
|
76
|
+
unless @provider
|
77
|
+
value = self.send(#{key_field.inspect})
|
78
|
+
self.#{sms_service_provider_options[:accessor_name]}=value
|
79
|
+
end
|
80
|
+
@provider
|
81
|
+
end
|
82
|
+
|
83
|
+
def #{sms_service_provider_options[:accessor_name]}=(value)
|
84
|
+
@provider = SmsOnRails::ServiceProviders::Base.get_service_provider(value)
|
85
|
+
#{set_instance_code}
|
86
|
+
@provider
|
87
|
+
end
|
88
|
+
|
89
|
+
def default_service_provider; SmsOnRails::ServiceProviders::Base.default_service_provider; end
|
90
|
+
def self.sms_service_provider_map; SmsOnRails::ServiceProviders::Base.provider_map; end
|
91
|
+
def self.sms_service_provider_list; SmsOnRails::ServiceProviders::Base.provider_list; end
|
92
|
+
EOS
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
unless ActiveRecord::Base.respond_to?(:has_a_sms_service_provider)
|
100
|
+
ActiveRecord::Base.extend SmsOnRails::ServiceProviders::HasASmsServiceProvider
|
101
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module SmsOnRails
|
2
|
+
module LockableRecord
|
3
|
+
|
4
|
+
class UnableToLockRecord < Exception; end
|
5
|
+
class AlreadyProcessed < UnableToLockRecord; end
|
6
|
+
|
7
|
+
|
8
|
+
def self.extended(base)
|
9
|
+
base.class_inheritable_hash :locrec_options
|
10
|
+
|
11
|
+
base.locrec_options = {
|
12
|
+
:log_lock_warnings => true,
|
13
|
+
:columns => {
|
14
|
+
:status => 'status',
|
15
|
+
:processed_on => 'processed_on',
|
16
|
+
:notes => 'notes',
|
17
|
+
:retry_count => 'retry_count',
|
18
|
+
:sub_status => 'sub_status'
|
19
|
+
},
|
20
|
+
|
21
|
+
:status => {
|
22
|
+
:not_processed => 'NOT_PROCESSED',
|
23
|
+
:processed => 'PROCESSED',
|
24
|
+
:processing => 'PROCESSING',
|
25
|
+
:failed => 'FAILED',
|
26
|
+
:cancelled => 'CANCELLED'
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
base.send :include, InstanceMethods
|
31
|
+
base.send :extend, ClassMethods
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
def locrec_columns
|
37
|
+
self.class.locrec_columns
|
38
|
+
end
|
39
|
+
def locrec_status
|
40
|
+
self.class.locrec_status
|
41
|
+
end
|
42
|
+
|
43
|
+
def already_processed?
|
44
|
+
get_locrec_col(:status) != locrec_status[:not_processed]
|
45
|
+
end
|
46
|
+
# Lock the record by setting the status from NOT_PROCESSED TO PROCESSED
|
47
|
+
# StalerecordErrors are caught and logged
|
48
|
+
def lock_record
|
49
|
+
if already_processed?
|
50
|
+
raise SmsOnRails::LockableRecord::AlreadyProcessed.new(
|
51
|
+
"Record #{self.to_param} appears to be processed. #{locrec_columns[:status]}" +
|
52
|
+
" is #{get_locrec_col(:status)} instead of #{locrec_status[:not_processed]}"
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
set_locrec_col :processed_on, Time.now
|
58
|
+
set_locrec_col :status, locrec_status[:processing]
|
59
|
+
set_locrec_col :retry_count, get_locrec_col(:retry_count).to_i + 1
|
60
|
+
save!
|
61
|
+
return true
|
62
|
+
#this just means another proc got to it first, skip
|
63
|
+
rescue ActiveRecord::StaleObjectError => exc
|
64
|
+
if locrec_options[:log_lock_warnings]
|
65
|
+
log_locrec_error :level => :warn,
|
66
|
+
:msg => "#{self.class} lock failed",
|
67
|
+
:exception => exc
|
68
|
+
end
|
69
|
+
raise SmsOnRails::LockableRecord::UnableToLockRecord.new(
|
70
|
+
"Unable to set #{locrec_columns[:status]}. Record is stale")
|
71
|
+
|
72
|
+
end
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
def lock_record_and_execute(&block)
|
77
|
+
|
78
|
+
begin
|
79
|
+
# Lock the process, execute the block, set the status to PROCESSED
|
80
|
+
# If the process could not be locked (stale record exception), continue without error
|
81
|
+
# If the process raised any other exception, set the status to FAILED
|
82
|
+
if lock_record
|
83
|
+
yield
|
84
|
+
|
85
|
+
if get_locrec_col(:status) == locrec_status[:processing]
|
86
|
+
set_locrec_col :status, locrec_status[:processed]
|
87
|
+
set_locrec_col :processed_on, Time.now if locrec_columns[:processed_on]
|
88
|
+
save!
|
89
|
+
end
|
90
|
+
end
|
91
|
+
return true
|
92
|
+
# Do not mess with the record status if it is already being processed
|
93
|
+
#rescue ActiveRecord::StaleObjectError => soe
|
94
|
+
# reset_status(locrec_status[:failed], soe)
|
95
|
+
|
96
|
+
#rescue self.class.locrec_options[:fatal_exception] => ie
|
97
|
+
# reset_status(locrec_status[:failed], ie)
|
98
|
+
# raise ie
|
99
|
+
#
|
100
|
+
|
101
|
+
# Set status to failed and reraise exception if the exception is fatal
|
102
|
+
# or we wish to throw all errors
|
103
|
+
rescue Exception => exc
|
104
|
+
reset_locrec_status(locrec_status[:failed], exc) unless exc.is_a?(SmsOnRails::LockableRecord::UnableToLockRecord)
|
105
|
+
raise exc
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def reset_locrec_status(current_status, exc)
|
110
|
+
begin
|
111
|
+
#log_entry = LogEntry.error("Send #{self.to_s.demodulize} unexpected error: #{self.id}",nil,exc)
|
112
|
+
#fresh copy in case it was updated
|
113
|
+
reload
|
114
|
+
if get_locrec_col(:status) == locrec_status[:processing]
|
115
|
+
|
116
|
+
set_locrec_col :status, current_status
|
117
|
+
|
118
|
+
if locrec_columns[:notes]
|
119
|
+
set_locrec_col :notes, "#{current_status}: #{exc.to_s}"
|
120
|
+
end
|
121
|
+
|
122
|
+
if locrec_columns[:sub_status] && exc.respond_to?(:sub_status)
|
123
|
+
set_locrec_col :sub_status, exc.sub_status.to_s
|
124
|
+
end
|
125
|
+
|
126
|
+
save!
|
127
|
+
end
|
128
|
+
rescue Exception => e2
|
129
|
+
log_locrec_error(:level => :fatal, :msg => "fatal fatal error in #{self.class}", :exception => e2)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def log_locrec_error(options={})
|
134
|
+
message = options[:msg]||"ERROR LOCKING INSTANCE #{self}"
|
135
|
+
message << "\n#{options[:exception].to_s}" if options[:exception]
|
136
|
+
logger.send options[:level]||:error, message
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_locrec_col(col, value)
|
140
|
+
send "#{locrec_columns[col]}=", value
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_locrec_col(col)
|
144
|
+
send locrec_columns[col]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
module ClassMethods
|
149
|
+
|
150
|
+
def lockable_record(options={})
|
151
|
+
self.locrec_options = ActiveRecord::Base.locrec_options.merge(options)
|
152
|
+
[:columns, :status].each do |method|
|
153
|
+
if options[method]
|
154
|
+
self.locrec_options[method] = ActiveRecord::Base.locrec_options[method].merge(options[method])
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def locrec_columns; locrec_options[:columns]; end
|
160
|
+
def locrec_status; locrec_options[:status]; end
|
161
|
+
|
162
|
+
def recover_stale_records
|
163
|
+
update_all(["#{locrec_columns[:status]} = ?", locrec_status[:not_processed]],
|
164
|
+
stale_record_conditions)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def delete_stale_records
|
169
|
+
delete_all stale_record_conditions
|
170
|
+
end
|
171
|
+
|
172
|
+
def stale_record_conditions
|
173
|
+
query = []
|
174
|
+
query << "#{locrec_columns[:status]} = :status"
|
175
|
+
query << "#{locrec_columns[:processed_on]} < now() - interval :min minute"
|
176
|
+
map = {:status => locrec_status[:processing],
|
177
|
+
:min => locrec_options[:recover_stalled_minutes]}
|
178
|
+
[query.join(' AND '), map]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
ActiveRecord::Base.extend SmsOnRails::LockableRecord unless defined?(ActiveRecord::Base.locrec_options)
|
185
|
+
|
186
|
+
|