#416 Form Objects pro
Jun 03, 2013 | 18 minutes | Refactoring, Forms
Models have a tendency to become a complex mess as an application grows. In this episode you will learn a couple of techniques to extract form-behavior out into its own class.
- Download:
- source codeProject Files in Zip (69.4 KB)
- mp4Full Size H.264 Video (45 MB)
- m4vSmaller H.264 Video (25.6 MB)
- webmFull Size VP8 Video (29.6 MB)
- ogvFull Size Theora Video (59.7 MB)
Resources
- 7 Refactoring Patterns for Models
- Virtus
- Reform
- Episode 398: Service Objects
- Episode 371: Strong Parameters
app/forms/password_form.rb
class PasswordForm # Rails 4: include ActiveModel::Model extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations def persisted? false end attr_accessor :original_password, :new_password validate :verify_original_password validates_presence_of :original_password, :new_password validates_confirmation_of :new_password validates_length_of :new_password, minimum: 6 def initialize(user) @user = user end def submit(params) self.original_password = params[:original_password] self.new_password = params[:new_password] self.new_password_confirmation = params[:new_password_confirmation] if valid? @user.password = new_password @user.save! true else false end end def verify_original_password unless @user.authenticate(original_password) errors.add :original_password, "is not correct" end end end
app/forms/signup_form.rb
class SignupForm # Rails 4: include ActiveModel::Model extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations def persisted? false end def self.model_name ActiveModel::Name.new(self, nil, "User") end validates_presence_of :username validate :verify_unique_username validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ validates_length_of :password, minimum: 6 delegate :username, :email, :password, :password_confirmation, to: :user delegate :twitter_name, :github_name, :bio, to: :profile def user @user ||= User.new end def profile @profile ||= user.build_profile end def submit(params) user.attributes = params.slice(:username, :email, :password, :password_confirmation) profile.attributes = params.slice(:twitter_name, :github_name, :bio) self.subscribed = params[:subscribed] if valid? generate_token user.save! profile.save! true else false end end def subscribed user.subscribed_at end def subscribed=(checkbox) user.subscribed_at = Time.zone.now if checkbox == "1" end def generate_token begin user.token = SecureRandom.hex end while User.exists?(token: user.token) end def verify_unique_username if User.exists? username: username errors.add :username, "has already been taken" end end end
users_controller.rb
def new @signup_form = SignupForm.new end def create @signup_form = SignupForm.new if @signup_form.submit(params[:user]) session[:user_id] = @signup_form.user.id redirect_to @signup_form.user, notice: "Thank you for signing up!" else render "new" end end
users/new.html.erb
<%= form_for @signup_form do |f| %>
passwords_controller.rb
def new @password_form = PasswordForm.new(current_user) end def create @password_form = PasswordForm.new(current_user) if @password_form.submit(params[:password_form]) redirect_to current_user, notice: "Successfully changed password." else render "new" end end
passwords/new.html.erb
<%= form_for @password_form, url: passwords_path, method: :post do |f| %>