Module: ActsAsReplaceable::HelperMethods
- Defined in:
- lib/acts_as_replaceable/acts_as_replaceable.rb
Class Method Summary collapse
- .copy_attributes(attributes, source, target) ⇒ Object
-
.find_existing(record) ⇒ Object
Searches the database for an existing copies of record.
- .insensitive_match_conditions(record) ⇒ Object
-
.lock(record, timeout = 20) ⇒ Object
A lock is used to prevent multiple threads from executing the same query simultaneously eg.
-
.lock_if(condition, *lock_args, &block) ⇒ Object
Conditionally lock (lets us enable or disable locking).
-
.mark_changes(record, existing) ⇒ Object
Copy attributes to existing and see how it would change if we updated it Mark all record’s attributes that have changed, so even if they are still default values, they will be saved to the database.
-
.match_conditions(record) ⇒ Object
Search the incoming attributes for attributes that are in the replaceable conditions and use those to form an Find conditions.
- .sanitize_attribute_names(klass, *args) ⇒ Object
Class Method Details
.copy_attributes(attributes, source, target) ⇒ Object
87 88 89 90 91 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 87 def self.copy_attributes(attributes, source, target) attributes.each do |attribute| target[attribute] = source[attribute] end end |
.find_existing(record) ⇒ Object
Searches the database for an existing copies of record
94 95 96 97 98 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 94 def self.find_existing(record) existing = record.class.default_scoped existing = existing.where match_conditions(record) existing = existing.where insensitive_match_conditions(record) end |
.insensitive_match_conditions(record) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 60 def self.insensitive_match_conditions(record) sql = [] binds = [] record.[:insensitive_match].each do |attribute_name| if value = record[attribute_name] sql << "LOWER(#{attribute_name}) = ?" binds << record[attribute_name].downcase else sql << "#{attribute_name} IS NULL" end end return nil if sql.empty? return [sql.join(' AND ')] + binds end |
.lock(record, timeout = 20) ⇒ Object
A lock is used to prevent multiple threads from executing the same query simultaneously eg. In a multi-threaded environment, ‘find_or_create’ is prone to failure due to the possibility that the process is preempted between the ‘find’ and ‘create’ logic
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 112 def self.lock(record, timeout = 20) lock_id = "ActsAsReplaceable/#{OpenSSL::Digest::MD5.digest([match_conditions(record), insensitive_match_conditions(record)].inspect)}" acquired = false # Acquire the lock by atomically incrementing and returning the value to see if we're first while !acquired do unless acquired = Rails.cache.increment(lock_id) == 1 puts "lock was in use #{lock_id}" sleep(0.250) end end # Reserve the lock for only 10 seconds more than the timeout to ensure a lock is always eventually released Rails.cache.write(lock_id, "1", :raw => true, :expires_in => timeout + 10) Timeout::timeout(timeout) do yield end ensure # Give up the lock Rails.cache.write(lock_id, "0", :raw => true) if acquired end |
.lock_if(condition, *lock_args, &block) ⇒ Object
Conditionally lock (lets us enable or disable locking)
101 102 103 104 105 106 107 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 101 def self.lock_if(condition, *lock_args, &block) if condition lock(*lock_args, &block) else yield end end |
.mark_changes(record, existing) ⇒ Object
Copy attributes to existing and see how it would change if we updated it Mark all record’s attributes that have changed, so even if they are still default values, they will be saved to the database
79 80 81 82 83 84 85 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 79 def self.mark_changes(record, existing) copy_attributes(record.attribute_names, record, existing) existing.changed.each {|attribute| record.send("#{attribute}_will_change!") } return existing.changed? end |
.match_conditions(record) ⇒ Object
Search the incoming attributes for attributes that are in the replaceable conditions and use those to form an Find conditions
52 53 54 55 56 57 58 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 52 def self.match_conditions(record) output = {} record.[:match].each do |attribute_name| output[attribute_name] = record[attribute_name] end return output end |
.sanitize_attribute_names(klass, *args) ⇒ Object
42 43 44 45 46 47 48 49 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 42 def self.sanitize_attribute_names(klass, *args) unless klass.table_exists? ActiveRecord::Base.logger.warn "(acts_as_replaceable) table `#{klass.table_name}` does not exist so excluding all attribute names" return [] end # Intersect the proposed attributes with the column names so we don't start assigning attributes that don't exist. e.g. if the model doesn't have timestamps klass.column_names & args.flatten.compact.collect(&:to_s) end |