I have two Models:
Users, and Articles
Every user is able to create an article but can only edit his own Articles?
Can someone provide an example of that? (Model, Controller and View Please?)
Thanks
EDIT
I do not want the whole code. I can get the most of the code using scaffolding. I need the modifications that I have to do to achieve that. My biggest concern is how to allow only the author of an article to edit it. That's what I am asking.
I can't write the full example code out but I can at least point you in the right direction.
You're describing a one to many association between your two models. This guide is the best I've seen in figuring out how to set up those associations in Rails.
Once you've got that in place you can limit access based on ownership of the article quite easily. For something this straight forward you probably wouldn't need a permissions gem but there are some solid ones out there.
I'd protect access in the controllers and in the view. You can simply check the current_user against the article object on the view, and in the controller you can use before filters to protect the article.
If you get further down this path and have more specific questions I'm glad to try and answer them.
Assuming an Article belongs_to :user and you have an authentication setup that gives you a current_user method, you should be able to do something like this in your ArticlesController:
def edit
#article = Article.find(params[:id])
if #article.user == current_user
render :edit
else
flash[:alert] = "You don't have permission to edit this article."
redirect_to some_path
end
end
You would also need something similar for your update method.
Related
I am building an admin interface for a website. I have certain controllers that have admin functions/views that also have user facing views. For example, when a user goes to /blog, it should show the title, date, first paragraph, etc. of each blog post with links to read the whole post. If an admin goes to admin/posts they would see a list of just the blog post titles, how many comments, edit/delete links, link to create a post, etc.
How would I accomplish this? My (simplified) routes files is this:
namespace :admin do
resources :posts
end
Do I need to have separate controllers?
Usually when using namespaces you want your code to be namespaced as well. I would go for 2 different controllers serving 2 different views.
app/controllers/posts_controller.rb
app/controllers/backend/posts_controller.rb
or
app/controllers/posts_controller.rb
app/controllers/admin_area/posts_controller.rb
You get the idea. I would do the same thing with the views.
You controllers would look like this:
class PostsController < ApplicationController
end
class Backend::PostsController < BackendController
end
class BackendController < ApplicationController
end
There are quite a few ways you could approach this, I can't really think of one being "right" over the other. For simplicity's sake, I'll offer one quick solution, though, admittedly, it's a shortcut.
Presumably, you have a logged in user going to this /admin route, and the current_user would be authorized as an admin, so you can use that to your advantage in your show method.
if current_user.admin?
render 'admin_show'
else
render 'show'
Your views, of course, would render the different implementations.
If you are going to have a number of differences in what the methods do, though, it may be worth creating a new admin_posts_controller. But if there are only a couple differences, then that could be good enough.
Meh, hope that helps.
I'm looking for best practices. Here's the scenario:
Customers can pay for one or more Widgets from a form. So I have a Payments model and a Widgets model. There is no association between them (Payments is associated with Customer). What's the best way to handle this?
In the Payments controller I could do:
def create
#customer = Customer.find(params[:customer_id])
if #customer.payments.create!(params[:payment])
how-many-widgets = params[:payment][:number].to_i
while how-many-widgets > 0
widget = Widgets.new
... update widget ...
widget.save!
how-many-widgets = how-many-widgets - 1
end
end
redirect_to #customer
end
Is this the best way to do this? Or is there some more elegant solution?
If you're saving and changing things, it's a good bet you should be doing this code in a model, rather than a controller. If I were to refactor your code, it would look a little like this:
def create
#customer = Customer.find(params[:customer_id])
if #customer.payments.create!(params[:payment])
params[:payment][:number].times do
Widget.create(params[:widget])
end
end
redirect_to #customer
end
If Widget.create isn't what you're looking for, come up with a custom method that takes the params in, transforms them, and then spits out the correct object. Also if the widgets should be related to either the customer or payments, don't hesitate to relate them -- like, if you look at that code and say, "I also need to pass the current user/customer/payment to the widget," that would be a good hint that the widget should be associated to that model somehow.
Currently putting in a lot of rails 3 practice and I was working on an authentication system and was following a tutorial on railscasts. Ryan from railscasts done a sort of update to that tutorial with some minor changes to take advantage of rails 3.1
e.g. has_secure_password
So some of the code in my Sessions_controller changed to:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_username(params[:username])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_path, :notice => "Logged In"
else
flash.now.alert = "Invalid Credentials"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, :notice =>"Logged Out"
end
end
What I would like to know is if some of the code in the create method/action should be in the model? Is it good or bad practice to have this code there?
What rules should I be following as I want to learn the correct way and not pick up bad habits because I've gone past that part of learning a framework where things start to make sense much more usually than they don't.
Advice is appreciate..
What I would like to know in particular is..
1. When does a programmer know when code belongs in the model? How does he/she make that decision?
This is one of the most important questions in OO programming.
It's all about responsibilities. Place code in your model if you think that model is responsible for that piece of functionality.
In your example you see that:
The SessionController is only responsible creating and destroying the the user's session.
The User is responsible for authentication.
All business logic goes into your models. Your controllers takes care of populating your views, handling the user's input and sending the user on their way. View simply display information, hardly contain any logic (if any).
Also: take a look at existing projects for inspiration (for example, Shopify).
My advice:
In User model (pseudocode):
function authenticate(username, pass) {
/*get user by name
return user_id (or user object if you need some user data in view) if auth ok, otherwise false
*/
}
I think you should always keep controllers small as possible.
The most important think in OOP is encapsulation so you should write all operation on user in user class, return to client code (in this case controller) only what controller need to do his job - add user id to session.
I have a rails app that involves users, essays, and rankings. It's pretty straightforward but I'm very new to rails. The part I'm having trouble with right now is the create method for the rankings.
The Essay class has_many :rankings and the Ranking class belongs_to :essay
in the rankings controller I have:
def create
#ranking = #essay.rankings.build(params[:ranking])
flash[:success] = "Ranking created!"
redirect_to root_path
end
but I get the error: undefined method `rankings' for nil:NilClass
I need each ranking to have an essay_id, and I believe build updates this for me.
I thought that rails give me the rankings method because of the relation I set, and why is #essay nil?
Thanks in advance
Build doesn't save. You should be using new and then save. I'd provide you sample code, but you haven't really given us a clear picture of what you have going on. That #essay instance variable is being used before it's defined, and I'm not really sure how your application is determining which essay the ranking belongs to.
You might wanna give Rails Guides a read.
I think this is what you intend to do:
# EssayController#new
def new
#essay = Essay.new(params[:essay])
#ranking = #essay.rankings.build(params[:ranking])
#...
end
Take a look at nested model forms , it should get you in the right direction.
I have a permission model in my app, that ties (Users, Roles, Projects) together.
What I'm looking to learn how to do is prevent a user for removing himself for their project...
Can you give me feedback on the following?
class Permission < ActiveRecord::Base
.
.
.
#admin_lock makes sure the user who created the project, is always the admin
before_save :admin_lock
def before_save
#Get the Project Object
project = Find(self.project_id)
if project.creator_id == current_user.id
# SOME HOW ABORT OR SEND BACK Not Allowed?
else
#continue, do nothing
end
end
end
Is that look like the right approach?
Also, I'm not sure how to do the following two things above:
How to abort prevent the save, and send back an error msg?
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Thanks for reading through
How to abort prevent the save, and send back an error msg?
return false during the callback chain tells activemodel to stop (similar to how adding errors to the model during a validation tells it to stop at that point)
self.errors.add_to_base "msg" will add an error to the model, which can then be rendered on the view.
Get the devise, current_user.id in the model, that doesn't seem possible, so how do Rails gurus do stuff like the above?
Models shouldn't really know about things like the current request, if at all possible, you should be locking things down at the controller/action level.
EDIT:
So, the role of controllers is to deal with everything involved in getting the correct information together based on the request, and passing it to the view (which becomes the response). People often say "make your models fat and your controllers skinny", but that could be said of any system that embraces object oriented design -- your logic should be in objects when possible.
That being said, the whole point of controllers is to deal with routing the right things to the right places, and authentication is definitely a concern of routing.
You could easily move the line comparing creator_id to user id in the action, and react based on that.
Now, sometimes you genuinely need that stuff in the model and there is no way around it. That becomes a problem, because you need to fight rails to get it there. One way would be to attr_accessor a current_user field on your model, and pass that in on initialize. Another would be to remove the fields from the params hash that a user is not allowed to change in the action. Neither is really that nice though.
Agreed with Matt that you should try to use the controller for the redirect. The model should have the logic to determine if the redirect is appropriate. Maybe something like
class ProjectsController < ApplicationController
def update
redirect_to(projects_url, :alert => "You can't remove yourself from this project.") and return if Role.unauthorized_action?(:update, params[:project])
#project = Project.find(params[:id])
if #project.update_attributes(params[:project])
...
end
class Role
def self.unauthorized_action?(action, params)
# your logic here
end
You should check out CanCan for some ideas.
In permission model take one field project_creater as boolean
In project modelbefore_create :set_project_ownership
def set_project_ownership
self.permissions.build(user_id: User.current.id, project_creater: true)
end
In project controllerbefore_filter :set_current_user
In Application controllerdef set_current_user
User.current = current_user
end