Module: ActsAsReplaceable::HelperMethods

Defined in:
lib/acts_as_replaceable/acts_as_replaceable.rb

Class Method Summary collapse

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.acts_as_replaceable_options[: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.acts_as_replaceable_options[: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