-
-
Save dabit/c8c06f0b2fc51219dd6691bf6306a6e0 to your computer and use it in GitHub Desktop.
class ApplicationRecord < ActiveRecord::Base | |
primary_abstract_class | |
def self.inherited(subclass) | |
super | |
return unless subclass.has_attribute?(:deleted_at) | |
setup_for_soft_delete(subclass) | |
rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished | |
nil | |
end | |
def self.setup_for_soft_delete(subclass) | |
subclass.send(:default_scope, -> { where(deleted_at: nil) }) | |
class << subclass | |
def archived | |
where.not(deleted_at: nil) | |
end | |
end | |
subclass.define_method(:destroy) do | |
touch(:deleted_at) | |
end | |
end | |
def self.human_enum_name(enum_name, enum_value) | |
I18n.t("activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}") | |
end | |
def self.plural_name | |
model_name.human(count: 2) | |
end | |
def self.enum_collection_for_select(enum_plural_name) | |
send(enum_plural_name).collect { |k, _v| [k, human_enum_name(enum_plural_name, k)] } | |
end | |
end |
I always add this so I can easily create select tags for "statuses" or "types" of a model (wherever you use enums)
class ApplicationRecord < ActiveRecord::Base
def self.human_enum_collection(enum_name, except: [])
send(enum_name.to_s.pluralize).keys.filter_map do |val|
[human_enum_name(enum_name, val), val] unless except.include?(val)
end.compact
end
end
# Any ApplicationRecord using enums
class Payout < ApplicationRecord
enum status: { requested: 0, in_transit: 1, completed: 2, rejected: 3 }
enum account_type: { checking: 0, vista: 1, savings: 2 }
# ...
end
en:
activerecord:
attributes:
payout:
statuses:
requested: "Solicited"
in_transit: "In transit"
completed: "Completed"
rejected: "Rejected"
account_types:
checking: "Checking acc."
savings: "Savings acc."
vista: "Vista acc."
Use it in selects
<%= f.select :account_type, Payout.human_enum_collection(:account_type) %>
<%= f.select :status, Payout.human_enum_collection(:status, except: [:rejected]) %>
@alejandrodevs I usually prefer to be more explicit and implement a soft_delete
method. I do this in a general purpose concern that I include in all soft_deletable models.
@alejandrodevs My use case is very simple and straightforward; I just want to keep a small selection of tables where I want the data to be deleted but still keep it handy in case someone needs it back. I have been in several situations where the user deletes a record by mistake or simply because they thought they would not need it in the future and then realize that they do, in fact, need it later.
I use the default scope because I don't expect this data to show up anywhere within the app, and I want it to behave as if it's gone forever. I rarely add the functionality to browse this data. I include an archived
scope for my convenience, just in case I want to dig through it via the console.
Adding a deleted_at
column is explicit enough to tell me if a table is soft deletable or not (I religiously use annotate, so I can always see what my tables look like).
I override destroy
because it makes it simpler to turn it on and off just by adding or removing the deleted_at
column.
By the way, I will include your scope-based refactoring in a future iteration; thank you for that. I agree that using scope
is better.
Lastly, I understand there are gems for everything these days, but I personally never add a dependency for something I can fix with a few lines of code.
@dabit Thanks for sharing this. Just a couple of comments.
This part:
could be improved like ( works the same but it is always a good practice to wrap these kind of methods in a scope to ensure it always returns a AR collection):
IMO I wouldn't override AR destroy functionality and I wouldn't add a default_scope either. There could be some side effects doing that.
There was a gem called paranoia that was deprecated because of that (you can read that in the README introduction). Then, discard was created in order to fix that.
There is a section explaining that: https://github.com/jhawthorn/discard#why-not-paranoia-or-acts_as_paranoid.
What do you think?