简体   繁体   中英

Updating only some attributes in a rails model

I am trying to create an account management page in which you can update your display name, email, password, and other stuff. I want to allow the user to update only part of their information without the model rejecting the changes if unchanged info is invalid. I need to update only part of a model, specifically updating account details without having to enter a password and confirmation.

Using a single form requires the user to change their password when updating details. I tried splitting the account details into a "profile" and "security" form hopefully to not send an empty password when updating somebody's name, but the model rejects the change noting the password is missing. However, updating only the password with the two form setup seems to work just fine.

I am looking for a general solution that would let me expand to only update a specific subset of fields in the future (adding 2FA to the security section would require the user to change their password to update their 2FA key as is).

The only outside gems I am using are the Have I Been Pwned gem and an updated email_check gem .

My current code is:
routes.rb

Rails.application.routes.draw do
  # ...
  get '/account', to: 'account#index'
  post '/account', to: 'account#update'

  root 'application#home'
end

the account model

class Account < ApplicationRecord
  self.primary_key = :id

  before_validation do
    self.id.to_s.downcase! # lowercase the id
    self.email.to_s.downcase! # lowercase the email
  end

  validates :id,
            presence: true,
            length: { minimum: 5, maximum: 32 },
            uniqueness: true,
            format: { with: /\A[a-z0-9-]*\z/, message: 'Only lowercase alphabet, numbers and dash allowed.' }
  validates :name,
            presence: true,
            length: { minimum: 2, maximum: 50 }
  validates_email_strictness :email,
                  message: 'Something about that email doesn\'t look right... Make sure the spelling is right or try another?'

  has_secure_password
  validates :password,
            presence: true,
            not_pwned: true,
            format: { with: /\d/ },
            length: { minimum: 8 }
  attr_accessor :password_confirmation

  attr_accessor :remember_token
end

account_controller.rb

class AccountController < ApplicationController
  def index
    if logged_in?
      @account = current_account
    else
      flash[:danger] = "You must be logged in to do that!"
      redirect_to '/account/login'
    end
  end
  def update
    @account = current_account
    # TODO security concerns or otherwise when receiving only profile, security, etc fields
    if @account.update_attributes(account_params)
      flash.now[:success] = 'Update success!'
    else
      flash.now[:danger] = 'Something went wrong!'
    end
    render 'index'
  end

  private def account_params
    params.require(:account).permit(:id,:name,:email,:password,:password_confirmation)
  end
end

and the account/index.html.erb

<% provide(:title,'Manage Account') %>

<h1>Manage Account</h1>

<%= render 'shared/error_messages' %>

<h3>Profile & Contact Info</h3>
<div>
  <p>Account ID</p>
  <input disabled value="<%= @account.id %>">
  <form action="/account" method="post">
    <input name="utf8" type="hidden" value="&#x2713;" />
    <%= hidden_field_tag :authenticity_token, form_authenticity_token %>

    <p>Name</p>
    <input name="account[name]" value="<%= @account.name %>">
    <p>Email</p>
    <input name="account[email]" value="<%= @account.email %>" type="email">

    <button type="submit">Submit</button>
  </form>
</div>
<h3>Password & Security</h3>
<form action="/account" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <%= hidden_field_tag :authenticity_token, form_authenticity_token %>

  <p>New Password</p>
  <input name="account[password]" type="password">
  <p>Confirm New Password</p>
  <input name="account[password_confirmation]" type="password">

  <button type="submit">Submit</button>
</form>

If you want to validate password field only when it is entered then use proc object conditionally as below. This will allow other fields to update irrespective of the password field

  validates :password,
            presence: true,
            not_pwned: true,
            format: { with: /\d/ },
            length: { minimum: 8 }, unless: Proc.new { |account| account.password.blank? }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM