I have an "admin control panel" page which is handled by an AdminController. You can do two things in the control panel: create_product and create_order. There will be forms for each object, and when you submit the form, it will insert new records into the database.
What is the Rails way for implementing this? Do I generate a CreateProductController and CreateOrderController along with the appropriate view, which are accessible by clicking on the create_product and create_order links from the control panel?
Does the Rails way describe a way to handle both workflows using a single controller? At some point I will need to define a post method for each form, so it seems like creating separate controllers is the easiest way to set up required behavior and also the routing details.
Product and Order are all resources when having controller. The better way is to use RESTful resources. You can also add namespace for easier identification.
class Admin::ProductsController < AdminController
def create
end
def new
end
# And #show, #index, #destroy etc.
end
class Admin::OrdersController < AdminController
def create
end
def new
end
end
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
Is it possible to access controller parameters when defining abilities in ability.rb?
I have an event and users that can participate in or create that event. It seems like I could create a different controller action for every possible scenario, e.g. a user signs himself up for an event or a creator deletes someone from the event. However I think it would be a lot easier to read to have less actions and be able to define abilities based on what parameters are being passed in from the client.
Answer
#chumakoff has some good info down below that helped explain how CanCanCan is working. I decided to authorize these actions by default in ability.rb, and then raise an error, e.g. raise CanCan::AccessDenied.new("You cannot delete someone else from this event"), in the controller if I detect incorrect user/event parameter IDs being sent in.
If I understand correctly, you are using cancan's authorize_resource or load_and_authorize_resource controller helper that calculates user abilities based on controller actions names.
But it's not obligatory to use this helper for all actions. You can skip it for actions having complex ability logic and check abilities manually.
For example:
class ParticipationsController < ApplicationController
authorize_resource except: :create # skiping `authorize_resource` for `create` action
# ...
def create
if creator_adds_someone_to_event?
authorize! :add_to, #event
end
if user_signs_up_for_event?
authorize! :sign_up_for, #event
end
# ...
end
So, you can check many different abilities in the same controller action. Just disable default cancancan's behaviour for the action.
Yes there is a debugging tool Named as " pry" . Use that it would help u out. Just use binding.pry wherever u want to check the value of parameters in the code and the console will stop executing at that moment so u can check the value of the parameters.
If, for whatever reason, I don't require a user to input data into a form to create a new record in my rails application, and instead just do this:
def new
#blahblah = Blahblah.create({
x: "blah",
y: "blah"
})
redirect_to action:'index'
end
Would it be considered bad practice to just create the new record inside the new action like this? I find myself often in situations where I need to save data to the database but have no need to get the data from the user via a form (sent to the create action, where I normally 'create' records).
I my view you should not make Restful dirty. You should not touch new or create action. You should go for another action in controller and do whatever you want.
This is not right way to add create code in new action. So either go to create action directly or my preferred go and make another action and put your code there.
You can create another action instead of new. So your new action won't affect.
##routes.tb
get 'whatever/create_record' => 'whatever#create_record', :as => 'create_record'
and in your controller
##whatever_conroller.rb
redirect_to create_record_path(:attr1 => val1 ...)
This will take you create_record method properly. And your new action won't affect. Because new/create/update/edit/destroy specially design for restful routing.
You can use the create action. In the other hand, if you manage custom business logic you can use a custom action in your controller (don't forget to update you routes.rb with new action)
Would it be considered bad practice
Yes.
The entire ethos of Rails is a CRUD based infrastructure:
Where would the web be with out acronyms? REST stands for
REpresentational State Transfer and describes resources (in our case
URLs) on which we can perform actions. CRUD, which stands for Create,
Read, Update, Delete, are the actions that we perform.
Although, in
Rails, REST and CRUD are bestest buddies, the two can work fine on
their own. In fact, every time you have written a backend system that
allows you to add, edit and delete items from the database, and a
frontend that allows you to view those items, you have been working
with CRUD.
--
All this basically means is that when you have controllers, models and routes, you're working with objects.
Your question of:
def new
#blahblah = Blahblah.create({
x: "blah",
y: "blah"
})
redirect_to action:'index'
end
breaks the Rails convention of CRUD. If you wanted to create an object without any input, why don't you either hard code it, or manually seed it into the database?
#app/models/blahblah.rb
class Blahblah < ActiveRecord::Base
def self.x
# value
end
def self.y
# value
end
end
This would allow you to call:
#geolocation = Geomap.x * Geomap.y
where I need to save data to the database but have no need to get the
data from the user
What data do you need to save?
If it's stuff like role information, just include it in the User creation process, with callbacks including before_create:
#app/models/user.rb
class User < ActiveRecord::Base
before_create :set_role
def set_role
role_id = "0" unless self.role
end
end
To the point: I want to put Post has_many Comments but I do not want to create a separate comment controller and subsequent views. Mainly because the comments will never show up anywhere else but inside the SHOW action of a Post. Or am I breaking the MVC paradigm?
You are breaking the MVC paradigm, as you said. The point of MVC is to split everything up into bite-size chunks so it is more manageable. That's how I see it at least.
How would comments be created without a specific controller for them. The showing part on a Post is the easy part:
#comments = #post.comments
There is a fundamental distinction to be made between the internal domain model of your system and the public interface your system exposes.
If you are using a relational database, it is good practice to have
Comment.belongs_to :post
Post.has_many :comments
The internal domain model of your system can help you design your public interface - but you can also tailor your public interface how you want it, without being forced to make it a strict reflection of your internal domain model!
In your case, I would suggest having a CommentsController. But in this controller class, you do not need all of the normal REST actions. You only need a few of them.
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
respond_to :js
def create
#post = Post.find(params[:post_id])
#comment = post.comments.create(params[:comment])
respond_with [#post, #comment]
end
end
In this controller, you only have a create action which would be the target of the "new comment" form at the bottom of the page displaying a post. You do not need any of the other REST actions because people never view, edit, or delete a comment in isolation - they only create new ones, and not from a dedicated new-comment page either. The routing for this is as follows:
# config/routes.rb
MyApp::Application.routes.draw do
resources :posts do
resources :comments, :only => [:create]
end
end
The more you deviate from the MVC paradigm, the more problems you'll have later on. For example, if you wanted to add admin views for your Comments, it would be easier to expand on it through the Comments Controller. Else, you'll end up having multiple actions for your comment in the Posts controller (eg. approve_comment, delete_comment, voteup_comment, etc).
That being said, you can always wire things up so that actions on your comments direct the user back to the Post that originated it. So, comment related actions will reside in the Comments Controller, but the user is generally working with Posts (and its associated Comments).
The scenario is this:
I have some users on my site. A user has a role, the relevant ones here is admin and normal. I also have a model, let's call it SomeModel.
I have created a backend for the site, which uses an admin layout. All admins have full access to edit any content.
The problem arises with this relation: User -> owns -> SomeModel. That means a non admin User can own an instance of SomeModel and should be able to edit the data of this instance.
The controller for SomeModel has an edit action which then caters to both admins and regular users.
However, I don't want regular users seing the admin layout and right now, the way I do this is like so:
if current_user.admin?
render :layout => 'admin'
end
Which defaults to the standard layout if the user is not an admin. I have this in all my actions for SomeModel and it just doesn't seem like a very Rails way of doing things.
Is there a better way?
You can do this at the controller level:
class MyModelController < ActionController::Base
layout :user_or_admin_layout
def index
# fetching objects
end
private
def user_or_admin_layout
current_user.admin? ? "admin_layout" : "user_layout"
end
end
There are more examples in the rails documentation for layout