So I recognize that Rails is a pretty opinionated language... which is why I feel like there's a definite Rails answer to this question. I'm trying to write the routing/ controllers for the following ADMIN user flow:
admin reviews/updates orders (has a get and post)
admin reviews/updates items in orders (has a get and post)
admin approves items in orders (has a get and post)
admin approves orders (has a get and post)
(Approval is not the same as update in this circumstance, so it's fairly important to differentiate that well)
This is literally a 1-2-3-4 step process flow, so I'm frankly thinking of doing everything under an AdminsController, with methods that follow the flow:
def step_1_get
orders_to_show = Order.where(...)
end
def step_1_post
order_params =
# some kind of order update
end
def step_2_get
items_to_show = Item.where(...)
end
def step_2_post
end
...
Similarly, I'd have all the views organized in steps under the admin folder.
I feel this would be much more clear than having a bunch of methods littered through the OrdersController and ItemsController even though I'm kind of breaking MVC. But again, what's the Railsy way of doing this?
Related
I am currently struggling with building up a multi step form where every step creates a model instance.
In this case I have 3 models:
UserPlan
Connection
GameDashboard
Since the association is like that:
An user has an user_plan
A connection belongs to an user_plan
A game_dashboard belongs to a connection
I would like to create a wizard to allow the current_user to create a game_dashboard going through a multi-step form where he is also creating connection and user_plan instance.
For this purpose I looked at Wicked gem and I started creating the logic from game_dashboard (which is the last). As soon as I had to face with form generating I felt like maybe starting from the bottom was not the better solution.
That’s why I am here to ask for help:
What would be the better way to implement this wizard? Starting from the bottom (game_dashboard) or starting
from the top (use_plan)?
Since I’m not asking help for code at the moment I didn’t write any controller’s or model’s logic, in case it would be helpful to someone I will put it!
Thanks a lot
EDIT
Since i need to allow only one process at a time but allowing multiple processes, to avoid the params values i decided to create a new model called like "onboarding" where i handle steps states there, checking each time the step
The simplest way would be to rely on the standard MVC pattern of Rails.
Just use the create and update controller methods to link to the next model's form (instead of to a show or index view)
E.g.
class UserPlansController < ApplicationController
...
def create
if #user_plan = UserPlan.create(user_plan_params)
# the next step in the form wizard process:
redirect_to new_connection_path(user_id: current_user, user_plan_id: #user_plan.reload.id)
else
#user_plan = UserPlan.new(user: current_user)
render :new
end
end
...
# something similar for #update action
end
For routes, you have two options:
You could nest everything:
# routes.rb
resources :user do
resources :user_plan do
resources :connection do
resources : game_dashboard
end
end
end
Pro:
This would make setting your associations in your controllers easier because all your routes would have what you need. E.g.:
/users/:user_id/user_plans/:user_plan_id/connections/:connection_id/game_dashboards/:game_dashboard_id
Con:
Your routes and link helpers would be very long and intense towards the "bottom". E.g.
game_dashboard_connection_user_plan_user_path(:user_id, :user_plan_id, :connection_id, :game_dashboard)
You could just manually link your wizard "steps" together
Pro:
The URLs and helpers aren't so crazy. E.g.
new_connection_path(user_plan_id: #user_plan.id)
With one meaningful URL variable: user_plan_id=1, you can look up everything upstream. e.g.:
#user_plan = UserPlan.find(params['user_plan_id'])
#user = #user_plan.user
Con:
(not much of a "con" because you probably wind up doing this anyway)
If you need to display information about "parent" records, you have to perform model lookups in your controllers first:
class GameDashboardController < ApplicationController
# e.g. URL: /game_dashboards/new?connection_id=1
def new
#connection = Connection.find(params['connection_id'])
#user_plan = #connection.user_plan
#user = #user_plan.user
#game_dashboard = GameDashboard.new(connection: #connection)
end
end
I need to create some routes for my blog website such that normal user can only read the posts and products. while super admin creates admins to moderate posts. How can i achieve that in rails?
I want
myapp.com/admin to bring me to log in page for admins.
Only super admin can manage moderators.
So far, I have a controller in 'app/controller/admin/home_controller'
which has index action for viewing all posts and users for admin.
Try adding a before filter before your action so that each time a admin try to hit that url he/she should get redirected to admin_login_url
before_filter :check_role
private
def check_role
unless current_user.role=='super_admin'
redirect_to admin_login_url
end
end
I am going to share what i actually did after reading tons of approaches just in case if anyone is stuck where i was. Hopefully helps:
Create a new controller in 'app/controllers/'. This controller is only responsible for GET requests.
Remove Create, Update, Delete methods from the controllers in 'app/controllers/', as these will be only accessible to admins.
Create a new admin_controller.rb in 'app/controllers/admin/'. Add authentications here.
Create new controllers(inherited from AdminsController) per model in the same directory and put the admin operations there.
class AdminPostsController < AdminsController
I'm using Ruby on Rails 5 and I want that depending of the role of a User, the user can post 5 Posts or 10 or 15, and that is just a part of the several possible situations that I have to check for Authorization (for things that are not Posts for instance I have other situations that are a bit more complex), so I started using Cancancan (v 1.15.0) few days ago to don't make the User model too huge, so this way I have one class for each Role and in ability.rb I just merge the classes depending on the Role.
The problem is that using Cancancan apparently it checkes the Authorization only once. For example in Creating a Post, In Post#create the first line of code is:
authorize! :create, Post
In the Role class of the user, I have this code:
if user.posts.size < 10
Rails.logger.debug "If-size: #{user.posts.size}"
can :create, Post
else
Rails.logger.debug "Else-size: #{user.posts.size}"
cannot :create, Post
end
I have some tests with RSpec and I see the first time the current_user (the one with that specific role) creates a Post using the controller (I mean, not by FactoryGirl or any other way that is not using the controller), it appears in log/test.log:
If-size: 0
But neither the Else nor the If appears ever again in the log, it doesn't matter how many Posts I create by the Controller, it only evaluates this condition the first time and the User get authorized to creates as many Posts as he wants because the first time the condition in the If is true and it is not evaluated each time the method create of the controller Post is called.
EDIT: Solved by the method suggested by MarsAtomic. Thanks! :)
CanCanCan isn't meant to dynamically evaluate roles based on conditions within your application. That type of functionality is best handled by your application's own logic, rather than an authorization tool.
You should consider adding a column to your roles table that indicates how many posts a particular role is allowed. This column would allow you to check on the number of posts each user (via role) is allowed to create.
Then, in your posts_controller.rb, you can wrap your post creation logic in a block that runs only if the user has not exceeded the maximum number of posts allowed:
def create
if user.posts.size < user.role.max_posts
#post = Post.new(post_params)
#post.save
redirect_to #post
else
# flash an error on the page or redirect to an error page
end
end
I have a Rails site that requires a lot of form fields that need to be filled out after the user first signs up (using a large jQuery wizard). At first, I wrapped all the "getting started" (executed when the user logs in for the first time) specific code in the users controller like this:
Class UsersController < ApplicationController
def new
#user = User.new
end
def getting_started
def getting_started
#user = User.find(current_user.id)
unless #user.employees.length == 15
15.times { #user.employees.build }
end
end
end
My question is, should I separate out the getting started method into it's own controller if the getting started method is beginning to grow rather large? What is the "rails way" of doing this?
Size isn't what dictates a new controller--controller purpose dictates a new controller. If it's not related to a User and is entity-like on its own, new controller. If it's just more User data, it should stay.
If it's simple size you're concerned with, it depends. If it's code acting directly on a User, it may belong in the User model. If not, it belong in private methods or its own library.
Without further details regarding what getting_started actually does, it's difficult to be more specific.
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