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.
Files changed (103) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/Manifest +101 -0
  3. data/README.rdoc +211 -0
  4. data/Rakefile +109 -0
  5. data/VERSION +1 -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 +54 -0
  81. data/lib/sms_on_rails/service_providers/dummy.rb +21 -0
  82. data/lib/sms_on_rails/service_providers/email_gateway.rb +70 -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/smsonrails.gemspec +159 -0
  91. data/sztywny-smsonrails.gemspec +148 -0
  92. data/tasks/sms_on_rails_tasks.rake +68 -0
  93. data/test/active_record_extensions/delivery_and_locking_test.rb +84 -0
  94. data/test/models/draft_test.rb +72 -0
  95. data/test/models/outbound_test.rb +89 -0
  96. data/test/models/phone_number_test.rb +131 -0
  97. data/test/run.rb +19 -0
  98. data/test/service_providers/abstract_test_support.rb +104 -0
  99. data/test/service_providers/clickatell_test.rb +37 -0
  100. data/test/service_providers/email_gateway_test.rb +30 -0
  101. data/test/test_helper.rb +24 -0
  102. data/uninstall.rb +1 -0
  103. metadata +193 -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
+
data/install.rb ADDED
@@ -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
+