I am implementing event categories into my app, and now I have encountered the following problem. If the user inputs an existing URL (say, 'site/types/1') he will get to the desired category, however if he enters site/types/asdf (a non-existent URL) he will get an empty index page, and no error. I would like him to get a 404 or an error page, and I know that can be achieved with a bang in a controller, if using a 'find_by!' method (right?). However my query differs and I cant seem to figure out the principle.
How can I make this work with more complex queries that use scopes and lambdas? Thank you
I have the following code:
event controller
def index
if params[:type]
#events_future = Event.future_by_type(params[:type])
else
#events_future = Event.future_events
end
end
event model
scope :future_by_type, lambda { order(...).where(...) }
router
get 'types/:type', to: 'events#index', as: :type
resources :types, except: :show
You can just throw an 404 exception, Rails will redirect to 404.html automatically:
def index
if params[:type]
#events_future = Event.future_by_type(params[:type])
else
#events_future = Event.future_events
end
#complex query here
.......
!#events_future.empty? || raise ActionController::RoutingError.new('Not Found')
end
You could use type in a RESTful way, i.e.
resources :types, only: [:index, :show]
And within your TypesController:
def index
#events = Event.future_events
end
def show
#type = Type.find(params[:id]) # raises RecordNotFound if type doesn't exist
#events = Event.future_by_type(#type)
end
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'm trying to build a link shortener. The intended behavior is that on the first page (new) the user inserts his long link and presses a button, then he gets redirected to an another page called result, where a preset message will be waiting for him, along with both his short and long link.
I'm struggling with controllers, however, as no matter what I do something always comes wrong. Right now my controller looks like this:
class UrlsController < ApplicationController
def new
#short_url = Url.new
end
def create
#short_url = Url.new(url_params)
if #short_url.save
flash[:short_id] = #short_url.id
redirect_to "/urls/result"
else
render action: "new"
end
end
def show
Url.find(params[:id])
##short_url_yield =
redirect_to #short_url.url
end
def result
end
private
def url_params
params.require(:url).permit(:url)
end
end
And the routes.rb:
Rails.application.routes.draw do
resources :urls, :only => [:show, :new, :create, :result]
get 'urls/result' => 'urls#result'
root to: redirect('/urls/new')
end
When I submit the link, however, rails returns the following error:
Couldn't find Url with 'id'=result
Extracted source (around line #17):
def show
Url.find(params[:id])
##short_url_yield =
redirect_to #short_url.url
end
It seems I don't understand the logic behind it. What's going wrong? Isn't the show bit supposed to be a redirect that happens when I click the shortified link?
Rails routes have priority in the order they are defined. Since your SHOW route declaration is before get 'urls/result' => 'urls#result' the url gets matched as /urls/id=result.
Simply move your custom route above the resources block or use a collection block.
resources :urls, :only => [:show, :new, :create, :result] do
collection do
get 'result'
end
end
Using the collection and member blocks tells Rails to give priority to the routes inside over the normal CRUD actions such as show.
When I go to the characters controller, show action, all the normal params[:id] is as how it should be according to REST.
In the show view, I render a partial. In that partial, I have a link that goes to the vote_socionics action. This action is defined under a socionics_votes module, which gets included by the characters controller. (I have it set up this way because I have other controllers that also include this module).
My problem is that when I click on this link, and it goes to the set_votable private method within the socionics_votes_module.rb file, the params[:id] is no longer present. Using pry, I found that it actually turns into params[:character_id]
Questions:
1) Why does this happen (is it because it goes to a "different" controller, even if it's a module?)
2) How do I work around this? I would think that it would be more elegant to have it be params[:id], instead of having to do an if-else to account for both keys.
characters_controller.rb
class CharactersController < ApplicationController
include SocionicsVotesModule
def show
#character = Character.find(params[:id])
end
characters/show.html.haml
= render partial: 'votes/vote_socionics',
locals: { votable: #votable, votable_name: #votable_name, socionics: #socionics }
_vote_socionics.html.haml
= link_to content_tag(:div,"a"), send("#{votable_name}_vote_socionics_path", votable, vote_type: "#{s.type_two_im_raw}"),
id: "vote-#{s.type_two_im_raw}",
class: "#{current_user.voted_on?(votable) ? 'voted' : 'not-voted'}",
method: :post,
data: { id: "#{s.type_two_im_raw}" }
socionics_votes_module.rb
module SocionicsVotesController
extend ActiveSupport::Concern
included do
before_action :set_votable
end
private
def set_votable
votable_constant = controller_name.singularize.camelize.constantize
#votable = votable_constant.find(params[:id]) # This is where it fails, since there is no params[:id], and rather, params[:character_id]
end
def set_votable_name
#votable_name = controller_name.singularize.downcase
end
routes.rb
concern :socionics_votes do
post 'vote_socionics'
end
resources :characters, concerns: :socionics_votes
resources :celebrities, concerns: :socionics_votes
resources :users, concerns: :socionics_votes
The URL of the link in the partial when hovered over.
localhost..../characters/4-cc/vote_socionics?vote_type=neti
Something like .find(params[:id] || params[:"#{#votable_name}_id"]) didn't work, and seems silly.
You need to add the vote_socionics route as a member of the resource:
concern :socionics_votes do
member do
post 'vote_socionics'
end
end
This way the id parameter gets set correctly
In my rails app, I have a namespace in my route file,
namespace :account do
resources :activities
end
My controller is
class Account::ActivitiesController < Account::AccountController
before_action :find_activity, only: [:show, :edit]
def index
#activities = Activity.all
end
def show
end
private
def find_activity
#activity = Activity.find(params[:id])
end
def activity_params
params.require(:activity).permit(:name, :description)
end
end
In my index view I'n trying to access the show page by do this:
= #activities.each do |activity|
= link_to "show", account_activity_path(activity)
When I'm running rake route, I get this result:
account_activities GET /account/activities(.:format) account/activities#index
POST /account/activities(.:format) account/activities#create
new_account_activity GET /account/activities/new(.:format) account/activities#new
edit_account_activity GET /account/activities/:id/edit(.:format) account/activities#edit
account_activity GET /account/activities/:id(.:format) account/activities#show
PATCH /account/activities/:id(.:format) account/activities#update
PUT /account/activities/:id(.:format) account/activities#update
DELETE /account/activities/:id(.:format) account/activities#destroy
When I'm taping directly localhost:3000:/account/activities/1, I'm going to the right page, but when I click on the show link in my index view, I'm getting this error:
No route matches [GET] "/account/undefined"
I'm using rails 4, and everything worked fine until today. I don't see what's happen so if you have any ideas, could be great
Thanks a lot
For anyone else that stumbles upon this issue, hopefully this will help. Check your javascript events!
I had a shared JS file between two HTML pages. Two different people were working on each page and both people attached a CHANGE event handler to an element ID.
$("#site").change(function() {
var url = $(this).data('url');
window.location = url;
}
Both events were executing on each page, causing the UNDEFINED route.
Using Rails 3.2. I am implementing vanity url for user URL:
# routes.rb
resources :users
match 'u/:login' => 'users#show', :as => :main_user
# users_controller.rb
class UsersController < ApplicationController
def show
#user = User.where(:login => params[:login]).first
end
end
Usually if we use #user = User.find(params[:id]), it would return ActiveRecord::RecordNotFound, which then redirects to 500 or 404 (not sure which one would be redirected to, but that's not important).
But in the case above, it would just return #user = nil and continue rendering show action. How can I code it in a way it works the same like searching for id?
You can use .first! (Docs) to raise an ActiveRecord::RecordNotFound error in case no record could be found.
#user = User.where(:login => params[:login]).first!