Rails getting started wizard in separate controller from users controller? - ruby-on-rails

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.

Related

Ruby on Rails 7 Multistep form with multiple models logic

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

Explanation of User.new in Ruby on Rails? (From Michael Hartl's tutorial)

I have searched everywhere to try to find an explanation of how this works/what its purpose is, but I cant find anything helpful.
I am doing Michael Hartl's tutorial, and my question is mainly about the two actions: 'new' and 'create'.
The new action has the following:
def new
#user = User.new
end
In the view corresponding to the 'new' action, there is a form_for helper, where users can type in their attributes and hit submit. As expected, the beginning of the form_for helper looks like this:
form_for(#user)
However here is where I am stumped... In the create action, there is the following code:
def create
#user = User.new(user_params)
#user_params is a function we defined which simply returns the permitted params from the user.
What is the purpose of #user = User.new in the 'new' action? What does User.new even accomplish? I am assuming that the instance variable #user is necessary to pass to the form, but in that case, why do we have to redeclare an #user isntance variable in 'create'? Isn't it sufficient to have only #user = User.new(user_params) in our 'create' action? Is the User.new somehow necessary to make the form function properly?
I am mainly just trying to figure out what #user = User.new accomplishes in our 'new' action and its corresponding 'new' view (with the form), and why it is necessary when we have a 'create' action which actually CREATES the object. ANY help is SO GREATLY APPRECIATED. Thank you all for always doing your best to explain. Thank you ahead of time to anyone who answers this.
The new and create are different actions. New is called when you get the new route. Create is called when you post to the new route. So, you have to create the user in new so they're available in the form. You have to create the user with the form contents in create so you can save it to the database.
You can't assume that the request to new will go to the same rails instance as the request to create. It's common to run multiple instances of your app behind a proxy.
It's called object orientated programming
HTTP
In Ruby, each variable you define is an object. These objects are then manipulated throughout each instance of the app.
In traditional (stateful) applications, your computer is able to store a number of objects in memory, and since your application is always in state, you'll be able to manipulate them from a single invocation.
In HTTP (stateless) applications, you have to rebuild the objects with each call. Because your application doesn't retain state (memory) between each request, you have to build the objects again.
This is why Rails "variables" are called with a class function on the model (class): User.find ...
--
Thus, when using the following:
#app/controllers/your_controller.rb
class YourController < ApplicationController
def new
#user = User.new #-> invokes a new user object
end
def create
#user = User.new user_params #-> invokes a new user object & populates with your params
#user.save #-> "saves" the new record
end
def show
#user = User.find params[:id] #-> stateless means you have to rebuild the object again
end
end
... what you're doing is rebuilding the object each time your actions are invoked.
This is one of the pitfalls of using HTTP - your server is "dumb" and cannot retain state between requests. Although Rails does a great job at making it a seamless process, it can be difficult if you haven't got your head around it yet.
Most generally, users enter data and we programmer-types traditionally store it in a relational database.
This creates an "impedance" between a relational model (i.e., tables and rows) and an object-oriented one (roughly, classes and instances).
ORMs like ActiveRecord help abstract much of this tedium, and in this way model instances--such as those we're creating in controller actions--serve as helpful containers for data.
This lets us easily represent models in views when gathering user input, and bind inputs to model attributes when persisting it (basic CRUD).
The separate controller actions merely represent these two steps in the process, as any Web-based app ultimately speaks HTTP.
This is really the whole benefit and genesis of Rails and similar MVC frameworks, born in a time of relational databases and server-side rendering. (Though they are increasingly coping with and adapting to an environment that now includes document/object-oriented databases and client-scripted front-ends.)

Using current user in Rails in a model method

I'm currently trying to implement simple audit for users (just for destroy method). This way I know if the user has been deleted by an admin or user deleted itself. I wanted to add deleted_by_id column to my model.
I was thinking to use before_destroy, and to retrieve the user info like described in this post :
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
module UserInfo
def current_user
Thread.current[:user]
end
def self.current_user=(user)
Thread.current[:user] = user
end
end
But this article is from 2007, I'm not sure will this work in multithreaded and is there something more up to date on this topic, has anyone done something like this lately to pass on the experience?
Using that technique would certainly work, but will violate the principle that wants the Model unaware of the controller state.
If you need to know who is responsible for a deletion, the correct approach is to pass such information as parameter.
Instead of using callbacks and threads (both represents unnecessary complexity in this case) simply define a new method in your model
class User
def delete_user(actor)
self.deleted_by_id = actor.id
# do what you need to do with the record
# such as .destroy or whatever
end
end
Then in your controller simply call
#user.delete_user(current_user)
This approach:
respects the MVC pattern
can be easily tested in isolation with minimal dependencies (it's a model method)
expose a custom API instead of coupling your app to ActiveRecord API
You can use paranoia gem to make soft deletes. And then I suggest destroying users through some kind of service. Check, really basic example below:
class UserDestroyService
def initialize(user, destroyer)
#user = user
#destroyer = destroyer
end
def perform
#user.deleted_by_id = #destroyer.id
#user.destroy
end
end
UserDestroyService.new(user, current_user).perform

When does code belong in the model in ruby on rails?

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.

Rails3 - Permission Model Before_Save Check?

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

Resources