Read only mode for Ruby on Rails application - ruby-on-rails

I have an interactive Ruby on Rails application which I would like to put into a "read only mode" during certain times. This will allow users to read the data they need but block them from actions that write to the database.
One way to do this would be to have a true/false variable located in the database that was checked before any write was made.
My question. Is there a more elegant solution for this problem out there?

If you really want to prevent any database write, the easiest way I can imagine would be to override the readonly? method of a model to always return true, either in selected models or maybe even for all ActiveRecord models. If a model is set to readonly (normally done by calling #readonly! on it), any try to save the record will raise an ActiveRecord::ReadOnlyRecord error.
module ActiveRecord
class Base
def readonly?
true
end
end
end
(actually untested code, but you get the idea…)

Zargony's solution seems to be the best one, but I would like to add to it a bit.
So, about his code:
This works nicely. A good solution is to add this in an initializer and run this code only if an env var is set, so that you can choose whether to run the app in read-only mode on launching the app.
if ENV['READ_ONLY'] == 'true'
module ActiveRecord
class Base
def readonly?
true
end
end
end
end
And then run the server from command prompt like READ_ONLY=true bin/rails s. Also, adding
rescue_from ActiveRecord::ReadOnlyRecord, with: ->() {
flash[:alert] = "The site is running in read-only mode. We are going to return to full operation soon. Thank you for your patience!"
redirect_to root_path
}
to the ApplicationController (that all of your controllers should inherit from) is a nice way to show the users what is going on.

Another good one which I liked a little better is Declarative Authorization
which is covered by Railscasts as well: Railscasts - Declarative Authorization

Permissions plugin? Something simple like cancan where you define what a user can do, when. It will allow you to display links, or not, and restrict access to controller actions. The railscast will explain better than I can.
http://github.com/ryanb/cancan
http://railscasts.com/episodes/192-authorization-with-cancan

The answer by Zargony will work well but raise an exception if your application is trying to write anything. If you want your application to fail silently on writes so that it doesn't show error pages on each operation (e.g. if you update a timestamp on login in your code, you will get an exception), you can use the approach below:
unless Rails.env.test?
class ActiveRecord::Base
before_save do
raise ActiveRecord::Rollback, "Read-only"
end
before_destroy do
raise ActiveRecord::Rollback, "Read-only"
end
end
end

Related

How to limit who can call a method in ruby

I'm looking for a good ruby way to limit who can call a method. The example occurs within a rails environment but its not specific to rails.
I have a Model called Document that handles finding documents within folders. Folders are permissioned so I have a class that handles Permissions. In heavily simplified form Permissions exposes the interface to retrieve Documents:
class Permissions
def documents(folder_list)
#strip folders from folder_list user doesn't have permissions for
Document.where("folder.name in (?)", permissioned_folder_list)
end
end
Functionally this works fine but rspec testing is a nightmare when the querying is much more complex than this simplified example. We end up with lots of expectations that are to do with the mechanics of how Documents are stored. Really I want to have something more like this:
class Permissions
def documents(folder_list)
#strip folders from folder_list user doesn't have permissions for
Document.documents(permissioned_folder_list)
end
end
class Document
self.documents(folder_list)
Document.where("folder.name in (?)", folder_list)
end
end
Which would be nicely factored and easy to test. The trouble is this now provides an interface on Document which looks like a nice domain-level interface but completely bypasses permissions. Its very easy for someone to come along and use this and get reasonable-looking results which are wholly incorrect.
What I'd like to do is prevent the Document::documents method from being called by anything other than an instance of Permissions. Its not foolproof as you can still call all or where and bypass permissions but then you have to recreate all the complex query logic when there is a method that clearly handles all that for you, but you can't call it unless you go through Permissions.
Whats the idiomatic ruby way of doing something like this? BTW, because permissions are stored in the user's sesion session, calling Document and having it call Permissions rather than calling Permissions and having it call Document is pretty messy.
Is there some reason the private keyword is not sufficient?
You seem to not want another class for the private methods. Why not just make them instance methods that follow the private declaration?
If you need to use session from somewhere other than a controller, pass it as an argument.
class Permissions
def documents(folder_list, session)
_documents(folder_list, session)
end
private
def _documents(folder_list, session)
Document.where("folder.name in (?)", folder_list)
end
end
Not sure if I'm missing some part of your question.

Override redirect_to in rails

I use an engine in my rails app that logins the user and redirects to a service param (it's a CAS engine). However, from the host app I want to redirect the user (after he/she has logged in) in a different location sometimes depending on the params. At the moment I can't get it work because rails permits only 1 call of redirect_to/render. The engine inherits from my rails app ApplicationController.
How can I override/redefine redirect_to to call it multiple times?
The problem might be solved in other ways but I really don't want them. I have tried some of them but none can compete with the simplicity of just letting the last defined redirect_to take action.
I'm only interested in solutions that involve redefining redirect_to so that I can invoke it multiple times.
Of course you can "override" it. You can redefine any method in any object at any point in Ruby. However, this is a terrible idea. You should fix your problem, which is that you're calling redirect_to twice, instead of hacking apart Rails in order to allow your problem to continue.
If you're still set on "fixing" this the wrong way, find the source code (this was trivially easy to do), copy it into an initializer/library file of your own, and make the modifications.
module ActionController::Redirecting
def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
self.response_body = "<html><body>You are being redirected.</body></html>"
end
end
If you really want to do this, despite being forewarned that it is the wrong solution to your problem and that you're fundamentally altering behavior of Rails that other things may depend on, comment out the line that raises a DoubleRenderError.
It seems it was much easier than I thought. All you need to do is to explicitly modify the response object.
Thus you could declare the following function in ApplicationController:
def re_redirect_to(location, status = 303)
response.location = location
response.status = status
end
That's it basically. Elegant and simple.
As I said in the first post:
I use an engine in my rails app that logins the user and redirects to a service param (it's a CAS engine). However, from the host app I want to redirect the user (after he/she has logged in) in a different location sometimes depending on the params. At the moment I can't get it work because rails permits only 1 call of redirect_to/render. The engine inherits from my rails app ApplicationController.
So basically I had no other option than override the engine's redirect_to in an after_action in ApplicationController. I believe it's much better and more maintainable solution than modifying the engine's source code.
I would like to note here that it's absolutely good to follow the conventions. Definitely calling redirect_to more than once should be avoided in 99% cases. But it's good to know that there is a way to deal with that other 1%. Conventions are just conventions.
This worked for me, in the application controller, override redirect, do your thing, then call super:
class ApplicationControler < ... #depends on your rails version
def redirect_to(options = {}, response_status = {})
# do your custom thing here
super # and call the default rails redirect
Hope this helps,
Kevin

Rails best practices - Controller or model?

I want to use this piece of code to retrieve a user's list of credit cards on file with Stripe to show on his profile (/users/:id)
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
Thing is, I'm not exactly sure where (in terms of Rails best practices) it fits. My first tought is to put it in the show method of the User controller since it's not really business logic and doesn't fit in the model. I've also looked at helper methods but they seem (from my understanding) to be used strictly when toying around with HTML.
Can any of you Rails experts chime in?
Thanks!
Francis
Good question. Whenever you see an instance variable in rails (starting with a #), it usually is a view/controller bit of code.
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
However looking at the tail end of that
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
This might fit better of in a model, where you can reuse that same line, but have the safety of added error handling and predictable behavior. For example
# user.rb
def stripe_customer_cards
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
rescue Stripe::InvalidRequestError
false # You could use this to render some information in your views, without breaking your app.
end
Also note the use of self. This usually implies use of a Rails model, because calling self in the controller actually refers to the controller, rendering it almost worthless, unless you really know what you are doing.
EDIT
To render an error message, simply write a call to redirect or render, with the alert option.
if #stripe_cards = current_user.stripe_customer_cards
# Your being paid, sweet!
else
# Render alert info :(
render 'my_view', alert: 'This is an alert'
redirect_to other_path, alert: 'Another alert'
end
I also like to make it a point to mention that you should not handle errors just because you can. Don't handle errors you don't expect. If you handle errors you don't expect it will
Confuse users
Make bugs in code harder to fix
Exaggerate the time before an error is recognized
I'd recommend adding a virtual attribute in your User model:
# app/models/user.rb
def cards
Stripe::Customer.retrieve(stripe_customer_id).cards.all # note the spelling of `retrieve`
end
Then, you'd be able to access all a users cards in the following manner:
user = User.first
#=> #<User id:1>
user.cards
#=> [Array of all cards]

Is it safe to put reference to current user in User model in Rails?

You know, I think I have to check current user in the model callbacks (like before_update). Rather than rely solely on adding where ('something.user_id = ?', 'current_user.id') in the controllers. I need something like Thread.CurrentPrincipal in .NET
Is it safe to put reference to current user in User model? I'm sorry I don't really understand how it works under the hood yet.
Or how you do it The Rails way?
Sorry if this a silly question.
Added on 3/27
Oops
To get the right answer you have to ask the right question. And that's not an easy task in itself. How can it be that other people's questions are so obscure and they get their answers and your own question is so clear-cut but nobody understands it? :)
I do not understand where to put security check. That the user will get access only to his own stuff. On the controller level? And then test every action? "should not /view|create|edit|destroy/ other user's stuff"? I thought may be I can put it in the model and have one place to /write|refactor|test/. That's why I asked about how I can get a reference to the current user.
Actually I'm surprised I didn't find anything relevant in Rails Guides or Blogs. Some questions were asked but no authoritative "best practices" besides "don't do it".
After giving some thought I decided to just create in the controller before_filter the scope scoped to the current user and just rely on my own convention (promised to myself that I won't access the model directly). And just test it once per controller. That's not a banking application anyway.
I'm not sure I get your situation. If you want to check if some other model's instance belongs to current user - use associations (I inferred that from "something.user_id = ?").
Else - in ActiveRecord before_update method is used on a per-instance basis. I.e. you pass current instance to that callback as an argument. Hence:
def before_update(current_user_instance)
current_user_instance.do_something
end
Will yield any user instance as current_user_instance in the callback. So you can do following:
>> user_1 = User.find(1)
>> user_1.update_attribute(:some_attribute, 'some value')
>> user_2 = User.find(2)
>> user_2.update_attribute(:some_attribute, 'some other value')
This will call do_something method on separate instances (user_1 and user_2)
I don't really understand Thread.CurrentPrincipal, but current_user generally means the logged in user and that is completely a controller context. It is not available for use inside a model. So a hacky solution would be:
class UseCase < ActiveRecord::Base
after_save :my_callback_method
attr_accessor :current_user
def my_callback_method
# Some operations based on current_user
end
# ...
end
And then in your controller:
#...
use_case = UseCase.find(use_case_id) # Just an example, can be anything
use_case.current_user = current_user
# then this
use_case.save
# or basically
use_case.method_that_triggers_after_save_callback
#...
Warning: I am sure this is bad practise (I have never used it myself). But this will work. Ruby gurus / MVC gurus out there, please comment.

Can I "Unload" a Rails Metal Class after one time use?

I've got a Rails metal class that basically does a one time check to make sure there is an admin user in the system, if not, it redirects user to create one. Right now I'm using the rack session to prevent double checking but that seems to have the following problems:
Still does one time check for each session
Slows things down by checking the session in the first place.
I wonder if its possible to direct Rails to "remove" or "unload" the class from the chain. Can this be done safely? Any other suggestions?
A simplest solution (although not quite as clean) would be to store the fact that an admin user exists in the class.
class EnsureAdminUser
def self.call(env)
if #admin_defined or Admin.any?
#admin_defined = true
[404, {"Content-Type" => "text/html"}, "Not Found"]
else
…
end
end
end
This saves you the DB hit on each request.
To actually delete the metal you will need to do something a bit more radical (and evil):
ObjectSpace.each_object(Rails::Rack::Metal){|metal_handler|
metal_handler.instance_eval{ #metals.delete(EnsureAdminUser) }
}

Categories

Resources