blythedunham-sms_on_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/Manifest +101 -0
  3. data/README +163 -0
  4. data/README.rdoc +211 -0
  5. data/Rakefile +76 -0
  6. data/app/controllers/admin/sms_on_rails/base_controller.rb +11 -0
  7. data/app/controllers/admin/sms_on_rails/drafts_controller.rb +75 -0
  8. data/app/controllers/admin/sms_on_rails/outbounds_controller.rb +117 -0
  9. data/app/controllers/admin/sms_on_rails/phone_carriers_controller.rb +85 -0
  10. data/app/controllers/admin/sms_on_rails/phone_numbers_controller.rb +101 -0
  11. data/app/controllers/sms_on_rails/creation_support.rb +99 -0
  12. data/app/controllers/sms_on_rails_controller.rb +14 -0
  13. data/app/helpers/admin/sms_on_rails/drafts_helper.rb +2 -0
  14. data/app/helpers/admin/sms_on_rails/phone_carriers_helper.rb +2 -0
  15. data/app/helpers/sms_on_rails/phone_numbers_helper.rb +9 -0
  16. data/app/helpers/sms_on_rails/sms_helper.rb +44 -0
  17. data/app/models/sms_on_rails/draft.rb +9 -0
  18. data/app/models/sms_on_rails/outbound.rb +17 -0
  19. data/app/models/sms_on_rails/phone_carrier.rb +14 -0
  20. data/app/models/sms_on_rails/phone_number.rb +8 -0
  21. data/app/views/admin/sms_on_rails/base/index.html.erb +5 -0
  22. data/app/views/admin/sms_on_rails/drafts/_show.html.erb +34 -0
  23. data/app/views/admin/sms_on_rails/drafts/edit.html.erb +36 -0
  24. data/app/views/admin/sms_on_rails/drafts/index.html.erb +32 -0
  25. data/app/views/admin/sms_on_rails/drafts/new.html.erb +34 -0
  26. data/app/views/admin/sms_on_rails/drafts/send_sms.html.erb +3 -0
  27. data/app/views/admin/sms_on_rails/drafts/show.html.erb +4 -0
  28. data/app/views/admin/sms_on_rails/outbounds/edit.html.erb +68 -0
  29. data/app/views/admin/sms_on_rails/outbounds/index.html.erb +37 -0
  30. data/app/views/admin/sms_on_rails/outbounds/new.html.erb +54 -0
  31. data/app/views/admin/sms_on_rails/outbounds/show.html.erb +69 -0
  32. data/app/views/admin/sms_on_rails/phone_carriers/edit.html.erb +24 -0
  33. data/app/views/admin/sms_on_rails/phone_carriers/index.html.erb +24 -0
  34. data/app/views/admin/sms_on_rails/phone_carriers/new.html.erb +22 -0
  35. data/app/views/admin/sms_on_rails/phone_carriers/show.html.erb +24 -0
  36. data/app/views/admin/sms_on_rails/phone_numbers/edit.html.erb +33 -0
  37. data/app/views/admin/sms_on_rails/phone_numbers/index.html.erb +28 -0
  38. data/app/views/admin/sms_on_rails/phone_numbers/new.html.erb +31 -0
  39. data/app/views/admin/sms_on_rails/phone_numbers/show.html.erb +32 -0
  40. data/app/views/layouts/sms_on_rails/basic.html.erb +26 -0
  41. data/app/views/sms_on_rails/_phone_carrier_form_item.html.erb +6 -0
  42. data/app/views/sms_on_rails/_send_sms.html.erb +33 -0
  43. data/app/views/sms_on_rails/index.html.erb +8 -0
  44. data/app/views/sms_on_rails/send_sms.html.erb +3 -0
  45. data/app/views/sms_on_rails/show.html.erb +29 -0
  46. data/config/routes.rb +19 -0
  47. data/db/data/fixtures/sms_phone_carriers.yml +110 -0
  48. data/db/migrate/sms_on_rails_carrier_tables.rb +9 -0
  49. data/db/migrate/sms_on_rails_model_tables.rb +48 -0
  50. data/db/migrate/sms_on_rails_phone_number_tables.rb +11 -0
  51. data/db/seed_data.rb +16 -0
  52. data/generators/sms_on_rails/USAGE +31 -0
  53. data/generators/sms_on_rails/commands/inserts.rb +63 -0
  54. data/generators/sms_on_rails/commands/timestamps.rb +33 -0
  55. data/generators/sms_on_rails/runners/add_all_models.rb +6 -0
  56. data/generators/sms_on_rails/runners/dependencies.rb +1 -0
  57. data/generators/sms_on_rails/runners/remove_all_models.rb +5 -0
  58. data/generators/sms_on_rails/runners/sms_on_rails_routes.rb +14 -0
  59. data/generators/sms_on_rails/sms_on_rails_generator.rb +255 -0
  60. data/generators/sms_on_rails/templates/configuration/clickatell.rb +6 -0
  61. data/generators/sms_on_rails/templates/configuration/email_gateway.rb +7 -0
  62. data/generators/sms_on_rails/templates/migrate/schema_migration.rb +15 -0
  63. data/generators/sms_on_rails/templates/migrate/sms_on_rails_update_phone_numbers.rb +40 -0
  64. data/generators/sms_on_rails/templates/phone_number_collision.rb +2 -0
  65. data/init.rb +3 -0
  66. data/install.rb +1 -0
  67. data/lib/sms_on_rails.rb +8 -0
  68. data/lib/sms_on_rails/activerecord_extensions/acts_as_deliverable.rb +92 -0
  69. data/lib/sms_on_rails/activerecord_extensions/acts_as_substitutable.rb +80 -0
  70. data/lib/sms_on_rails/activerecord_extensions/has_a_sms_service_provider.rb +101 -0
  71. data/lib/sms_on_rails/activerecord_extensions/lockable_record.rb +186 -0
  72. data/lib/sms_on_rails/all_models.rb +3 -0
  73. data/lib/sms_on_rails/model_support/draft.rb +178 -0
  74. data/lib/sms_on_rails/model_support/outbound.rb +136 -0
  75. data/lib/sms_on_rails/model_support/phone_carrier.rb +77 -0
  76. data/lib/sms_on_rails/model_support/phone_number.rb +248 -0
  77. data/lib/sms_on_rails/model_support/phone_number_associations.rb +13 -0
  78. data/lib/sms_on_rails/schema_helper.rb +51 -0
  79. data/lib/sms_on_rails/service_providers/base.rb +222 -0
  80. data/lib/sms_on_rails/service_providers/clickatell.rb +52 -0
  81. data/lib/sms_on_rails/service_providers/dummy.rb +19 -0
  82. data/lib/sms_on_rails/service_providers/email_gateway.rb +68 -0
  83. data/lib/sms_on_rails/service_providers/email_gateway_support/errors.rb +20 -0
  84. data/lib/sms_on_rails/service_providers/email_gateway_support/sms_mailer.rb +21 -0
  85. data/lib/sms_on_rails/service_providers/email_gateway_support/sms_mailer/sms_through_gateway.erb +6 -0
  86. data/lib/sms_on_rails/util/sms_error.rb +12 -0
  87. data/lib/smsonrails.rb +1 -0
  88. data/public/images/sms_on_rails/railsYoDawg.jpg +0 -0
  89. data/public/stylesheets/sms_on_rails.css +137 -0
  90. data/sms_on_rails.gemspec +32 -0
  91. data/tasks/sms_on_rails_tasks.rake +67 -0
  92. data/test/active_record_extensions/delivery_and_locking_test.rb +84 -0
  93. data/test/models/draft_test.rb +72 -0
  94. data/test/models/outbound_test.rb +89 -0
  95. data/test/models/phone_number_test.rb +131 -0
  96. data/test/run.rb +18 -0
  97. data/test/service_providers/abstract_test_support.rb +104 -0
  98. data/test/service_providers/clickatell_test.rb +39 -0
  99. data/test/service_providers/email_gateway_test.rb +30 -0
  100. data/test/test_helper.rb +24 -0
  101. data/uninstall.rb +1 -0
  102. metadata +187 -0
@@ -0,0 +1,6 @@
1
+ SmsOnRails::ServiceProviders::Clickatell.config =
2
+ {
3
+ :api_id => 'api-key',
4
+ :user_name => 'username',
5
+ :password => 'password'
6
+ }
@@ -0,0 +1,7 @@
1
+ SmsOnRails::ServiceProviders::EmailGateway.config =
2
+ {
3
+ :sender => 'youremail address',
4
+ :subject => 'Default Subject Text'
5
+ #:bcc => nil,
6
+ #:mailer_klass => nil
7
+ }
@@ -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
@@ -0,0 +1,2 @@
1
+ require "#{RAILS_ROOT}/app/models/phone_number"
2
+ SmsOnRails::PhoneNumber = ::PhoneNumber
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/lib/sms_on_rails'
2
+
3
+
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -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
+