How to save attributes using Form Objects in Rails - ruby-on-rails

I have a user model which consists of 8-10 attributes.
I tried to use form object concept to extract out the validations stuffs into another UserForm Class.
FYI I am using Rails 4 :)
My controller :
class UsersController < ApplicationController
def create
#user = UserForm.new(user_params)
#user.save
end
def user_params
# Granted permission for all 10 attributes.
params.require(:user).permit(:first_name, :last_name, :email....)
end
end
My custom class looks like this:
class UserForm < ActiveModel::Validator
# like this i have 10 attributes
attr_accessor :first_name, :last_name, :email, ....
#validation for all 10 attributes
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
#I think this is a bad idea, putting all 10 attributes.
#User.create(first_name: first_name, email: email, .... )
# what better solution we can have here ?
end
end
Now everything seems quite good so far. Just I am confused how to get all attributes saved directly with User.create (in persist! method) rather than manually assigning each and every value ?

UserFrom.create(user_params)
Also, why not just User.create(user_params) ?

have you looked into "Virtus" gem. it makes dealing with Form object really easy.
https://github.com/solnic/virtus
class UserForm < ActiveModel::Validator
include Virtus.model
attr_accessor :user
attribute :first_name, String
attribute :last_name, String
attribute :email, String
and so on..
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
#user = User.create(self.attributes)
end
end

Related

Setting a basic entity while creating a new user in Devise

Im using Devise to create my users in an App with Ruby on Rails.
I have a User model that has a Plan (hobby,premium, etc...)
When creating a new user, I want to add the basic plan to this new user (for business rules needs, I cant leave it blank).
The question is, how can I add this plan when creating this new user?
Here is my controller:
class RegistrationsController < Devise::RegistrationsController
clear_respond_to
respond_to :json
def save_user_type
session[:user_type] = params[:user_type]
end
private
def sign_up_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :type, :provider )
end
end
In which method should I add something like this?
#user.plan = Plan.first
#user.save
class User < ActiveRecord::Base
has_one :plan
after_create :build_default_plan
private
def build_default_plan
plan.create(#paln_params)
#.. so on
end
end
Added this line to the user model
after_create do |user|
user.plan = Plan.first
user.save
end

One Model and two controller validation Ruby on Rails?

One thing that confuses me the most is when doing validation in one model with two controllers. I have a login system which register and logs users in. There both use the same model but both does not use the same amount of HTML widgets. One controller contains password, retype password, user name, first & second name and so on. The second controller uses only the user name and password fields. How would you do validation in the same model for this situation?
Thank you
here is the controller that register new users:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to '/cool'
else
#user = Newuser.new
#user.valid?
#user.errors.messages
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :salt, :firstname, :secondname, :address, :postcode)
end
end
second controller:
class LoginsController < ApplicationController
before_filter :authorize
def index
#rentals = Rental.where(user_id: current_user.id).limit(5)
#buys = Buy.where(user_id: current_user.id).limit(5)
#users = User.where(id: current_user.id)
#buyGames = BuyGame.where(user_id: current_user.id).limit(5)
end
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to '/logout'
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(account_params)
redirect_to '/cool'
else
render 'edit'
end
end
private
def account_params
params.require(:user).permit(:name, :email, :password, :salt, :firstname, :secondname, :address, :postcode)
end
end
Here is my model:
class User < ApplicationRecord
has_secure_password
end
One way to go is to remove validations from the model and put them in form objects. For this case, you'll have two form objects, each with its own set of validations. And you use the appropriate one in respective controllers. Something along these lines:
# logins_controller
def update
login_form = FormObjects::LoginForm.new(login_params)
if login_form.valid?
redirect_to '/cool'
else
render 'edit'
end
end
# users controller
def create
signup_form = FormObjects::SignupForm.new(user_params)
if signup_form.save
redirect_to '/cool'
else
render 'new'
end
end
# signup_form
module FormObjects
class SignupForm
include ::ActiveMode::Model
validate_presense_of :email, :password, :password_confirmation, :address, :whatever_else
def save
# create user here
end
end
end
# login_form
module FormObjects
class LoginForm
include ::ActiveMode::Model
validate_presense_of :email, :password
end
end
You can simply specify validations on actions, that is:
validates :first_name, presence: true, on: :create # which won't validate presence of first name on update or any other action
I believe the trick you are looking for is to define validation actions on create/update of the model. Something roughly along these lines:
class User < ActiveRecord::Base
# These are example validations only; replace with your actual rules.
validates :password, confirmation: true
validates_presence_of :username
validates :first_name, presence: true, format: {with: /.../}, on: create
validates :last_name, presence: true, format: {with: /.../}, on: create
end
...However, I am unclear why you would want to do this in your specific example. It would be advisable to always run all validation checks on fields like first_name, to help maintain data integrity.

Rails - validation inside decorator

I'm struggling with some kind of issue. I have a rails model (mongoid).
class User
include Mongoid::Document
include ActiveModel::SecurePassword
validate :password_presence,
:password_confirmation_match,
:email_presence,
field :email
field :password_digest
def password_presence
end
def email_presence
end
def password_confirmation_match
end
end
My goal is to call validations depends on which decorator I will use. Let's say I've got two decorators:
class PasswordDecorator < Draper::Decorator
def initialize(user)
#user = user
end
end
def RegistraionDecorator < Draper::Decorator
def initialize(user)
#user = user
end
end
So now when I create/save/update my user object inside RegistraionDecorator I would like to perform all validation methods.
RegistraionDecorator.new(User.new(attrbiutes))
But when I will do it inside PasswordDecorator I want to call for example only password_presence method.
PasswordDecorator.new(User.first)
When I move validations to decorator it won't work cuz its different class than my model.
How can I achieve that?
Try to use a Form Object pattern instead.
Here is an example (from a real project) of how it could be done with reform.
class PromocodesController < ApplicationController
def new
#form = PromocodeForm.new(Promocode.new)
end
def create
#form = PromocodeForm.new(Promocode.new)
if #form.validate(promo_params)
Promocode.create!(promo_params)
redirect_to promocodes_path
else
render :edit
end
end
private
def promo_params
params.require(:promocode).
permit(:token, :promo_type, :expires_at, :usage_limit, :reusable)
end
end
class PromocodeForm < Reform::Form
model :promocode
property :token
property :promo_type
property :expires_at
property :usage_limit
property :reusable
validates_presence_of :token, :promo_type, :expires_at, :usage_limit, :reusable
validates_uniqueness_of :token
validates :usage_limit, numericality: { greater_or_equal_to: -1 }
validates :promo_type, inclusion: { in: Promocode::TYPES }
end
Bonus: The model does not trigger validations and much easy to use in tests.

Mixed attr_accessible and strong_parameters, how to skip attr_accessible?

First of all, I am fully aware of the bad practice with mixing the two.
I have a model that has attr_accessible set up. I'd like to start transitioning our application to strong_parameters. The problem is that I need to do this piecemeal as we refactor individual parts of the application. Is there a ActiveRecord method I can use to update the attributes that bypasses attr_accessible for right now? Or can I define a attr_accessible=false type of thing that bypasses it?
Code example:
Model:
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password
end
Controller:
class UsersController < ApplicationController
before_action :set_user
def update
#user.assign_attributes(user_params)
#user.save!
end
private
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(
:first_name, :last_name, :email, :other_attribute_not_in_accessible
)
end
end
Found it!
In the controller, do this:
#user.update_attributes(user_params,:without_protection=>true)
And then it'll work.

Rails 4 Strong Params Error

I am using rails 4. I have a model that uses validation, but does not store any records. It is only used for a web contact form that sends an email.
I am trying to use strong parameters with this controller/model. Currently I am getting a nomethoderror on my new action. Any ideas? I think it is something to do with my model not being a full blown model.?
Code slimmed down for easy viewing.
model:
class Message
include ActiveModel::Model
include ActiveModel::ForbiddenAttributesProtection
end
controller:
class MessagesController < ApplicationController
def new
#message = Message.new
end
private
def project_params
params.require(:message).permit(:name, :email, :content)
end
end
Your project_params needs to be renamed to message_params since you want to allow message in your MessagesController and not project.
Please try:
class MessagesController < ApplicationController
def new
#message = Message.new
end
private
def message_params
params.require(:message).permit(:name, :email, :content)
end
end
Update:
Also, although you've mentioned "code slimmed down for easy viewing", I should add that you also need to define att_accessor for those permitted attributes as follows:
class Message
include ActiveModel::Model
include ActiveModel::ForbiddenAttributesProtection
attr_accessor :name, :email, :content
end
Please see Louis XIV's answer in this question: "Rails Model without a table"

Resources