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 %>"}
Related
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 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
I have a subscriber#create method that is only used for ajax submits to it (the html form uses data-remote="true" to do the Ajax. The form does indeed submit and the data ends up in the db but the method throws an error saying that the template was not found.
How can I specify a function as being an Ajax handler in Rails? -- one that doesn't have to render a template, etc.
Here is what the method looks like:
class SubscribersController < ApplicationController
def create
Subscriber.create(:email => params[:email],
:ip_address => request.remote_ip,
:referring_page => request.referer ) unless Subscriber.find_by_email(params[:email])
end
end
You should handle the call in your respond_to properly.
...
respond_to do |format|
format.html
format.js { :nothing => true }
end
The thing it, you should probably return something. Even if it is an AJAX call, you should send something back to let the caller know that the creation was a success.
def create
#subscriber = Subscriber.new(#your params)
respond_to do |format|
if #subscriber.save
format.js { render :json => #subscriber, :status => :created, :location => #susbscriber }
else
format.js { render :json => #susbcriber.errors, :status => :unprocessable_entity }
end
end
end
Also, you shouldn't have to do the unless Subscriber.find_by_email(params[:email]) in your controller. You should just add validates_uniqueness_of :email to the Subscriber model.
you want something like render :layout => !request.xhr? in your controller, this will prevent the layout if the request is ajax
I'm using Rails 3 for this one. I've got a collections model, a user model and an intermediate subscription model. This way a user can subscribe to multiple collections, with a particular role. However, I don't want a user to be able to subscribe to the same collection twice.
So in my Subscription model I've got something like:
validate :subscription_duplicates
def subscription_duplicates
self.errors.add_to_base "This user is already subscribed" if Subscription.where(:user_id => self.user.id, :collection_id => self.collection.id)
end
However this seems ugly. Also, it breaks when I want to do something like the following in my collection controller:
def create
#collection = Collection.new(params[:collection])
#collection.subscriptions.build(:user => current_user, :role => Subscription::ROLES['owner'])
#collection.save
respond_with(#collection)
end
When I do the build the subscription does not have an id so I get a "Called id for nil" error.
Thanks for any guidance!
use validates_uniqueness_of
validates_uniqueness_of :user_id, :scope => :collection_id
First of all, your create action should always test if the object was saved, and if not then handle that (usually by re-rendering the new/edit page and showing the errors to the user).
A standard sort of create action would look like this (for a #post in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to #post }
format.xml { render :xml => #post, :status => :created, :location => #post }
format.js
else
format.html { render :action => :new } #or edit or wherever you got here from
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
format.js
end
end
end
Shingara's approach to avoiding duplicates should work fine for you.
I am working on an app which needs has both website and an api and I am implementing a common Devise authentication for that. To implement this requirement I am overriding Devise Registrations Controller to respond to both html and json. For json I want the controller to return the user data and I am able to implement this, but for html I want to use the original devise behavior of signing in and redirecting to root path.
The code for Registrations Controller is:
class RegistrationsController < Devise::RegistrationsController
def create
#user = User.create(user_params)
respond_to do |format|
format.html {
#user.save ? (super) : (render :new)
}
format.json {
#user.save ? (render :json => {:state => {:code => 0}, :data => #user }) :
(render :json => {:state => {:code => 1, :messages => #user.errors.full_messages} })
}
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
end
Using this controller I get a validation error Email has already been taken but when I check my logs it shows the user has been created.
Can anyone tell me what error I am doing? I just want my controller to fallback to original devise functionality in case of html request.
The problem was when you used HTML, you created the user twice by running both #user = User.create(user_params) and super. I moved that first line of code to JSON format, and HTML to only run super.
class RegistrationsController < Devise::RegistrationsController
def create
respond_to do |format|
format.html {
super
}
format.json {
#user = User.create(user_params)
#user.save ? (render :json => {:state => {:code => 0}, :data => #user }) :
(render :json => {:state => {:code => 1, :messages => #user.errors.full_messages} })
}
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
end
Here is the working version of the code (tested locally myself).
You controller should look like:
def create
#user = User.create(user_params)
respond_to do |format|
format.html {
#user.save ? (render #user) : (render 'layouts/application')
}
format.json {
#user.save ? (render :json => {:state => {:code => 0}, :data => #user }) :
(render :json => {:state => {:code => 1, :messages => #user.errors.full_messages} })
}
end
end
Add a _user partial app/views/users/_user.html.erb:
<p>
<%= #user.email %>
<%= #user.created_at %>
<%= #user.updated_at %>
</p>
So, the changes are minimal. Once you make these changes, your RegistrationsController#create action will work successfully for HTML format.