Class: ApplicationRecord

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
WithPgLock
Defined in:
app/models/application_record.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods included from WithPgLock

#with_pg_lock

Class Method Details

.active_between(start_date_field, end_date_field, **options) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/models/application_record.rb', line 154

def self.active_between(start_date_field, end_date_field, **options)
  define_singleton_method(:active) do |actually_filter=true|
    if actually_filter
      self.where("(#{start_date_field} IS NULL OR #{start_date_field} < :now) AND (#{end_date_field} IS NULL OR #{end_date_field} > :now)", now: Time.current)
    else
      all
    end
  end

  aliased_as = options.delete(:aliased_as)
  singleton_class.send(:alias_method, aliased_as, :active) if aliased_as
end

.aggregate_of(association) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/models/application_record.rb', line 79

def self.aggregate_of(association)
  class_eval do
    define_method("rebuild_#{association}!") do |children|
      transaction do
        self.send(association).all_except(children).destroy_all
        self.update! association => children
        children.each &:save!
      end
      reload
    end
  end
end

.all_except(others) ⇒ Object



23
24
25
26
27
28
29
# File 'app/models/application_record.rb', line 23

def self.all_except(others)
  if others.present?
    where.not(id: [others.map(&:id)])
  else
    all
  end
end

.defaults(&block) ⇒ Object



18
19
20
21
# File 'app/models/application_record.rb', line 18

def self.defaults(&block)
  after_initialize :defaults, if: :new_record?
  define_method :defaults, &block
end

.enum_prefixed_translations_for(selector) ⇒ Object



178
179
180
181
182
# File 'app/models/application_record.rb', line 178

def self.enum_prefixed_translations_for(selector)
  send(selector.to_s.pluralize).map do |key, _|
    [I18n.t("#{selector}_#{key}", default: key.to_sym), key]
  end
end

.numbered(*associations) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# File 'app/models/application_record.rb', line 123

def self.numbered(*associations)
  class_eval do
    associations.each do |it|
      define_method("#{it}=") do |e|
        e.merge_numbers!
        super(e)
      end
    end
  end
end

.organic_on(*selectors) ⇒ Object



146
147
148
149
150
151
152
# File 'app/models/application_record.rb', line 146

def self.organic_on(*selectors)
  selectors.each do |selector|
    define_method("#{selector}_in_organization") do |organization = Organization.current|
      send(selector).where(organization: organization)
    end
  end
end

.resource_fields(*fields) ⇒ Object

Partially implements resource-hash protocol, by defining ‘to_resource_h` and helper methods `resource_fields` and `slice_resource_h` using the given fields



170
171
172
173
174
175
176
# File 'app/models/application_record.rb', line 170

def self.resource_fields(*fields)
  include Mumuki::Domain::Syncable::WithResourceFields

  define_singleton_method :resource_fields do
    fields
  end
end

.serialize_symbolized_hash_array(*keys) ⇒ Object



31
32
33
34
35
36
# File 'app/models/application_record.rb', line 31

def self.serialize_symbolized_hash_array(*keys)
  keys.each do |field|
    serialize field
    define_method(field) { self[field]&.map { |it| it.deep_symbolize_keys } }
  end
end

.teaser_on(*args) ⇒ Object



8
9
10
11
12
13
14
15
16
# File 'app/models/application_record.rb', line 8

def self.teaser_on(*args)
  args.each do |selector|
    teaser_selector = "#{selector}_teaser"
    define_method teaser_selector do
      send(selector)&.markdown_paragraphs&.first
    end
    markdown_on teaser_selector, skip_sanitization: true
  end
end

.update_or_create!(attributes) ⇒ Object



134
135
136
137
138
# File 'app/models/application_record.rb', line 134

def self.update_or_create!(attributes)
  obj = first || new
  obj.update!(attributes)
  obj
end

.whitelist_attributes(a_hash, options = {}) ⇒ Object



140
141
142
143
144
# File 'app/models/application_record.rb', line 140

def self.whitelist_attributes(a_hash, options = {})
  attributes = attribute_names
  attributes += reflections.keys if options[:relations]
  a_hash.with_indifferent_access.slice(*attributes).except(*options[:except])
end

.with_pg_retry(&block) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'app/models/application_record.rb', line 108

def self.with_pg_retry(&block)
  retries ||= 0
  transaction(&block)
rescue ActiveRecord::StatementInvalid => e
  retries += 1

  raise e if retries > 2
  if %w(PG::ConnectionBad PG::UnableToSend).any? { |it| e.message.include? it }
    warn "Postgres connection failed. Retrying in 5 seconds..."
    sleep 5
    ActiveRecord::Base.connection.verify!
    retry
  end
end

.with_temporary_token(field_name, duration = 2.hours) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'app/models/application_record.rb', line 92

def self.with_temporary_token(field_name, duration = 2.hours)
  class_eval do
    token_attribute = field_name
    token_date_attribute = "#{field_name}_expiration_date"

    define_method("generate_#{field_name}!") do
      update!(token_attribute => self.class.generate_secure_token, token_date_attribute => duration.from_now)
    end

    define_method("#{field_name}_matches?") do |token|
      actual_token = attribute(token_attribute)
      actual_token.present? && token == actual_token && attribute(token_date_attribute)&.future?
    end
  end
end

Instance Method Details

#deleteObject



62
63
64
65
66
# File 'app/models/application_record.rb', line 62

def delete
  super
rescue ActiveRecord::InvalidForeignKey => e
  raise_foreign_key_error!
end

#destroy!Object



53
54
55
56
57
58
59
60
# File 'app/models/application_record.rb', line 53

def destroy!
  super
rescue ActiveRecord::RecordNotDestroyed => e
  errors[:base].last.try { |it| raise ActiveRecord::RecordNotDestroyed.new it }
  raise e
rescue ActiveRecord::InvalidForeignKey => e
  raise_foreign_key_error!
end

#saveObject



38
39
40
41
42
43
# File 'app/models/application_record.rb', line 38

def save(*)
  super
rescue => e
  self.errors.add :base, e.message
  self
end

#save_and_notify!Object



68
69
70
71
72
# File 'app/models/application_record.rb', line 68

def save_and_notify!
  save!
  notify!
  self
end

#save_and_notify_changes!Object



45
46
47
48
49
50
51
# File 'app/models/application_record.rb', line 45

def save_and_notify_changes!
  if changed?
    save_and_notify!
  else
    save!
  end
end

#update_and_notify!(data) ⇒ Object



74
75
76
77
# File 'app/models/application_record.rb', line 74

def update_and_notify!(data)
  assign_attributes data
  save_and_notify!
end