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
81 82 83 84 85 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 81 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
88 89 90 91 92 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 88 def self.find_existing(record) existing = record.class existing = existing.where match_conditions(record) existing = existing.where insensitive_match_conditions(record) end |
.insensitive_match_conditions(record) ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 56 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 [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
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 106 def self.lock(record, timeout = 20) lock_id = "ActsAsReplaceable/#{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)
95 96 97 98 99 100 101 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 95 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
73 74 75 76 77 78 79 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 73 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
48 49 50 51 52 53 54 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 48 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 |
# File 'lib/acts_as_replaceable/acts_as_replaceable.rb', line 42 def self.sanitize_attribute_names(klass, *args) # 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 |