I've been going through the documentation for getting ActiveRecord validation working with ActiveModel. For some reason I am not seeing any validation results returned.
I have a set of models which instead of interfacing with ActiveRecord are interfacing through a custom API that will be sitting behind Rails.
The Model:
class ApiObject < ApiConnector
include ActiveModel::Validations
attr_accessor :fieldName
validates :fieldName, :presence => true
def save
#save method implementation
end
end
The Controller:
def create
#apiObject = ApiObject.new(params[:api_object])
respond_to do |format|
if #apiObject.save
format.html { redirect_to(#apiObject, :notice => 'User was successfully created.') }
format.xml { render :xml => #apiObject, :status => :created, :location => #apiObject }
else
format.html { render :action => "new" }
format.xml { render :xml => #apiObject.errors, :status => :unprocessable_entity }
end
end
end
The Form:
<%= form_for :api_object, :url => '/apiobjectcontroller/' do |f| %>
<%= f.label :fieldName, 'Field Name' %>
<%= f.text_field :fieldName %>
<%= f.submit 'Create'%>
<% end %>
I am following the code laid out here: Rails ActiveModel Validation
The method is correctly returning to the form because #apiObject.save is returning as false, but no validation response is coming back. I've checked the markup and the usual rails validation results are not returned. What am I missing?
I have similar code that works, but I have an initialize method in my classes. Perhaps your model should be:
class ApiObject < ApiConnector
include ActiveModel::Validations
validates :fieldName, :presence => true
attr_accessor :fieldName
def initialize(fieldName)
#first_name = fieldName
end
def save
return false unless valid?
# save method implementation to go here
# ...
true # if save successful, otherwise, false
end
end
If the above works, and you end up having a lot of attributes to assign in your initializer, then you could use this old trick:
def initialize(attributes = {})
attributes.each do |name, value|
instance_variable_set "##{name}", value
end
end
EDIT: Added a call to valid? in save implementation, so that errors collection will be filled out.
This should fully answer:
http://asciicasts.com/episodes/211-validations-in-rails-3
In a nutshell: create your form with an instance variable + add the necessary code to display your errors.
Related
I have two models: one for contacts ("Contatos") and one for users ("Usuarios"). Contatos has_one Usuario , as follows:
class Contato < ApplicationRecord
has_one :usuario, dependent: :destroy
accepts_nested_attributes_for :usuario,
allow_destroy: true
And
class Usuario < ApplicationRecord
has_secure_password
belongs_to :contato
validates_presence_of :login, :password
validates_uniqueness_of :login
end
I want to use one form for creating and editing both models. The _form partial that I currently have is this:
<%= form_with(model: contato, local: true) do |contato_form| %>
<%= if contato.errors.any?
showferr contato
end %>
#Here are the inputs for contato, I cut them out so it wouldn't be too long to read.
Bellow (same file as above) there is a check box for the Contato model that I left on, it sets a Boolean in the model(and DB) telling if the contact has a user on not, additionally I use some JavaScript (Coffee) to toggle the whole user (Usuario) form part based on the checkboxe's value .
<div class="form-group">
<%= contato_form.label :possui_usuario, :class => 'inline-checkbox' do %>
Possui usuário
<%= contato_form.check_box :possui_usuario, {id: "hasUser", checked: #contato.possui_usuario} %>
<% end %>
</div>
</div>
<div id="userPart" class="findMe" <% unless #contato.possui_usuario %> style="display:none;" <% end %> >
<h2> Usuário: </h2>
<div class="container">
<%= contato_form.fields_for :usuario, #contato.usuario do |usuario_form| %>
<%= render partial: 'usuarios/campos_usuario', locals: {form: usuario_form, object: #contato} %>
<% end %>
</div>
</div>
<br/>
<div class="container-fluid text-right">
<%= contato_form.submit 'Confirmar', :class => 'btn-lg btn-success' %>
</div>
<% end %>
The partial form for the Usuario model is rendering ok, but what I want to do is to only create and/or validate the user part if the checkbox is selected (if I say that the contact does have a user).
Here's what I attempted last (there were many attempts):
At Contato model:
attr_accessor(:has_user)
#has_user = 0
before_validation do |record|
#has_user = record.possui_usuario
end
def self.user?
#has_user == 1
end
validates_presence_of :nome
validates_length_of :nome, in: 1..45
validates_presence_of :email
validates_format_of :email, with: email_regex
validates_associated :usuario, if: user?
Controller for Contato:
class ContatosController < ApplicationController
before_action :set_contato, only: [:show, :edit, :update, :destroy]
# GET /contatos
# GET /contatos.json
def index
#contatos = Contato.all
#page_title = 'Contatos'
end
# GET /contatos/1
# GET /contatos/1.json
def show
#page_title = 'Ver contato: ' + #contato.nome
end
# GET /contatos/new
def new
#contato = Contato.new
#contato.build_usuario
#contato.ativo = true
#page_title = 'Novo contato'
end
# GET /contatos/1/edit
def edit
#page_title = 'Editar contato: ' + #contato.nome
unless #contato.possui_usuario
#contato.build_usuario
end
end
# POST /contatos
# POST /contatos.json
def create
#contato = Contato.new(contato_params)
respond_to do |format|
if #contato.save
flash[:notice] = 'Contato foi criado com sucesso.'
format.html {redirect_to #contato}
format.json {render :show, status: :created, location: #contato}
else
flash[:warn] = "Erro ao criar contato."
format.html {render :new}
format.json {render json: #contato.errors, status: :unprocessable_entity}
end
end
end
# PATCH/PUT /contatos/1
# PATCH/PUT /contatos/1.json
def update
respond_to do |format|
if #contato.update(contato_params)
format.html {redirect_to #contato, notice: 'Contato foi atualizado com sucesso.'}
format.json {render :show, status: :ok, location: #contato}
else
format.html {render :edit}
format.json {render json: #contato.errors, status: :unprocessable_entity}
end
end
end
# DELETE /contatos/1
# DELETE /contatos/1.json
def destroy
#contato.destroy
respond_to do |format|
format.html {redirect_to contatos_url, notice: 'Contato deletado com sucesso.'}
format.json {head :no_content}
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_contato
#contato = Contato.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def contato_params
params.require(:contato).permit(:id, :empresa_id,
:ativo, :nome,
:cargo, :celular,
:email, :nascimento,
:observacoes, :mensagem_instantanea,
:tipo_msg_inst, :possui_usuario,
usuario_attributes: [:login, :password, :permissoes, :id, :contato_id, :_destroy])
end
end
Sorry for the long question and big code blocks.
I see two holes in the data presented currently ...
First, your controller action where create is called is where you should be testing to see if you are calling to the model / activerecord.
Something like ...
def create
if #contato && #contato.usuarios # might be able to just do last half
respond_to do |format|
if #contato = #contato.create!(contato_params) # note the bang or '!'
format.html { redirect_to #contato, notice: 'contato was successfully created.' }
else
format.html { render :new }
end
end
end
end
Without seeing your controller - I am going to guess you didn't nest your controller via Rails strong_param feature properly. Note here - these two won't run, I'm not quite sure what information is needed, but I wanted you to make sure if you are nesting your models and using a single controller - you are away you need to nest your models in strong_params (google search nested rails strong_params for thousands of help / hits).
params.require(:contato).permit(:login, :password, usuario: [id, ...] )
If that's not it - also tell us if all the functionality of create/read/update/destroy works normally & you are just looking to limit it to create in certain circumstances?
Update - based on the controller - just move your check for create from the model & move it to the controller at the start of the #create action ... maybe start with ...
def create
# Note - here you will have to inspect contato_params to find syntax
if contato_params[:usuario_attributes][:contato_id]
... rest of action wrapped in here ...
end
end
... once again ... you will need to work out exact syntax - but just like you did with the edit - this spot is where you control the creation - not in the model.
More specifically I see this #contato.possui_usuario in the form ... that's probably the variable you want to check against in your controller, but perhaps my suggestion is more important - I can't tell you that with certainty - I'm also not sure you need the has_user trick per say in model & might be tempted to do a controller version in the private method section ...
class ContatosController
private
def has_user?
... whatever ...
end
Clarification from comment:
If I move the control over the user form part to the controller (which
makes a lot of sense) how would I about canceling the
validates_associated part of the model in case the user decides that
this contact wont have any users?
You don't move the form control (defined as variable in the form), you move the model method that deals with the form control to the controller - then you can wrap it all in a transaction to rollback any other changes OR if you build your activerecord out with #build it will do it for you.
I want to allow an update but want to exclude the update of a specific column and create it's own update method.
Question: Is there a way for me to define my order params but exclude a specific column? Or even separate the columns into methods?
def order_params
params.require(:order).permit(:name, :email, :image, :video, :description)
end
My issue is that, I have a stripe charge in my update method so when i simply update an orders "order_status", the customer gets charged.
I have a column "order_status" which can be changed by enum 1,2,3 and i want to avoid that within it the update so no charge is made.
I created:
def order_status
params.permit(:order_status)
end
But since the original order_params has :orders, the order status is still being included.
I have the enum as, "created", "charged", and "cancelled". A seller can cancel an order with:
<%= form_for #order, remote: true do |f| %>
<%= f.hidden_field :order_status, value: "cancelled" %>
<%= f.button type:'submit', class: "btn btn-danger", onclick: "refreshPage()" %>
<% end %>
What should i do to have this "order_status" update be on its own and out of the original update method so customers don't get charged?
Update:
I did find a way to simply create it's own method for charging and taking the charge out of the update method with all the params..
def charge_update
respond_to do |format|
#amount = (#order.order_price).to_i * 100
#amount_seller = (#order.order_price).to_i * 75
if #order.update(params[:tracking_number])
if user_signed_in?
charge = Stripe::Charge.create({
:amount => (#order.order_price).to_i * 100,
:description => 'Rails Stripe customer',
:currency => 'usd',
:customer => #order.stripe_customer_token,
:destination => {
:amount => #amount_seller ,
:account => (#order.seller.stripe_token),
}
})
#order.order_status = "charged"
format.html { redirect_to #order, notice: 'Order was successfully uploaded.' }
format.json { render :show, status: :ok, location: #order }
else
format.html { render :edit }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
end
The customer only gets charged once the order is sent.
But, how can i specify the params status itself?
For instance, I want to create a method for the order_status, but only for when the order_status = "cancelled"... and then create 2 more methods for the other 2 possibly status'
?
So when I/seller cancels an order, I can create a custom message, send emails, etc. and do the same for "charged", and "created"....
Is there a way for me to define my order params but exclude a specific
column?
Easy. The #except method can be used to remove a key or keys from the (whitelisted) params hash.
order_params.except(:order_status)
Or even separate the columns into methods?
One thing to remember is that ActionController::Parameters is actually a pretty basic hash like object and you can compose your whitelist functions however you want.
#permit works somewhat like Hash#slice on steroids - it returns a new ActionController::Parameters instance with the permitted attribute set to true. It also takes a hash argument for nested parameters.
You can use also use arrays to compose whitelists from different methods. This can be pretty nifty since all the methods in ActionController::Parameters are reductive. The trick is using the splat(*) operator to convert the array to a list of arguments:
def base_parameters
[:a, :b, :c, :d]
end
def others
[:e, :f]
end
def create_parameters
params.require(:order)
.permit(*base_parameters.concat(others))
end
Recently I added validations to one of the models in my application, and this seems to have caused a somewhat strange behaviour that I'm not handling properly in my code.
Here's a hypothetical example:
Clients
# name: string, phone: string, address: string
class Client < ActiveRecord::Base
has_many :transactions
accepts_nested_attributes_for :transaction, allow_destroy: true
validate :phone, numericality: true
end
Transactions
# p_date: date, location_id: integer
class Transaction < ActiveRecord::Base
belongs_to :client
end
This is how the controller would look like (again, have in mind that this is hypothetical):
PurchasesController
before_action :set_client, only: [:show, :edit, :update, :destroy]
def update
respond_to do |format|
if #client.update(client_params)
format.html { redirect_to clients_path, notice: 'Updated Succesfully' }
format.json { render :show, status: :ok, location: #client }
else
format.html { render :edit }
format.json { render json: #client.errors, status: :unprocessable_entity }
end
end
end
def client_params
params.require(:client).permit(
:name, :phone, :address,
transaction_attributes: [:id, :p_date, :location_id, :_destroy]
)
end
def set_client
#client = Client.find(params[:id])
end
For new records this works fine, but when I run into old ones that do not conform to the new validation rules in the phone number, the nested attributes aren't saved, because it's parent record is no longer valid.
I'm trying to find a way how to handle such errors.
Currently, this would be handled by the else condition in if #client.update(client_params) in the controller. When an error happens, the controller renders the :edit action, which results in another error in my view, cause now the helper that generates the fields for the nested form is receiving a null value for #client.
The view in question that generates the error looks like this:
purchases/:client_id/edit.html.haml
= form_for #client, :url => {:controller => 'purchase', :action => 'update'} do |f|
- if #client.errors.any?
#error_explanation
%h2
The following errors were found:
%ul
- #client.errors.full_messages.each do |message|
%li= message
=render 'form', f: f
.actions
=f.submit 'Save Changes', :class => 'btn btn-md btn-primary'
The error says: "First argument in form cannot contain nil or be empty", which I'm assuming it is cause the render is not sending the id of the Client.
In case you're wondering, I'm using form_for #client, :url => {:controller => 'purchase', :action => 'update'} do |f| cause this view is not in the Client controller. if I omit the extra parameters, the form is sent directly to the Client controller, which has different code pertaining only to the Client model.
I've partially managed to work around this by using the following in the update action:
def update
respond_to do |format|
if #client.update(client_params)
# *snip*
else
format.html { redirect_to edit_purchase_path(#client) }
format.json { render json: #client.errors, status: :unprocessable_entity }
end
end
end
This will redirect me back to the edit action, but no errors are printed. I get an identical page with the values used before I edited the values of the Transaction. I'm think there must be an easy way to send the errors back to the view, but I'm not sure where are these supposed to be used.
The "original" controller was generated by a scaffold, so the render :edit part is from the scaffolding itself. I'm aware my example could be somewhat vague (I'm just transcribing what I'm experiencing), so bear with me if this sounds a little odd. I'll gladly go into more detail if the information provided isn't enough.
I have a table CLIENTS with id, name and email fields and I am sending them emails using ActionMailer with 3rd party SMTP.
Now I want the clients to have subscription option too so I added "subscription" column with default value as true.
Now how to generate a link which can be put in views mailer template so when the user clicks on it, the subscription value changes to false so in future the client dont' get any email ? Do note that these clients are not my rails app users so I can't uses what is been suggested here Rails 3.2 ActionMailer handle unsubscribe link in emails
I found this link how to generate link for unsubscribing from email too which looked helpful but I thought may be in 3 years, we might have got a better solution
Here is my Complete Code -
#client.rb
attr_accessible :name, :company, :email
belongs_to :user
has_many :email_ids
has_many :emails, :through => :email_ids
before_create :add_unsubscribe_hash
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
Here is Clients_controller.rb file
# clients_controller.rb
def new
#client = Client.new
respond_to do |format|
format.html
format.json { render json: #client }
format.js
end
end
def create
#client = current_user.clients.new(params[:client])
respond_to do |format|
if #client.save
#clients = current_user.clientss.all
format.html { redirect_to #client }
format.json { render json: #client }
format.js
else
#clients = current_user.clients.all
format.html { render action: "new" }
format.json { render json: #client.errors, status: :error }
format.js
end
end
end
def unsubscribe
#client = Client.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#client.update_attribute(:subscription, false)
end
The code is working fine for existing records and the unsubscription is working perfectly, I am only having problem in creating new clients.
I have used #client in unsubscribe method as I am using this object in client_mailer.rb template (using #client or just using client, both are working!)
EDIT 2 -
_form.html.erb
<%= simple_form_for(#client, :html => {class: 'form-horizontal'}) do |f| %>
<%= f.input :name, :label => "Full Name" %>
<%= f.input :company %>
<%= f.input :email %>
<%= f.button :submit, class: 'btn btn-success' %>
<% end %>
I have copied the full track stack at http://jsfiddle.net/icyborg7/dadGS/
Try associating each client with a unique, but obscure, identifier which can be used to look up (and unsubscribe) the user via the unsubscribe link contained within the email.
Start by adding another column to your clients table called unsubscribe_hash:
# from command line
rails g migration AddUnsubscribeHashToClients unsubscribe_hash:string
Then, associate a random hash with each client:
# app/models/client.rb
before_create :add_unsubscribe_hash
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
Create a controller action that will toggle the subscription boolean to true:
# app/controllers/clients_controller.rb
def unsubscribe
client = Client.find_by_unsubscribe_hash(params[:unsubscribe_hash])
client.update_attribute(:subscription, false)
end
Hook it up to a route:
# config/routes.rb
match 'clients/unsubscribe/:unsubscribe_hash' => 'clients#unsubscribe', :as => 'unsubscribe'
Then, when a client object is passed to ActionMailer, you'll have access to the unsubscribe_hash attribute, which you can pass to a link in the following manner:
# ActionMailer view
<%= link_to 'Unsubscribe Me!', unsubscribe_url(#user.unsubscribe_hash) %>
When the link is clicked, the unsubscribe action will be triggered. The client will be looked up via the passed in unsubscribe_hash and the subscription attribute will be turned to false.
UPDATE:
To add a value for the unsubscribe_hash attribute for existing clients:
# from Rails console
Client.all.each { |client| client.update_attribute(:unsubscribe_hash, SecureRandom.hex) }
Second question on here, I'd really like to solve this one myself but I just don't know where to start to debug it.
So here is my error in the browser (which occurs when I go to check out and enter my details in order/_form.html.erb)
ArgumentError in OrdersController#new
You need to supply at least one validation
Rails.root: C:/Users/Ruby/rails_practice/depot4
Application Trace | Framework Trace | Full Trace
app/models/payment_type.rb:6:in <class:PaymentType>'
app/models/payment_type.rb:1:in'
app/models/order.rb:7:in <class:Order>'
app/models/order.rb:1:in'
app/controllers/orders_controller.rb:1:in `'
And here is my def new in OrdersController:
def new
#cart = current_cart
if #cart.line_items.empty?
redirect_to store_url, :notice => "Your cart is empty"
return
end
#hide_checkout_button = true
#order = Order.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #order }
end
end
The thing is that I haven't touch def new, I've been working on def create, which is here:
def create
#order = Order.new(params[:order])
#order.add_line_items_from_cart(current_cart)
#cart = current_cart
#hide_checkout_button = true
pay_type = PaymentType.find( :conditions => ['pay_type = ?', #order.pay_type] )
#order.payment_type_id = pay_type.id
respond_to do |format|
if #order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
format.html { redirect_to(store_url, :notice => 'Thank you for your order.') }
format.json { render json: #order, status: :created, location: #order }
else
format.html { render action: "new" }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
What I am trying to do there is create an order which is which belongs_to a payment_type and has_many line_items which belongs_to a cart.
Incidentally, I am also trying to hide_checkout_button with an instance variable when I am on the order page.
The Orders table has a foreign key to the PaymentTypes table and I am trying to find the correct id from this PaymentTypes table for the payment_type submitted by the user.
If I comment out these two lines:
pay_type = PaymentType.find( :conditions => ['pay_type = ?', #order.pay_type] )
#order.payment_type_id = pay_type.id
Sometimes I get a different error:
NoMethodError in OrdersController#new
undefined method `key?' for nil:NilClass
I think this is to do with incorrect caching in the browser but I'm not sure what the connection is.
I will update with the rest after I post this first
Part deux
I know that this is about validation, but I can't see what I am doing wrong... order.rb:
class Order < ActiveRecord::Base
attr_accessible :address, :email, :name, :pay_type, :payment_type_id, :cart_id,
:product_id
has_many :line_items, :dependent => :destroy
belongs_to :payment_type
PAYMENT_TYPES = PaymentType.pluck(:pay_type)
validates :name, :address, :email, :pay_type, :presence => true
validates :pay_type, :inclusion => PAYMENT_TYPES
And then you've got the other side of that belongs_to in payment_type.rb
class PaymentType < ActiveRecord::Base
attr_accessible :pay_type
has_many :orders
validates ***:pay_type,*** :uniqueness
end
I know that I am totally just confusing things but I have one fail in the functionals tests and one error that has something to do with updating an order but I don't know what yet. I am going to work on them to see if by solving them I inadvertently solve this weird error.
If anyone can give me tips on hacking and debugging in rails that would be great. I would love to be able to solve this without typing all of this in here.
I don't think the server trace gives any more information than the browser window in this case but if you need it, or anything else please ask.
UPDATE:
So my problem is that I know how to solve it with a global variable in payment_type.rb, but this means that I have one column of payment types in the Orders table and another of names and payment_type_ids in another column, which is the foreign key.
Since I have the foreign key I shouldn't need a specific column for payment_types in the Orders table. I should just be able to see the value from the PaymentType table in the Orders view.
How do you do this without a Global variable?
UPDATE deux:
Ok, so I never posted this before (from orders_form.html.erb):
26: <div class="field">
27: <%= f.label :pay_type %><br />
28: <%= f.select :pay_type, PaymentType::PAYMENT_TYPES,
29: :prompt => 'Select a payment method' %>
30: </div>
31: <div class="actions">
So I've tried to select for :pay_type in Orders but given options from :pay_type in PaymentTypes.
I can't imagine that matters does it? Seems to be where my problem lies, but can't be sure.
I think the syntax of your validate inclusion of is wrong. It should be something like:
validates :pay_type, :inclusion => { :in => PAYMENT_TYPES }