Why not refer to the session hash in the Model layer? - ruby-on-rails

This question has been repeatedly asked and answered with one-line assertions, such as "because it is an obvious violation of MVC." Frankly, I just don't get it. Indeed, it feels to me that putting session inside the controller merely an artifact that ApplicationController faces the network layer via a rack call, rather than an MVC mandate. Let me explain my problem.
Rolling an authentication from scratch, I found myself agonizing and spiking all over the place for lack of ability to articulate simple tests (session isn't available to the testing framework either). My authentication scheme, as almost all I have seen in rails, wanted to use the session hash as a persistence layer to retain the id for the User model of the "current user." Doesn't that feel FAR MORE like a model than a controller artifact?
The code smells are obvious whenever you look at the "typical" sessions controller (this one from Ryan Bates excellent screencasts). Desperate to shovel this concept in with rest, we see unhealthy language such as:
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
To me, this is a code smell, an obviously overlogicked controller that is screaming for refactoring! But we can't. Why? Oh yes, because it is a violation of MVC to put references to the session, used as a persistence lawyer into the model. WTF? Doesn't it say something to you that we seem to want to CALL THIS REST RESOURCE /sessions?
To see why this is just plain screwy, look at your login views -- hand-coded html, or use of the "_tags" API? If we had an ActiveModel model to do this code, then the create code could look like the usual scaffolding, or possibly even reduced to the "respond_with" one-liner.
def create
recognition = Recognition.new(params[:user])
if recognition.save
redirect_to root_url, :notice => "Thank you for signing up!"
else
render "new"
end
end
Then, take a look at the hand-coded html for all of those views! If Recognition was a model, persisted by session (or some other means that should not be the responsibility of the controller layer anyway), then you could simple use form builder or simple_form to generate the forms. Of course, we could simply pass the session hash to a "new_login" class method of Recognition, say Recognition.on(session).new(params[:recognition]), but that seems uglier than it has to be. Perhaps it is inherent, as we will want to use a current_user reference later in the application layer, perhaps Recognition.on(session).current_user similar to the way one might use a singleton pattern?
Just try to build your authentication package with strict BDD, and honestly tell me you haven't spiked this portion of it? If we had a Recognition model, this entire thing would be reduced to a simple set of unit tests without hackery. Now, instead we have the "sole" use case for integration testing, magic invasions of ActiveController modules and hacks to make speedy any acceptance testing of the logged_in_as predicate.
I think the entire point of ActiveModel was to facilitate this kind of rethinking and refactoring. Not all models use "the" database. Why not persist to the "session?"
I have been using devise and its kin for too long, burying these smells with the "dont mess with the gem" excuse I don't have to look at them. No longer! I think I am going to reject the zealots from now on. Sorry, to me, session is a persistence layer that should be manipulated in the Model layer of MVC. I posit, but am not sure, that the reason it lives in controllerland has more to do with the ugly or elegant fact that controllers are rack objects than any theoretical MVC magic.
So once again, is there a more elegant way to access the session layer than to have logic in the controller for that?

Maybe it's just me, but I don't sniff the code smell in that controller. I guess it depends on what you think should go into controllers versus models.
I think people take the "skinny controllers" idea to an unhealthy extreme at times. Yes, you want everything that can be in a model to be on the model: but controllers exist to change models based on application state, and you should allow them to fulfill that design goal. Having every controller be something like:
def create
Creator.create(params) # Pass all params to the creator model, which will automatically detect what object you're trying to create, make it, and then render the appropriate view
end
def show
Shower.show(params) # Find the model object and the view based on the params and render them together
end
Defies the idea of separation of concerns and creates nightmarish problems for people trying to update and maintain your code. Controllers should call model objects and even methods on those models to create and persist application state, and models should be agnostic to the state of the application. If you couple them together too tightly you'll end up with view and controller code in your models, at which point it becomes very difficult to determine what's where in your application.
If this is what you want and you think it serves your goals, then go for it -- but your code will be more difficult to maintain and will be hard for other people to understand. MVC exists because it's a sensible way to separate concerns and makes your code less surprising for other people.
All of this said, the session model you're proposing is actually a pretty good idea... and hence, it already exists. ;) The authlogic framework has a Sessions model: when logging in using authlogic, you create a new UserSession with the params object. UserSessions live in the models folder and exist solely to abstract away the nitty gritty of authentication into a controller-aware model class, so if that's what you're looking for, it's already been done for you! Go check out the authlogic github repository for documentation and usage examples.
I would still shy away from passing any kind of controller state into a real ActiveRecord model though. Allow your controllers to manipulate models and render the results of that manipulation as HTML -- it's what they're there for!

Related

Rails 4.2 Dry up Controller

I have a User model which gets accessed through a REST API and ActiveAdmin. Since there is a lot duplicated code (almost all) it might be a good idea to dry things up. There are a lot of topics out there handling this issue but i do not find a proper way. I put some of the ideas i have below
UserController instance in each controller
Again duplicated code gets created and the functions
#AA
controller do
def create
#userController = UserController.new
#userController.create(params)
end
end
#REST API
#Create instance through callback
def create
#userController.create(params)
end
Inheritance
I would like to have one basic CRUD controller lying above my model through inheritance like this
Class BaseUserController < ApplicationController
Class Api::UserController < BaseUserController
but (yet) i dont know how to do this in AA also in mind with the inherited_resource gem in mind
Concerns
Also concerns seem to be a nice way. But i would then create seperate concerns for each action to have one concern to exactly handle one behaviour
aspect and also to make them more reuseable . This then again seems to unnecessarily bloat the app and also concerns are not really made to be used like that. (?)
Modules/Mixins
Since modules cannot be instantiated but implement actions fully, this might just be what i am looking for. So each controller includes the desired actions or extend fro ma model rather than a class then.
The thing is - the more i read the more i get confused. I hope some of you guys might help me bring some light or i might turn to the dark side an use C# and .NEt again :D
Thanks in advance!
Example Code
I took this create method for a SMS resource in both, the REST API and the AA, where i call a couple of Services, but if complexity grows i have to add these lines in two places. How could i avoid that? Cant i have a basic SMSController which is used from the API endpoints as well as the AA interface?
controller do
def create
#customer =Customer.find_by_phone(params[:sms_message][:phone])
SmsService.sendSMS(#customer[:phone],Cryptoalgorithm.sms(#customer[:id], #customer[:recharge_token],params[:sms_message][:text].to_i)
#sms = SmsMessage.create(:customer_id => #customer[:id], :text => params[:sms_message][:text], :phone => params[:sms_message][:phone])
#customer.increment!(:recharge_token)
redirect_to admin_sms_message_path(#sms[:id])
end
end

Best practice for observing RESTful routing in Rails and coupling resource models and controllers?

Strict REST proponents might say that if you ever find yourself defining an action on a controller that isn't CRUD, then you should strongly consider creating a new resource and defining your action as a CRUD operation on the new resource.
An example might be operations that change state on a model - say Purchase. In this example, instead of defining a 'complete' action on PurchaseController you might create a CompletePurchasesController and use the create action to update the purchase's state to completed.
Assuming the above, you obviously don't persist PurchaseState directly to the database.
My question is when do you couple the Controllers to Models? When do you define a PurchaseState model (which isn't persisted) and when do you simply work directly with Purchase.
Is it a question of complexity and the number of loosely associated models you are interacting with in the Controller actions?
'Complete' is a state transition event on (an existing) purchase. I find it counter-intuitive to conceptualize this action as a create on a virtual resource rather than an update action on a controller coupled to the Purchase model, in fact PurchaseController itself.
I would define individual update-like actions for such state transitions. I think this way you can leverage rails structure in the most economical way, including model-initialisation, view dispatching, routing, access control. Let's assume you use inherited_resources, cancan by simply adding
# routes.rb
resource :purchase do
put :complete, :on => :member
end
# purchase_controller.rb
def complete
#purchase.complete!
end
# cancan ability (entry already there for basic crud)
can :manage, Purchase, :user_id => user.id
you are already done implementing the entire UI (view/model logic aside).
How utterly cool is that in rails.
If your typical usecase is that purchase is only updated by state transitions, especially all having the same access rights and redirect views, then I would even use the update action of PurchaseController with state_event attributes. See
Can somebody give an active record example for pluginaweek - statemachine?
Strict REST-ists, bite me! :)
For updating the purchase state, you probably only need a PurchasesController update action, which you would define in your routes file as a 'put' or 'patch' method.
If all that happens on update is changing the state field on your purchase object, you can probably just do that right in the update action.
If there's some business logic around some of the state transitions, but ultimately you're only changing that purchase object, you probably want to put that in your Purchase model.
If other tables are also updated, or you're also doing things like queuing up an email to your user congratulating them on their new purchase, I think that's when you might add a separate PurchaseComplete or PurchaseAbort models / service objects. These seem to come into play most naturally when the logic for the action is more complex, and/or you have changes to more than one model, or are doing something else.

rails 3, is there a way to keep skinny controller when controller uses lots of session info?

I like a nice skinny controller as much as the next guy.
But the app I'm working on talks extensively to a remote system via JSON, and the session variables are used extensively... each little interaction is a separate hit to one of dozens of controller methods, and rails' built-in session tracking keeps track of all the state.
Consequently, the controller has dozens of methods, one for each "interaction" and those methods extensively read (and sometimes write) the session.
It's my understanding that if you're accessing the session in your model methods you're doing something horribly "wrong" ... but having 100+ lines of code PER controller-method for dozens of controller methods seems "wrong" too.
Given that scenario, what IS the best tradeoff between fat controllers vs models that access the session?
I recently came across a similar problem. I was writing and application that depended heavily on a remote API that accepted/returned JSON. Subsequently, I had very few models in my application, and my controllers were quite lengthly.
I ended up writing a custom parser module in lib/ and made calls to the Module (which handled a lot of the logic that would normally exist in the controller)
Consider the following:
module RemoteParser
def get_user(user_id)
result = ActiveSupport::JSON.parse(NetHttp.get('http://www.example.com/'))
session[:received_user] = true
end
end
Now your controllers can be skinny again:
class SomeController < ApplicationController
def index
#user = RemoteParser::get_user(params[:id])
end
end
I'd just like to add that this is an incredibly contrived example of such uses. If you provide additional information about what your requesting, etc, I can help you further.

Model-level authorization in Rails

I want to implement authorization in my Rails application on a model level (not controller), in a similar way that validation on models is done. What is the best way to do this?
If it is implemented in the models itself, the main problem is that the models don't have access to the current user. I've seen solutions like: Thread.current[:user_id] = session[:user_id], but that doesn't seem like a good idea.
I've seen a different approach where variants of the methods like create, find and new are created, accepting an additional parameter for the current user.
Another approach would be to implement all the methods in the User/role class, so for example user.posts.create or user.readable_posts.find would be used instead of Post.create or Post.find.
Which of these approaches would be suggested? Are there any better ways to implement the authorization? Are there any plugins that makes this easier? I need an approach that scales well for multiple roles and models.
I would recommend you to look at declarative authorization. It works with both models and controllers.
The way it do what you are asking is having a before_filter in the applicationController that sets Authorization.current_user = current_user where Authorization is a module.
I think that approach is the best one, it keeps the models clean and you don't have to remember to include the user everywhere, but can filter it in the models callback functions instead.
Why would you do this? Isn't controller level identification enough?
if #user.is_able_to_do_this?
do_this
else
blah!
end
... and in your model
def is_able_to_do_this
if self.rights > Constant::Admin_level_whatever
return true
end
return false
end

Rails saving IP address with every create/update request

I'd like to do the following:
define a before_filter in application.rb that extracts the user's IP address and stores it anywhere, preferably in the session.
define two before filters in all my models as before_create and before_update that add the current user's IP to the object to be stored.
Problem: I cannot access session[] neither env[] in a model. Can anyone help with a standard solution that I don't know yet?
Regards
Jason
Try this. In your user model add a class attribute accessor
cattr_accessor :current_ip
In your application controller add:
before_filter :set_current_ip
protected
def set_current_ip
User.current_ip = request.env['REMOTE_ADDR']
end
Then in your model you should be able to just call User.current_ip
We do something similar to get the current_user object passed through.
You're having trouble doing what you want because Rails is designed not to allow you to have access to session information in your models. It's the classic separation of concerns with MVC. Models are meant to work independently of your other layers, and you'll be thankful they do when you start doing things with Rake or other system tasks where you won't have a session.
The
cattr_accessor :current_ip
is a horrible approach. It's a hack and it should be apparent why. Yes, it may work, but it's the wrong approach to this problem.
Since you're tracking "who" did "what" by their IP, the logical place for this to happen is in the controller layer. There are several approaches you can take, including using CacheSweepers as auditors, as outlined in the Rails Recipes book. CacheSweepers can observe models but also have access to all controller information. Using the ditry attributes in a rails model, you can see exactly what changed.
#user = User.find_by_login "bphogan"
#user.login = "Brian"
#user.save
#user.changed
=> ["login"]
#user.changes
=> {"login"=>["bphogan", "brian"]}
#user.login_was
=> "bphogan"
Combine this with the session info you have and you have a pretty awesome auditor.
Does that help?
If you want to save the IP in the session, you can create a before filter in the applicationController. Like this, for each action, the filter is called and the ip is stored.
authlogic is a plugin to manage users login/sessions etc, it has a built in option to track the users IP
What you really need is a versioning plugin - I suggest having a look at one of the fine solutions at http://ruby-toolbox.com/categories/activerecord_versioning.html
Edit: archived version of that link (was 404 since sometime in 2012): https://web.archive.org/web/20111004161536/http://ruby-toolbox.com:80/categories/activerecord_versioning.html

Resources