I am following this tutorial in YT:
https://www.youtube.com/watch?v=oyjzi837wME&list=PLWqjhA7WxVW6L7AWzQElmYfXV3NUv_Lbs&index=1
def update
airline = Airline.find_by(slug: params[:slug])
if airline.update(airline_params)
render json: AirlineSerializer.new(airline, options).serialized_json
else
render json: {error: "airline not "}, status: 422
end
end
But I am getting this error:
NoMethodError (undefined method `update' for nil:NilClass)
Any help would be greatly appreciated.
In controllers you almost always want to use find or find_by! so that an ActiveRecord::RecordNotFound exception is raised and you bail early instead of getting a nil error:
def update
airline = Airline.find_by!(slug: params[:slug])
if airline.update(airline_params)
render json: AirlineSerializer.new(airline, options).serialized_json
else
render json: { errors: airline.errors.full_messages }, status: 422
end
end
After all if the record can't even be found there is no reason to continue processing - which is something the tutorial author didn't consider or test for.
This exception is rescued by Rails by default which sends a 404 response but you can override it on a per controller basis by using rescue_from:
class AirlinesController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :not_found
# ...
private
def not_found
render json: { error: "Oh noes" }, status: :not_found
end
end
On a side note params[:slug] is a bit of an antipattern. Your routes should just stick with the conventional /airlines/:id. :id in this case just means a unique identifier for the resource and not the id column. This avoids the need to refactor if you want to look records up by id or slug.
There are also some other problems with the tutorial code:
Authentication is opt-in as evidenced by before_action :authenticate, only: %i[create update destroy]. Use a opt-out secure by default approach instead so that you don't inadvertantly leave security holes. You do this by adding before_action :authenticate in your ApiController (or whatever the base class is) and then using skip_before_action :authenticate, ... to opt out on endpoints that should not require authentication.
There is no validation of slugs and no guarentee of uniqueness for either the slug or name in the form of unique indexes. Just validating the name doesn't actually guarentee the uniqueness of the slug as the name can be changed but the slug is generated when the record is first created.
parameterize is a quite naive solution to the problem of slugging. For real world use cases you need a library like stringex. Or use FriendlyID if you want to avoid reinventing the wheel.
I have spotted my mistake in following the tutorial. The code in the route should be as below:
namespace :api do
namespace :v1 do
resources :airlines, param: :slug
resources :reviews, only: [:create, :destroy]
end
end
Instead I have put resources :airlines, params: :slug with an s in the param.
So I am not very sure why there is no warning about this error when I tried to run it.
Related
What is the best way to validate url params that are not in the model.
Specifically I have a route like below:
get 'delivery_windows/:date',
to: 'delivery_windows#index',
:constraints => { :date => /\d{4}-\d{2}-\d{2}/ },
as: :delivery_windows
I want to make sure that :date is a valid date and regex is not a solution. the date cant be in the past and not more than 3 months into the future.
Thank you in advance
Thanks to theunraveler and sadaf2605 for their responses.
I ended up doing to combination of their suggestions by using the before_action and raising a routing error in there.
in my controller I added:
class AngularApi::V1::DeliveryWindowsController < ApplicationController
before_action :validate_date_param, only: :index
def index
...
end
private
def validate_date_param
begin
Date.parse(params[:date])
rescue ArgumentError
render json: [{
param: :date,
message: "Incorrect Date Format: Date format should be yyyy-mm-dd."
}].to_json, status: :unprocessable_entity
return
end
end
end
While I'm not sure that I would handle this in the routing layer myself, you should be able to use Advanced Routing Constraints for this.
The idea is that constraints can accept an object that responds to matches?. If matches? returns true, then the constraint passes, otherwise, the constraint fails. A simple implementation of this would be the following:
In your config/routes.rb, including something like this:
require 'routing/date_param_constraint'
get 'delivery_windows/:date',
to: 'delivery_windows#index',
constraints: DateParamConstraint,
as: :delivery_windows
Then, somewhere in your application (perhaps in lib/routing/date_param_constraint.rb), define a class like the following:
module DateParamConstraint
def matches?(request)
# Check `request.parameters[:date]` to make sure
# it is valid here, return true or false.
end
end
Well you can filter your date at controller, and raise 404 not found when you get date that does not fulfil your requirement.
def show
date=params[:date].strftime("%Y-%m-%d').to_date
if date > 0.day.ago or date > 3.month.from_now
raise ActionController::RoutingError.new('Not Found')
end
end
I've read everything I can find, and I'm still stumped.
I'm trying to use a before_filter to catch users who are not logged in, and send them to an alternative index page. The idea is that users who are logged in will see a listing of their own articles when they hit index.html.erb, users who are not logged in will be redirect to a showall.html.erb page that lists the articles but does not let them read them (and hits them with some ads).
I added a route:
resources :articles do
get "showall"
resources :comments
end
'Rake routes' shows a route article_showall. I have a partial _showall.html.erb in the views/articles folder (it only contains the text 'showall is working!'). If I render showall in another view (<%= render "showall" %>), it works fine.
This is the applicable part of my controller:
class ArticlesController < ApplicationController
skip_before_action :authorize, only: [:index, :show, :showall]
before_filter :require_user, :only => [:index]
def require_user
unless User.find_by(id: session[:user_id])
render 'showall', :notice => "Please log in to read articles."
end
end
def index
#articles = current_user.articles
end
def showall
#articles = Article.all
end
When I run it (while not logged in), it get the following error:
Missing template articles/showall, application/showall with....etc
I'm stumped. Why can I render 'showall' in a view, but I get Missing Template error when I refer to it in my controller? Thank you in advance for any help!
David and user4703663 are right. Your problem is that you named the template to be a partial but are rendering it as a template. You could either remove the underscore from the file name and leave your code as it is, or leave the filename as it is and "render partial: 'showall'" from your index view.
edit: I should add that the missing template error should not instill dread in your heart. It's guiding you to your mistake, directly. Don't stress it and just try to remember what it meant next time. It's almost always a typo, or loading a partial but naming it as a template or vice versa, or forgetting a subfolder for a relative path or something like that. It's normal, and the interpreter spits those errors to help you, not to oppress you. 😄
Replace
render 'showall', :notice => "Please log in to read articles."
with
render :file => '/partial/showall', :notice => "Please log in to read articles."
Great, fixed that problem, and now it spits back
undefined method `each' for nil:NilClass
even though the 'showall' is nearly identical to 'index'.
SIGH
So I handle exceptions with an error controller to display dynamic content to my users in production. I have it in my route file to do:
# Errors
%w( 404 422 500 ).each do |code|
get code, :to => "errors#show", :code => code
end
The only problem is now that I'm routing on errors such as that I lose information in my controller when I want to notify Airbrake. How can I maintain the exception information and send it to Airbrake on a 500? Right now all I get is the env that was occurring at the time of the exception which is less helpful for debugging purposes.
class ErrorsController < ApplicationController
def show
notify_airbrake(env)
render status_code.to_s, :status => status_code
end
protected
def status_code
params[:code] || 500
end
end
Are you handling an error by redirecting to a URL like http://your.site/500? That will be just an HTTP request like any other, losing the exception context you're after. Instead, you probably want to be using ActionController's Rescue functionality. It would look like this:
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :render_error
private
def render_error(error)
notify_airbrake(error)
render text: 500, status: 500
end
end
You can add multiple rescue_from declarations to handle different kinds of error, like the ActiveRecord::RecordNotFound from the Rails guide's example.
I am trying to create a module which houses the standard crud functions. whether this can be done, is wise, stand practice, or not, i would like to find out for myself. So far I have created the standard get requests no problem. However I am trying to implement a create action and am encountering a 'stack level too deep error'.
class FlagsController < ApplicationController
include CrudConcern
before_action lambda { crud_index(Flag.all) }, only: :index
before_action lambda { crud_new(Flag.new) }, only: :new
before_action lambda { crud_create(Flag.new, flags_path) }, only: :create
def create
end
end
crud module
def crud_create(model, route)
variable = model(params)
if variable.save
flash[:notice] = "Saved!"
redirect_to route
else
flash[:error] = "Try again"
render :new
end
end
Why would this occur? Is there a way around it? There is a Gem called Crudify which offers this so i assume it can be done.
Thanks
It looks like you are passing in an instance of a model, rather than the model class your method expects. I think you mean the following instead:
# in controller
before_action lambda { crud_create(Flag, flags_path) }, only: :create
# in crud module
def crud_create(model, route)
variable = model.new(params) # change is here
if variable.save
flash[:notice] = "Saved!"
redirect_to route
else
flash[:error] = "Try again"
render :new
end
end
UPDATE
It may be also a redirect loop. flags_path may be hitting the same create method (as opposed to the index), which will continually hit the crud_create before_action, causing the stack level too deep error. Try changing the redirect to test :)
To avoid hitting create again, you may have to set the status to 303. From the APIDock entry for redirect_to:
If you are using XHR requests other than GET or POST and redirecting
after the request then some browsers will follow the redirect using
the original request method. This may lead to undesirable behavior
such as a double DELETE. To work around this you can return a 303 See
Other status code which will be followed using a GET request.
example: redirect_to route, status: 303
I want to PUT to rails and avoid getting a 204. I am using this pattern:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_with(some_object)
end
end
However, when I do a put to update, I get a 204 back. I realize this is completely valid etc, but I explicitly want the content back. I can override it to some extent like this:
def update
respond_with(some_object) do |format|
format.json{render json: some_object}
end
end
but this seems a bit too hands-on for rails. Is there any more idiomatic way of avoiding a 204 and requesting the full content to be sent back? This is Rails 3.2.
In summary: I want maximally idiomatic rails that avoids a 204.
I made a custom responder which always returns my JSON encoded resource even on PUT/POST.
I put this file in lib/responders/json_responder.rb. Your /lib dir should be autoloaded.
module Responders::JsonResponder
protected
# simply render the resource even on POST instead of redirecting for ajax
def api_behavior(error)
if post?
display resource, :status => :created
# render resource instead of 204 no content
elsif put?
display resource, :status => :ok
else
super
end
end
end
Now, explicitly modify the controller which requires this behavior, or place it in the application controller.
class ApplicationController < ActionController::Base
protect_from_forgery
responders :json
end
You should now get JSON encoded resources back on PUT.
As a less invasive alternative, you can pass a json: option to the respond_with method invocation inside your controller update action, like this:
def update
# ...
respond_with some_object, json: some_object
end
Granted it seems a bit unDRY having to repeat the object twice in the arguments, but it'll give you what you want, the json representation of the object in the response of a PUT request, and you don't need to use the render json: way, which won't give you the benefits of responders.
However, if you have a lot of controllers with this situation, then customizing the responders, as jpfuentes2 showed in the accepted anwser, is the way to go. But for a quick single case, this alternative may be easier.
Source: https://github.com/plataformatec/responders/pull/115#issuecomment-72517532
This behavior seems intentional to fall in line with the HTTP spec, and "ideally" you should be firing off an additional GET request to see the results. However, I agree in the real world I'd rather have it return the JSON.
#jpfuentes2's solution above should do the trick (it's very similar to the pull request below), but I'm hesitant to apply anything that's patching rails internals, as it could be a real pain to upgrade between major versions, especially if you don't have tests for it (and let's face it, developers often skimp on controller tests).
References
https://github.com/rails/rails/issues/9862
https://github.com/rails/rails/pull/9887
Just to clarify, you do not need the responders gem to do this... You can just do:
config/initializers/responder_with_put_content.rb
class ResponderWithPutContent < ActionController::Responder
def api_behavior(*args, &block)
if put?
display resource, :status => :ok
else
super
end
end
end
and then either (for all updates actions to be affected):
class ApplicationController < ActionController::Base
def self.responder
ResponderWithPutContent
end
end
or in your action:
def update
foo = Foo.find(params[:id])
foo.update_attributes(params[:foo])
respond_with foo, responder: ResponderWithPutContent
end
What's wrong with simply doing:
def update
some_object = SomeObject.update()
render json: some_object
end
Not a big fan of this behavior. To get around it, I had to avoid using the respond_with method:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_to do |format|
format.json { render(json: some_object, status: 200) }
end
end
end