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.
Related
I've got User model with validation:
validates :experience_level, inclusion: { in: EXPERIENCE_LEVEL, allow_blank: true }
But one of the part of full registration is to update User's experience level. User can do this by inside of below controller:
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params)
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
And for that endpoint I want to use
validates :experience_level, presence: true, inclusion: { in: EXPERIENCE_LEVEL }
I know I could use on: :update but in such case User will not be able to update e.g. password if it doesn't go through the experience update form first.
If you want to make the model state aware you can do it by explicitly passing information into the model:
class User < ApplicationRecord
attr_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
stage == :add_experience_level
end
end
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params.merge(stage: :add_experience_level))
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
end
There is also ActiveSupport::CurrentAttributes:
Abstract super class that provides a thread-isolated attributes
singleton, which resets automatically before and after each request.
This allows you to keep all the per-request attributes easily
available to the whole system.
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :stage
end
def update
authorize current_user
Current.stage = :add_experience_level
end
class User < ApplicationRecord
attribute_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
Current.stage == :add_experience_level
end
end
Its really up to you if you want use it though as it can be considered harmful. If it quacks like a global...
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
I'm using Active Model Serializer 0.10.7 in rails 5
and I wanna know how to access devise current_user in serializer.
current_user is supposed to be set for scope by default.
according to doc
https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/serializers.md#controller-authorization-context
but my code doesn't work well...
anybody knows about this?
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
def is_reviewed
object.reviews.pluck(:user_id).include?(current_user.id)
end
end
and Book controller look like this.
class BooksController < ApplicationController
def index
#books = Book.order(created_at: :desc).page(params[:page])
respond_to do |format|
format.html
format.json {render json: #books, each_serializer: BookSerializer}
end
end
end
It is possible to pass a scope into your serializer when instantiating it in the controller (or elsewhere for that matter). I appreciate that this is for individual objects and not arrays of objects:
BookSerializer.new(book, scope: current_user)
Then in your Book Serializer you can do:
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
private
def is_reviewed
object.reviews.pluck(:user_id).include?(current_user.id)
end
def current_user
scope
end
end
Devise doesn't expose the current_user helper to models or serializers - you can pass the value to the model from the controller, or set it in a storage somewhere.
Some examples from other answers:
https://stackoverflow.com/a/3742981/385532
https://stackoverflow.com/a/5545264/385532
in application controller:
class ApplicationController < ActionController::Base
...
serialization_scope :view_context
end
in serializer:
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
def is_reviewed
user = scope.current_user
...
end
end
If you are using active_model_serializers gem, then it is straight forward.
In your serializer just use the keyword scope.
Eg:-
class EventSerializer < ApplicationSerializer
attributes(
:id,
:last_date,
:total_participant,
:participated
)
def participated
object.participants.pluck(:user_id).include?(scope.id)
end
end
You can also pass view context into serializer from controller when you want to intialize serializer your self instead of render json: book.
# controller
BookSerializer.new(book, scope: view_context)
# serializer
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
private
def is_reviewed
object.reviews.pluck(:user_id).include?(scope.current_user.id)
end
end
seems there is a typo, you have is_reviewed and you defined method has_reviewed
so it should be like this
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
def is_reviewed
object.reviews.pluck(:user_id).include?(current_user.id)
end
end
The following code works but I don't understand why.
The Model: I have a Class called Contact that doesn't have an initialize method (i.e it inherits the initialize method from the default Object class).
class Contact
include ActiveModel::Model
attr_accessor :name, :string
attr_accessor :email, :string
attr_accessor :content, :string
validates_presence_of :name
validates_presence_of :email
validates_presence_of :content
...
end
The controller: I have a ContactsController with a 'create' method that instantiates the Contact class passing along some parameters through the 'secure_params' method.
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
# THIS IS THE LINE THAT I DON'T UNDERSTAND
#contact = Contact.new(secure_params)
if #contact.valid?
#contact.update_spreadsheet
UserMailer.contact_email(#contact).deliver
flash[:notice] = "Message sent from #{#contact.name}."
redirect_to root_path
else
render :new
end
end
private
def secure_params
params.require(:contact).permit(:name, :email, :content)
end
end
Where do this parameters go to if there is no initialize method that sets them to instance variables and the default behavior of the 'new' method (inherited from the Ruby's Object class) does nothing with passed in parameters?
Why do they end up being set as instance variables? (something to do with the attr_accesors?)
You are including ActiveModel::Model which defines the initialize method that sets the values.
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