I'm reading Rails 3 Way by Obie Fernandez. He's demonstrating the use of a plugin Authlogic, and created a User and UserSession model and a UsersController and UserSessionsController.
He hasn't created any views (but he might assume some exist)
In the UserSessionsController, he creates this code
class UserSessionsController < ApplicationController
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
redirect_to user_path(current_user)
else
render :action => :new
end
end
def destroy
current_user_session.destroy
redirect_to new_user_session_path
end
end
My question relates to the create method. When he writes
UserSession.new(params[:user_session])
where is :user_session coming from? I undersdtand that UserSession.new instantiates a new object, but where do the params come from? and what names would they have?
Does it depend on something in the imaginary view? or are these params automatically generated by Rails based on the name of the Models?
params is a special hash that is passed to all actions, regardless of the type. If a given action has no parameters, then it's simply empty. It's how you can pass parameters from a page/form/URL parameters into the action. One of the most common sources of parameters are data elements from a form.
In the case of authlogic, it contains user credentials for creating the user session (username, password).
Check out the parameters section for more information.
Related
Not sure whether my database architecture is correct for rails. However below is my database architecture
Database Relations
Each User instance has only one PhoneBook instance.
A single Phonebook instance can have multiple Contact instances
A single Contact instance can have multiple Mobile instances
A single Contact instance can have multiple Email instances
The question is how should I implement my controller and views if I want to add a new contact for a signed in user in his phonebook.
you can do that with accepts_nested_attributes_for:, like a nested form
you could define the current user like so
controllers/application_controller.rb
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
# or find_by_authtoken!(...)
end
then you could do
controllers/phonebooks_controller.rb
def create
#phonebook = Phonebook.create(phonebook_params)
if #phonebook.save
# redirects here
end
end
.....
def phonebook_params
params.require(:phonebook).permit(:phonebook_params....).merge(:user_id => current_user)
end
and in your contacts controller
controllers/contacts_controller.rb
def create
#contact = Contact.create(contact_params)
if #contact.save
# redirects here
end
end
.....
def contact_params
params.require(:contact).permit(:contact_params....).merge(:user_id => current_user, :phonebook_id => current_user.phonebook)
end
Like that, you can use your forms in a simple manner, without having to generate routes like /user/id/phonebook/id/contacts
in addition to the links below the first answer, maybe have a look at this basic form. It it is not a direct answer to your question, but maybe it'll help you getting an idea of how a form could look like.
I am trying my hand on rails (4). I have done some Sinatra earlier.
I have a signup form, in which user can fill out his organization name, address and his own name, password etc. I have two tables - Users and Organizations, those table get populated with Signup data. So, I have two active records model users and organizations. My controllers looks as follows:
class SignupsController < ApplicationController
# GET /signup
def new
# show html form
end
# POST /signup
def create
#signup = Registration.register(signup_params)
respond_to do |format|
if #signup.save
format.html { redirect_to #signup, notice: 'Signup was successfully created.' }
else
format.html { render action: 'new' }
end
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def signup_params
params[:signup]
end
end
I do not have any Registration model (table in db). What I am looking for such Registration model (should I call it model?) where I can have something like:
class Registration
def self.register params
o = Organization.new params
u = User.new o.id, params
self.send_welcome_email params[:email]
end
def send_welcome_email email
#send email here
end
end
1) So where should I keep this Registration class?
2) Is this a correct approach for such situation? If not, then what is the best way to do it?
3) Having such class will effect running any unit tests?
4) I see there is file, signup_helper.rb what is the use of that in SignupsController
You can do include ActiveModel::Model in your model, and it will behave as a normal Model. You will be able to do validations, callbacks. Anything other than persisting to a database.
Have a look at this for more info.
class Registration
include ActiveModel::Model
def self.register params
o = Organization.new params
u = User.new o.id, params
self.send_welcome_email params[:email]
end
def send_welcome_email email
#send email here
end
end
To answer your questions:
1) I would move the Registration class to a Signup module (as it relates to the signup use case) in app/models:
# apps/models/signup/registration.rb
module Signup
class Registration
...
end
end
2) I like your approach. It's something I would do. It is not the "Rails way" but in my opinion it is superior.
3) No. Having a PORO (plain old ruby object) is currently fine for any unit tests and you can easily test it.
4) Helpers are an (in my opinion) old and obsolete way to share functionality between views (and controllers). Don't use them unless there is absolutely no other way (maybe some Library demands it ... ). It is alway better to use POROs like you did.
You don't have to include ActiveModel::Model unless you need it's functionality. E.g. validations. You can include individual pieces of its functionality in this case. E.g
include ActiveModel::Validations
I'm setting up a simple survey on my web page.
I want to add a before_filter so that the same person can't take the survey more than once.
My idea is to
1) create and save a remember_token to each survey when it is submitted.
2) create a cookie based on that remember token to be placed on the submitter's browser
3) Every time some visits the page, use a before filter to make sure they don't have a cookie that matches a survey in the database.
I put together the below, but for some reason, it automatically redirects to the thanks_path, regardless of whether I have a remember token?
Why does it do this? Am I using the session cookie incorrectly?
My surveys_controller is as below
before_filter :new_visitor, only: [:new, :create]
def new
#this is the survey form
#survey = Survey.new
end
def create
#this submits the survey and creates a cookie on the client's browser
#survey = Survey.new(params[:survey])
if #survey.save
cookies.permanent[:remember_token] = #survey.remember_token
redirect_to thanks_path
else
render action: "new"
end
end
def thanks
#blank page that just says, "thanks for taking the survey!"
end
def new_visitor
# if a browser has a survey cookie, redirect to thanks page
unless Survey.find_by_remember_token(cookies[:remember_token]).nil?
redirect_to thanks_path
end
end
I am creating the remember token in my Survey model.
class Survey < ActiveRecord::Base
before_save :create_remember_token
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
I think you need to test for the existence of the cookie[:remember_token] before using it as an argument to find_by_remember_token(). Only if cookies[:remember_token] is not nil and a record is found do you redirect to the thanks_page.
if cookies[:remember_token] && Survey.find_by_remember_token(cookies[:remember_token])
redirect_to thanks_page
end
unless Survey.find_by_remember_token(cookies[:remember_token]).nil?
this means if Survey not nil then redirect, i think you need to change to
unless Survey.find_by_remember_token(cookies[:remember_token])
or
if Survey.find_by_remember_token(cookies[:remember_token]).nil?
Once in a while we send customized registration links to our leads. The link contains parameters than can be used to pre-fill the registration form.
http://www.example.com/users/sign_up?user[company_name]=Foo&user[region]=NA
Our registration form has fields for accepting company name and region. Which can be pre-filled based on the registration link.
This should work in practice, but it doesn't due to how the registrations#new action is implemented. The new action calls the build_resource method with an empty hash.
def new
resource = build_resource({})
respond_with resource
end
The build_resource method ignores the resource_params when the input is non nil.
def build_resource(hash=nil)
hash ||= resource_params || {}
self.resource = resource_class.new_with_session(hash, session)
end
I had to over-ride the the new action in my registrations controller to overcome this issue. I don't like my solution as it is brittle.
def new
resource = build_resource
respond_with resource
end
Is there a reason why the new action is invoked with an empty hash? Can it be invoked with out empty hash(like in the create action)?
I ended up overriding build_resource and scoping the change to new action.
def build_resource(hash=nil)
# scope the change to new actions
return super unless action_name == "new"
super.tap do |user|
user.company_name = params[:user][:company_name]
user.region = params[:user][:region]
end
end
I believe this is the intended behaviour of the build_resource method. Similar to Model.new you can either pass a hash of initializing properties or nothing, resulting in a pre-filled and an empty model respectively.
If you want to make your controller action more explicit you could instead call build_resource(params[:user]) which should avoid the brittleness you're concerned about.
Say I'm making a Q&A site like StackOverflow. I have two resources: Question and Answer. I'm using default Rails RESTful resource routes, so each resource has its own controller and methods for creating it.
In the /questions/show view, I want to allow the user to submit an answer for the particular question. The form will POST to /answers, which will get routed as a request to the AnswersController with a call to the create method.
If the answer was created, I can simply redirect back to the original question. However, I'm running into trouble dealing with validation failures on the answer object. I need to render the /question/show view and show the validation errors for the answer object. It's not clear to me how to best do this.
Here are example snippets of what the two controllers might look like.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
if #answer.save
redirect_to #answer.question
else
# What should go here??
end
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
#answer = Answer.new(:question_id => #question.id)
end
end
What should go in the else clause of the AnswersController's create method? A redirect seems wrong, since the error is really caused by the same request. Calling something like render :template => 'questions/show' seems wrong too, since I have to initialize any instance variables that the template depends on.
This style of having separate actions for calling GET to view the form for creating an object and calling POST to actually create the object seems to work well within a single controller.
How can it be done across controllers?
Try this on for size. It redirects, but passes back the dodgy answer object full of errors.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
# stash the dodgy answer if it failed to save
session[:answer] = #answer unless #answer.save
redirect_to #answer.question
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
# if we have one stashed in the session - grab it from there
# because it probably contains errors
#answer = session[:answer] || Answer.new(:question_id => #question.id)
end
end
Some details need adding (eg clearing it from the session when done) etc