The logic of the application that I currently work on demands a Payment mustn't be editable if its status is open. I see two ways of implementing this:
1 A routing constraint like:
constraint: lambda { |req| Payment.find(req.id).status != 'open' }
2 A simple condition in PaymentsController#edit:
if #payment.status == 'open'
redirect_to payments_path
end
What option should I go for? Which is more suitable and clean, Rails-ish? Is there any other option? If I go with the first option and have a resources :payments, how can I add the constraint only for the edit route?
As to the Rails way of solving this it is actually none of your suggestions.
Routing - You routes should just simply declaratively state the RESTful interface of your application. They should not be aware of the current request unless absolutely necessary.
Controller - Adding business logic in the controller will bloat your controllers and violates DRY.
In MVC the model is in charge of enforcing the business logic. You could handle this through a custom validation for example:
class Payment
validates :cannot_be_edited_when_open, on: :update
def cannot_be_edited_when_open
errors.add(:status, 'is open. Cannot edit this record.') if self.open?
end
end
This will cause any call to .update to fail - which means that you probably will not need to change anything in your controller.
Edited
Another place to handle this would be on the authorization layer - the key difference here is how the feedback should be handled.
A validation failing will just re-render the form (422 Unprocessable Entity for an API) while an authorization error should clearly tell the user "no you don't have permission to do that - and changing the input won't change that" (403 Forbidden).
To setup the rule in CanCan you would do:
can [:edit, :update], Payment do |payment|
payment.status !== 'open'
end
You could also possibly set this up with hash condition instead of a block if your business logic allows it:
can [:edit, :update], Payment, status: 'not-open'
If the rule you specified is a business logic, i.e. no Payment object should be updated with the status open, then, the correct way would be to add that logic to your model. You can use before_validation or before_update callbacks. Also, don't display any means to edit (link, button, etc.) your Payments that have status open. If a user somehow gets to your form, display it, but then validation in your model will not let them save it. I think, in this case, everyone is responsible for their own responsibilities.
But, if you have to choose from the 2 options you provided, I would go with the second one. Your first option, having the business logic in routes, doesn't seem not right. It does not belong there, IMO. The second option is better, but the drawback is that you and your team members will still have to remember that they have to check the Payment object's status attribute does not have some value before touching it. Obviously, someone will forget to do that at some point. So, in the long run, your system (models in your system) will have a corrupted state.
Related
I'd like to have a a form view that can, depending on circumstances, have submit functionality disabled in a bullet-proof way so that even a clever user could not edit the HTML source (via a browser extension) to re-add the submit button.
It seems one way to do that might be to somehow inject an invalid authenticity token that replaces the (valid) rails-generated one, so that even if a user somehow re-adds the submit button (by editing the HTML via a browser extension) it would still be an invalid submission.
My thought is to have some logic in the view:
- if #form_disabled # set by controller
- somehow_invalidate_the_authenticity_token?
How might one 'break' Rails form submission?
The purpose of doing this, instead of rendering the preview in a :show action, is to have the exact same view displaying both the live-form and the dead-form.
If I were you, I would use pundit.
It's pretty simple, and has few lines of code if you need to know how it works.
I'd start to write the code here, but I realize that the example at the readme fit your needs.
At the application controller add this
At the folder app/policies put the class PostPolicy, of course, you must replace "Post" with the name of your controller in singular (even if you have not a model with that name). The update? (and create?) actions should return true/false to indicate if user is allowed or not.
A few lines down on the readme, you will find the PostsController#update action, which call to authorize with the record before the update. I think you want do the same with create (then you need a create? method at the policy class).
Pundit needs current_user controller method, if you don't have it. Just follow the user customization instructions.
Of course, new and edit actions don't call authorize because they are allowed to everybody. Only the POST & the PUT/PATCH actions are forbidden.
Yes, it's more than a surgery of one line of code. But it's simple and the right way of give access to users.
After reading my other answer, I start thinking that you can do the same that Pundit does at the controller:
def update
if <unauthorized user>
flash[:alert] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
else
# all the update stuff
# ...
end
end
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.
So lets say I have a form for submitting a new post.
The form has a hidden field which specify's the category_id. We are also on the show view for that very category.
What I'm worried about, is that someone using something like firebug, might just edit the category id in the code, and then submit the form - creating a post for a different category.
Obviously my form is more complicated and a different scenario - but the idea is the same. I also cannot define the category in the post's create controller, as the category will be different on each show view...
Any solutions?
EDIT:
Here is a better question - is it possible to grab the Category id in the create controller for the post, if its not in a hidden field?
Does your site have the concept of permissions / access control lists on the categories themselves? If the user would have access to the other category, then I'd say there's no worry here since there's nothing stopping them from going to that other category and doing the same.
If your categories are restricted in some manner, then I'd suggest nesting your Post under a category (nested resource routes) and do a before_filter to ensure you're granted access to the appropriate category.
config/routes.rb
resources :categories do
resources :posts
end
app/controllers/posts_controller
before_filter :ensure_category_access
def create
#post = #category.posts.new(params[:post])
...
end
private
def ensure_category_access
#category = Category.find(params[:category_id])
# do whatever you need to do. if you don't have to validate access, then I'm not sure I'd worry about this.
# If the user wants to change their category in their post instead of
# going to the other category and posting there, I don't think I see a concern?
end
URL would look like
GET
/categories/1/posts/new
POST
/categories/1/posts
pst is right- never trust the user. Double-check the value sent via the view in your controller and, if it does't match something valid, kick the user out (auto-logout) and send the admin an email. You may also want to lock the user's account if it keeps happening.
Never, ever trust the user, of course ;-)
Now, that being said, it is possible to with a very high degree of confidence rely on hidden fields for temporal storage/staging (although this can generally also be handled entirely on the server with the session as well): ASP.NET follows this model and it has proven to be very secure against tampering if used correctly -- so what's the secret?
Hash validation aka MAC (Message Authentication Code). The ASP.NET MAC and usage is discussed briefly this article. In short the MAC is a hash of the form data (built using a server -- and perhaps session -- secret key) which is embedded in the form as a hidden field. When the form submission occurs this MAC is re-calculated from the data and then compared with the original MAC. Because the secrets are known only to the server it is not (realistically) possible for a client to generate a valid MAC from the data itself.
However, I do not use RoR or know what modules, if any, may implement security like this. I do hope that someone can provide more insight (in their own answer ;-) if such solutions exist, because it is a very powerful construct and easily allows safe per-form data association and validation.
Happy coding.
I have 2 models - User and Activity - which are related by a has_many :through using the UserActivity model. A User can either "want" or "done" an activity, which creates a UserActivity record and sets the appropriate boolean.
What would you recommend for handling these actions when creating routes and controller actions? Would something like activity/:id/want, and activity/:id/done make the most sense, and thus have 2 member routes under the activity resource? Or would it make more sense to just use the update/create actions in the user_activity controller, with posting to /user_activity for a create, and putting to /user_activity/:id to update?
I would go with the latter approach, i.e. by allowing POST/PUT access to "user_activity". In the REST world this is considered as a new resource even though it just forms a relationship between "user" and "activity" resources.
One thought which comes to mind is to separate "want" or "done" activity types as a new resource as well(e.g. "status"). The idea is clean as it is even without it, but if there is a chance that you will have to extend the list to something on top of "want" or "done" (e.g. "someday/maybe"), it may be easier to define it as a new resource rather now than later.
So you would have:
user resource: /user and /user/id
activity resource: /activity and /activity/id
status resource: /status and /status/id
resource which forms the relationship between user, activity and resource: /user-activity and /user-activity/id
Depending on your URI design you can then work in accessable URIs which would allow you to get all activities for a user which are in a specific state, e.g.: /user-activity/user/{userId}/status/{statusId}
Please note: I cannot advise on Ruby-on-Rails specifics (as I'm from the PHP world), but I think that the (REST) principles should be very similar.
In a current project I need to support finding a User by login credentials and also by email address. I know that in RESTful design you use a GET to find resources. In Rails...
GET /users # => UsersController.index -- find all the users
GET /users/1 # => UsersController.show -- find a particular user
But I also need something akin to...
GET /users?username=joe&password=mysterio
GET /users?email=foo#bar.com
Is it conventional to add additional routes and actions beyond index and show?
Or is it more common to put conditional logic in the show action to look at the params and detect whether we're finding by one thing or another?
There's a similar issue with PUT requests. In one case I need to set a User to be "active" (user.active = true), and in another case I just need to do a general form-based editing operation.
Thanks guys. Eventually I'm going to figure out this REST stuff.
I'm new to SO, so I can't comment, but the checked green answer is not RESTful.
In a RESTful world, your controller grabs all the parameters and passes it to the model layer for processing. Typically, you shouldn't create another action.
Instead, you should do do something like this:
def show
#user = User.find_by_login_or_email(params[:user])
... #rest of your action
end
Your model can have a method like this:
class User
self.find_by_login_or_email(params)
return find_by_login(params[:login]) unless params[:login].blank?
return find_by_email(params[:email]) unless params[:email].blank?
nil #both were blank
end
end
Your view could look like this:
<%= f.text_field :user, :email %>
or
<%= f.text_field :user, :login %>
Note: untested code, so may be buggy...but the general line of thinking is usually not to create new actions for every one-off rule. Instead, look to see if you can push the logic into the models. If your controllers start to have too many non-standard actions, then it may be time to re-evaluate your domain modeling, and perhaps it's refactor the actions to some new models.
ps: you should never pass in passwords via a GET like that
I don't know how much of this is convention, but this is what I would do. I
would add another action, as long as it's specifically related to that
resource. In your example, show is a find by userid, so it makes sense as
another action on UsersController. You can turn it into a sentence that makes
sense, "get me the user with this email address"
For the other one, GET /users?username=joe&password=mysterio, I would do
that as another resource. I assume you're thinking that action would log in
the user if the password were correct. The verb GET doesn't make sense in that
context.
You probably want a 'session' resource (BTW, this is how restful_auth works).
So you would say "create me a session for this user", or something like POST
/sessions where the body of the post is the username & password for the user.
This also has the good side effect of not saving the password in the history
or letting someone capture it on the HTTP proxy.
So your controller code would look something like this:
class UsersController < ActionController::Base
def show
#user = User.find_by_id(params[:id])
# etc ...
end
def show_by_email
#user = User.find_by_email(params[:email)
end
end
class SessionsController < ActionController::Base
def create
# ... validate user credentials, set a cookie or somehow track that the
# user is logged in to be able to authenticate in other controllers
end
end
You would set up your routes like this:
map.connect "/users/byemail", :controller => "users", :action => "show_by_email", :conditions => { :method => :get }
map.resources :users
map.resources :sessions
That will get you URLs like /users/byemail?email=foo#example.com. There are
issues with encoding the email directly in the URL path, rails sees the '.com'
at the end and by default translates that into the :format. There's probably a
way around it, but this is what I had working.
Also like cletus says, there are ways to make your route match based on the format of the parts of the URL, like all numbers or alphanumeric, but I don't know off hand how to make that work with the dots in the url.
The first thing you can do is make your GETs as smart as possible. In your example, this can be handled programmatically. The argument can be processed this way:
Is a number? It's a userid;
Has a # in it? It's an email;
Otherwise? It's a username.
But I assume that you're not just talking about this example and want something to handle the general case rather than just this specific one.
There are basically two ways of dealing with this:
Add extra path information eg /users/email/me#here.com, /users/name/cletus; or
Be more specific in your "top-level" URL eg /user-by-email/me#here.com, /user-by-name/cletus.
I would handle it programmatically if you can.
Regarding the "ByEmail" request, have you considered creating a new email resource.
GET /email/foo_at_bar_dot_com
The response could contain a link to the related user.
I see so many people trying to apply RESTful design principles to their URL structure and then mapping those urls to procedural handler code. e.g. GET = Show, or is it GET = Index or ShowByEmail. By doing this you are really just pretending to do a RESTful design and then trying to create a mapping between a resource oriented URL space and procedurally oriented implementation. That is really hard to do and the procedural nature keeps leaking out into the URLs.
Resource oriented design often requires a very different way of thinking about problems that we are used to and unfortunately many of the frameworks out there keep sucking us back into the RPC model.
You might be able to set up different routes for different tasks. So for this case you could have one route to a method in UserControll dedecated to getting a user by email, and another for getting the information by credentials.