Make acts_as_paranoid finders exclude "deleted" objects conditionally - ruby-on-rails

Normally, with acts_as_paranoid, when you use a finder such as find or where, it excludes entries that have a deleted_at (or whatever you named your delete column) value other than null. However, I have created a user permission that allows users to view and manipulate even "deleted" (see: hidden) entities in the database. Thing is, as it stands, I will need to check for the permission every time I want to run find, or where, or what-have-you, and if you have that permission use with_deleted as opposed to having one place that checks to see if the acts_as_paranoid logic should even work to begin with, and always adding with_deleted when that logic shouldn't.
Is there any way to accomplish this, or something similar to it, to make it all DRY?

You can use cancan. This allows you to define abilities.
You can then define:
if user.admin?
can :manage, User.with_deleted
else
can :manage, User
end
Then you can select in the controller, using:
User.accessible_by(current_ability)

Related

Force filter on user_id

I made a new Rails project and added Devise for user management. I also made tables like 'posts' and 'tags' and they have a field 'user_id' because the data is per user.
Now I can make queries where I always include user_id as filter. This works fine, but I'm afraid that someday I will forget to filter on user_id and a user can see data of other users.
Is there a way in models to force a certain filter?
For some models like 'posts' and 'tags' I would like to always filter on the current_user. Is there a way to automatically do this or maybe raise an exception if I forget to filter on user?
Any tips are welcome.
(I could use something like Apartment, but I prefer a single database/schema for now)
In your proxy method for params, you can use the require method to require the user_id field. Thus if you restrain from using params directly, as everybody should, your contraint will be enforced.
Another way is to have a before_action filter, where you check your condition. That way, unless you purposefully exclude a method from this filter, your check will always be enforced (or 422 will be returned).
Putting a condition on the model itself seems wrong to me: the model should not know what the access conditions are because access control is an orthogonal feature and should not be entangled with the model.

Ruby - Ensure only one class object

I have a Model Bot and I would like to ensure that there is only one Bot object in my database. I also need to make sure it is persisted and not tampered with.
My original thought was to do this in a migration, one that would follow the :bots table migration. It would include a line that is something like:
Bot.all.size == 0 ? Bot.create! : nil
Maybe this would prevent the AR object from being messed with in future migrations?
BONUS: Would be awesome to be able to have instant and global access to this class object. I was thinking using a singleton module in my Bot class that way I can always reference Bot.instance and have access to that specific object.
USE CASE:
I have 4 types of users in my DB and this bot will be the facilitator to delivery role-specific messages to them through our in-app messaging feature.
The Class Bot will have a has_many association with BotMessage/bot_messages. On the bot_messages table will be an enum field for user_role.
Messages will be created by company admins and stored in these tables because we want them to be viewable at any time by looking at the "conversation" thread between the User and the Bot.
When it comes to only having 1 bot, it's just that. I have no need for an additional Bot object. Additionally, since there is only one object it would be nice to be able to have a way of explicitly targeting that object without having to run a query to find it.
For example, unlike User where there could be 1000 records and in order to find the specific one you would do something like #user = User.find_by_email('foo#bar.com'), doing something like that for the bot would be unnecessary since there is only one record to find. That is what lead me to believe having a singleton object may be worthwhile here, so whenever I need to pull up a message for a specific role, I could run Bot.instance.bot_messages.where(user_role: 1) or something similar
Based on your Use Case, I see no reason for Bot to be a model.
Let's say you have a role called cool_user and you want to get all the bot_messages for that role, you might do something like:
class Bot
class << self
def bot_messages(user_role)
BotMessage.send(user_role)
end
end
end
As a very thoughtful but potentially anonymous super code monkey notes in the comments, you could also do:
class Bot
def self.bot_messages(user_role)
BotMessage.send(user_role)
end
end
Which some folks might find more readable. IMO, it is a bit of an issue of personal preference.
In either case, you should be able to do
Bot.bot_messages(:cool_user)
Since, as stated in the docs,
Scopes based on the allowed values of the enum field will be provided as well.
So, I believe BotMessage, with the properly set enum, should respond to cool_user and return all the bot_messages for that role.
You may need to check the docs to get the syntax exactly right.
I believe this should also satisfy your BONUS requirement.
A proven solution would be to use an STI on User (with a user_type column)
class User < ApplicationRecord
...
end
class Bot < User
has_many :bot_messages, foreign_key: :user_id
end
Is it what you're looking for ?

Model to implement users (Ruby on Rails 3)

I am implementing a User that is not actually deleted from the system with destroy but only marked with :active = false.
The problem here is that such an inactivate user will show up in all User.find, User.all, ... calls. I don't want to pollute the code with all kinds of 'if-else's or overwriting the behavior of .find, .all etc.
I just want to know whether I can nicely define it within the User's model so that inactive users will virtually disappear unless I explicitly want to extract such a user.
If there is no way to do it in the model then what are my options?
Use a scope, or a class method with a where clause.
I think you may want to check acts_as_paranoid Here is a link for one of the implementations: https://github.com/technoweenie/acts_as_paranoid
From the wiki:
Now whenever destroy is called on that model, it is just removed from view and the deleted_at column set to the current date time. All the finder methods ignore “deleted” records.

Record changes pend approval by a privileged user; Its like versioning combined with approvals

I have a requirement that certain attribute changes to records are not reflected in the user interface until those changes are approved. Further, if a change is made to an approved record, the user will be presented with the record as it exists before approval.
My first try...
was to go to a versioning plugin such as paper_trail, acts_as_audited, etc. and add an approved attribute to their version model. Doing so would not only give me the ability to 'rollback' through versions of the record, but also SHOULD allow me to differentiate between whether a version has been approved or not.
I have been working down this train of thought for awhile now, and the problem I keep running into is on the user side. That is, how do I query for a collection of approved records? I could (and tried) writing some helper methods that get a collection of records, and then loop over them to find an "approved" version of the record. My primary gripe with this is how quickly the number of database hits can grow. My next attempt was to do something as follows:
Version.
where(:item_type => MyModel.name, :approved => true).
group(:item_type).collect do |v|
# like the 'reify' method of paper_trail
v.some_method_that_converts_the_version_to_a_record
end
So assuming that the some_method... call doesn't hit the database, we kind of end up with the data we're interested in. The main problem I ran into with this method is I can't use this "finder" as a scope. That is, I can't append additional scopes to this lookup to narrow my results further. For example, my records may also have a cool scope that only shows records where :cool => true. Ideally, I would want to look up my records as MyModel.approved.cool, but here I guess I would have to get my collection of approved models and then loop over them for cool ones would would result in the very least in having a bunch of records initialized in memory for no reason.
My next try...
involved creating a special type of "pending record" that basically help "potential" changes to a record. So on the user end you would lookup whatever you wanted as you normally would. Whenever a pending record is apply!(ed) it would simply makes those changes to the actual record, and alls well... Except about 30 minutes into it I realize that it all breaks down if an "admin" wishes to go back and contribute more to his change before approving it. I guess my only option would be either to:
Force the admin to approve all changes before making additional ones (that won't go over well... nor should it).
Try to read the changes out of the "pending record" model and apply them to the existing record without saving. Something about this idea just doesn't quite sound "right".
I would love someone's input on this issue. I have been wrestling with it for some time, and I just can't seem to find the way that feels right. I like to live by the "if its hard to get your head around it, you're probably doing it wrong" mantra.
And this is kicking my tail...
How about, create an association:
class MyModel < AR::Base
belongs_to :my_model
has_one :new_version, :class_name => MyModel
# ...
end
When an edit is made, you basically clone the existing object to a new one. Associate the existing object and the new one, and set a has_edits attribute on the existing object, the pending_approval attribute on the new one.
How you treat the objects once the admin approves it depends on whether you have other associations that depend on the id of the original model.
In any case, you can reduce your queries to:
objects_pending_edits = MyModel.where("has_edits = true").all
then with any given one, you can access the new edits with obj.new_version. If you're really wanting to reduce database traffic, eager-load that association.

What is the best way to override Rails ActiveRecord destroy behavior?

I have an application where I would like to override the behavior of destroy for many of my models. The use case is that users may have a legitimate need to delete a particular record, but actually deleting the row from the database would destroy referential integrity that affects other related models. For example, a user of the system may want to delete a customer with whom they no longer do business, but transactions with that customer need to be maintained.
It seems I have at least two options:
Duplicate data into the necessarily models effectively denormalizing my data model so that deleted records won't affect related data.
Override the "destroy" behavior of ActiveRecord to do something like set a flag indicating the user "deleted" the record and use this flag to hide the record.
Am I missing a better way?
Option 1 seems like a horrible idea to me, though I'd love to hear arguments to the contrary.
Option 2 seems somewhat Rails-ish but I'm wondering the best way to handle it. Should I create my own parent class that inherits from ActiveRecord::Base, override the destroy method there, then inherit from that class in the models where I want this behavior? Should I also override finder behavior so records marked as deleted aren't returned by default?
If I did this, how would I handle dynamic finders? What about named scopes?
If you're not actually interested in seeing those records again, but only care that the children still exist when the parent is destroyed, the job is simple: add :dependent => :nullify to the has_many call to set references to the parent to NULL automatically upon destruction, and teach the view to deal with that reference being missing. However, this only works if you're okay with not ever seeing the row again, i.e. viewing those transactions shows "[NO LONGER EXISTS]" under company name.
If you do want to see that data again, it sounds like what you want has nothing to do with actually destroying records, which means that you will never need to refer to them again. Hiding seems to be the way to go.
Instead of overriding destroy, since you're not actually destroying the record, it seems significantly simpler to put your behavior in a hide method that triggers a flag, as you suggested.
From there, whenever you want to list these records and only include visible records, one simple solution is to include a visible scope that doesn't include hidden records, and not include it when you want to find that specific, hidden record again. Another path is to use default_scope to hide hidden records and use Model.with_exclusive_scope { find(id) } to pull up a hidden record, but I'd recommend against it, since it could be a serious gotcha for an incoming developer, and fundamentally changes what Model.all returns to not at all reflect what the method call suggests.
I understand the desire to make the controllers look like they're doing things the Rails way, but when you're not really doing things the Rails way, it's best to be explicit about it, especially when it's really not that much of a pain to do so.
I wrote a plugin for this exact purpose, called paranoia. I "borrowed" the idea from acts_as_paranoid and basically re-wrote AAP using much less code.
When you call destroy on a record, it doesn't actually delete it. Instead, it will set a deleted_at column in your database to the current time.
The README on the GitHub page should be helpful for installation & usage. If it isn't, then let me know and I'll see if I can fix that for you.

Resources