I am completely new to rails (actually this is my day 1 of rails). I am trying to develop a backend for my iOS app. Here is my create user method.
def create
user = User.find_by_email(params[:user][:email])
if user
render :json => {:success => 'false', :message => 'Email already exists'}
else
user = User.new(user_params)
if user.save
render :json => {:success => 'true', :message => 'Account has been created'}
else
render :json => {:success => 'false', :message => 'Error creating account'}
end
end
end
How can I make it better?
You could use HTTP status code, but it might be overkill if your API is not going to be used by anything but your iOS app.
The way I would do it is to put the validation on the model's side and let ActiveModel populate the errors. Status codes are also super useful.
class User < ApplicationModel
validate_uniqueness_of :email
# Other useful code
end
class UsersController < ApplicationController
def create
#user = User.new(params.require(:user).permit(:email)) # `require` and `permit` is highly recommended to treat params
if #user.save # `User#save` will use the validation set in the model. It will return a boolean and if there are errors, the `errors` attributes will be populated
render json: #user, status: :ok # It's good practice to return the created object
else
render json: #user.errors, status: :unprocessable_entity # you'll have your validation errors as an array
end
end
end
Related
I am working on a legacy Rails 2 project,
In my model class, I have a validation:
class Student < ActiveRecord::Base
validates_uniqueness_of :foo, :scope => [:type, :gender], :message => "Already have such student"
...
end
It checks for uniqueness of field foo based on type and gender attributes, if a student with these attributes already exist while creating a new student, an error message is raise.
My question is, with this validation, instead of having that error message, how can I call render :status => 422, :json=>"Already have such student" ? Is it possible
==== controller ====
class StudentsController < BaseController
def create
student = Student.new({...})
# Since there are other validations in Student class, it could be any reason student is nil here.
if student.nil?
render :status => :unprocessable_entity, :json => "Failed to create student."
else
render :status => :ok, :json=> student.to_json
end
end
end
Try with this code
class StudentsController < BaseController
def create
student = Student.new({...})
if student.save
render :status => :ok, :json=> student.to_json
else
render :status => :unprocessable_entity, :json => student.errors.full_messages
end
end
end
Actually student is never nil even if it is not valid. So your code will always render ok
I have the following #create method:
def create
begin
#order = #api_user.orders.create!(order_params)
render :json => #order, :only => [:id], :status => :created, :location => #order
rescue
render :json => {}, :status => :unprocessable_entity
end
end
However, I am using a generalistic approach for the rescue. If the order could not be created because one of the passed fields failed the validation, I would like to let the user know about that. So, if the creation of the order raised this:
ActiveRecord::RecordInvalid: Validation failed: Description1 is too long (maximum is 35 characters)
What is the proper way of catching and letting the API user know about it?
One thing you can do is make use of a light API library like rocketpants (https://github.com/Sutto/rocket_pants)
in which case, the method you want could be written like this:
def create
if #order = #api_user.orders.create!(order_params)
expose #order
else
error! :bad_request, :metadata => {:error_description => "#{#order.errors.full_messages}"}
end
end
This is assuming you have set the #api_user instance variable earlier somewhere. Also, the gem uses Active Model Serializers (https://github.com/rails-api/active_model_serializers) to serialize the #order into JSON, so you can always customize the output to your liking by creating a basic serializer, look at the github page for more info :)
Here is another way:
def create
#order = #api_user.orders.build(order_params)
if #order.save
render :json => #order,
:only => [:id], :status => :created, :location => #order
else
render :status => :unprocessable_entity,
:json => {:errors => #order.errors.full_messages}
end
end
You'll get back an array of errors in the JSON
I have been searching for a while and yet I am not able to find a satisfactory answer as yet. I have two apps. FrontApp and BackApp. FrontApp has an active-resource which mimics a model in BackApp. All the model level validations live in BackApp and I need to handle those BackApp validations in FrontApp.
I have following active-resource code:
class RemoteUser < ActiveResource::Base
self.site = SITE
self.format = :json
self.element_name = "user"
end
This mimics a model which is as follows
class User < ActiveRecord::Base
attr_accessor :username, :password
validates_presence_of :username
validates_presence_of :password
end
Whenever I create a new RemoteUser in front app; I call .save on it. for example:
user = RemoteSession.new(:username => "user", :password => "")
user.save
However, since the password is blank, I need to pass back the errors to FrontApp from BackApp. This is not happening. I just don't understand how to do that successfully. This must be a common integration scenario; but there doesn't seem to be a good documentation for it?
My restful controller that acts as a proxy is as follows:
class UsersController < ActionController::Base
def create
respond_to do |format|
format.json do
user = User.new(:username => params[:username], :password => params[:password])
if user.save
render :json => user
else
render :json => user.errors, :status => :unprocessable_entity
end
end
end
end
end
What is it that I am missing? Any help will be much appreciated.
Cheers
From rails source code I figured out that the reason ActiveResource didn't get errors was because I wasn't assigning errors to "errors" tag in json. It's undocumented but required. :)
So my code should have been:
render :json => {:errors => user.errors}, :status => :unprocessable_entity
In the code:
class UsersController < ActionController::Base
def create
respond_to do |format|
format.json do
user = User.new(:username => params[:username], :password => params[:password])
if user.save
render :json => user
else
render :json => user.errors, :status => :unprocessable_entity
end
end
end
end
end
try to replace
user = User.new(:username => params[:username], :password => params[:password])
with
user = User.new(params[:user])
Your active-resource model pass the params like the hash above:
:user => { :username => "xpto", :password => "yst" }
This solution worked for me: https://stackoverflow.com/a/10051362/311744
update action:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json {
render json: #user.errors, status: :unprocessable_entity }
end
end
end
Calling controller:
#remote_user = RemoteUser.find(params[:id])
if (#remote_user.update_attributes(params[:remote_user]))
redirect_to([:admin, #remote_user], notice: 'Remote user was successfully updated.')
else
flash[:error] = #remote_user.errors.full_messages
render action: 'edit'
end
Working from the railscast #160 base code I've set up a very simple site that allows me to log in, out and register an account. (Its almost identical except that I've removed the 'username' from the users migrate table and relevant views so only an email address is required)
I'm now trying to create a new log in action so that I can log in via JSON.
I'd like to be able to send a get request to http://app:3000/apilogin?email=my#email.com&password=p4ssw0rd and have the rails app store the IP address the request came from (if the log in was correct) and send a relevant response (in JSON).
So far I have added a section to controllers/user_sessions_controller.rb so that it goes:
class UserSessionsController < ApplicationController
#...
def new_api
respond_to do |format|
format.json
end
end
end
To routes.rb:
map.apilogin "apilogin", :controller => "user_sessions", :action => "new_api"
But I'm at a loss as to what to put in views/user_sessions/new_api.json.rb! Can you help?
You don't need to define a view at all - just return appropriate json from the controller.
def new_api
#user_session = UserSession.new({:email => params[:email], :password => params[:password]})
respond_to do |format|
if #user_session.save
format.json { render :json => {:success => true} }
else
format.json { render :json => {:success => false, :message => 'incorrect username or password'}, :status => :unauthorized }
end
end
end
You can do something like this:
def new_api
respond_to do |format|
format.json { render :json => user.slice(:name).to_json }
end
end
Or you can also generate the JSON in views/user_sessions/new_api.json.erb as you would write normal erb code. Not a good idea though:
{"name":"<%= #user.name %>"}
I have two tables:
Client(id,name,...)
Purchase(id,item,date,client_id,...)
They have their respective Model, with their validations. What I need is to create a new client with a new purchase, all into the create method of Client controller. Something like this:
def create
#client = Client.new(params[:client])
respond_to do |format|
if #client.save
# Add purchase
#sell = Purchase.new
#sell.client_id = #client.id
#sell.date = params[:date]
# Fill another fields
if #sell.save
# Do another stuff...
else
format.html { render :action => "new" }
format.xml { render :xml => #client.errors, :status => :unprocessable_entity }
end
flash[:notice] = 'You have a new client!'
format.html { redirect_to(:action => :show, :id => #evento.id) }
format.xml { render :xml => #client, :status => :created, :location => #client }
else
format.html { render :action => "new" }
format.xml { render :xml => #evento.client, :status => :unprocessable_entity }
end
end
end
In Purchase's model I have:
belongs_to :client
validates_format_of :date, :with => /^20[0-9]{2}[-][0-9]{2}[-][0-9]{2}$/, :message => 'not valid'
validates_presence_of :date
And there is my problem: how can I validate the date input, through validations into the model, from Client controller? And, how can I rollback the new client created when errors?
Yes, I can do the check as the very first instruction in the method, with a regular expression, but I think it's ugly. I feel like might exist a conventional method for doing this validation or even doing all the stuff in another way (i.e. calling create method for Purchase from Client controller).
Can you put me back in the right way?
Thank you in advance.
Take a look at the following page on working with associations.
Rails provides you with a bunch of handy methods on your objects.
Like the following:
Client.purchases.empty?
Client.purchases.size,
Client.purchases
Client.purchases<<(purchase)
Client.purchases.delete(purchase)
Client.purchases.find(purchases_id)
Client.purchases.find_all(conditions)
Client.purchases.build
Client.purchases.create
When using these methods, you're taking advantage of the validations on each of the models.
Hop into your Rails console and create a new client and try any of the above methods. You'll quickly learn how powerful they are and you'll be on your way in no time.
Edit: Here's a much better guide on Rails associations!
Depends a little on the situation, but you can use validates_associated to run the validations on associated objects. Then you can create the user (but don't save), create the purchase (but don't save) and try to save the user. If you've done it right the user will fail to save with a validation error on the associated object.