I have User and Campaign models. I nest campaigns under users and URLs look like this:
http://localhost:3000/user-slug/campaign-slug
Routes:
resources :users, :path => '' do
resources :campaigns, :path => ''
end
Campaign model:
class Campaign < ActiveRecord::Base
...
extend FriendlyId
friendly_id :title, use: :history
...
end
My User model doesn't use history.
Campaign controller (from friendly_id guide):
class CampaignsController < ApplicationController
before_filter :find_campaign
def show
#campaign = Campaign.friendly.find(params[:id])
end
private
def find_campaign
#campaign = Campaign.friendly.find(params[:id])
# If an old id or a numeric id was used to find the record, then
# the request path will not match the post_path, and we should do
# a 301 redirect that uses the current friendly id.
if request.path != campaign_path(#campaign)
return redirect_to #campaign, :status => :moved_permanently
end
end
end
When I visit an old slug to trigger redirection I get this error:
ActionController::UrlGenerationError in CampaignsController#show
No route matches {:action=>"show", :controller=>"campaigns", :format=>nil, :id=>nil, :user_id=>#<bunch of other stuff in here>} missing required keys: [:id]
Not sure how I should tweak the redirect method to make it work.
Managed to get it to work with this:
def find_campaign
#campaign = Campaign.friendly.find(params[:id])
#user = #campaign.user
# If an old id or a numeric id was used to find the record, then
# the request path will not match the campaign_path, and we should do
# a 301 redirect that uses the current friendly id.
request_slug = params[:id]
if request_slug != #campaign.slug
return redirect_to user_campaign_path(#user, #campaign), :status => :moved_permanently
end
end
Instead of comparing the request path I compared the request slug which doesn't have the part before the slash. And I needed to redirect to the correct route user_campaign_path.
Related
I have a rails application in which I'm trying to add a future to ban existing users. My react request is as follows:
handleUserBan(evt) {
evt.preventDefault();
let user_id = evt.target.dataset.userId;
API.put('admin/users/'+user_id, {user: {banned: true}}, function(res) {
this.loadUsers();
}.bind(this))
}
And my 'UsersController' inside admin namespace is:
before_action :enforce_admin!
def show
#user = User.find(ban_params[:id])
end
def update
#user = User.find(ban_params[:id])
prms = ban_params
if prms.include?(:banned)
#user.update_attributes!(prms)
#user.save!
return render :status=>200, :json => {success: true}
end
end
private
def ban_params
params.require(:user).permit(:banned)
end
But I'm getting an error:
ActiveRecord: Record not found
Couldn't find User with 'id'=
Even though a user exists with the selected id in my database. My request is structured as follows:
Request
Parameters:
{"user"=>{"banned"=>"true"},
"id"=>"7",
"format"=>"json"}
And here are my routes for admin namespace:
namespace :admin do
put 'ban_user', :to => 'users#ban_user'
resources :charges
resources :coaches
resources :events
resources :invoices
resources :reviews
resources :users
end
try just params[:id] instead of ban_params[:id]
method ban_params will return only value of banned from params. In this case params contains { id: 'user_id', action: "your action", controller: 'controller', ..., user: { banned: true } }
def ban_params
params.require(:user).permit(:banned)
end
This code is filtering out the id parameter, since it's only permitting the banned parameter:
def ban_params
params.require(:user).permit(:banned)
end
Something like this might work, although it loses the permit constraint:
params.permit(:id, :user => [:banned])
I'm trying to install the contact page on my Ruby on Rails app. It seems straight forward enough, but after installing the mailer gems, and creating my controller with:
$ rails generate controller contact_form new create
I navigate to my contact URL (/contact_form/new), and it says
"Unable to autoload constant ContactFormController, expected
/home/ubuntu/workspace/app/controllers/contact_form_controller.rb to
define it"
Routes and controller are as follows:
routes.rb
get 'contact_form/new'
get 'contact_form/create'
resources :contact_forms
contact_form_controller.rb
class ContactFormsController < ApplicationController
def new
#contact_form = ContactForm.new
end
def create
begin
#contact_form = ContactForm.new(params[:contact_form])
#contact_form.request = request
if #contact_form.deliver
flash.now[:notice] = 'Thank you for your message!'
else
render :new
end
rescue ScriptError
flash[:error] = 'Sorry, this message appears to be spam and was not delivered.'
end
end
end
contact_form.rb
class ContactForm < MailForm::Base
attribute :name, :validate => true
attribute :email, :validate => /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i
attribute :message
attribute :nickname, :captcha => true
# Declare the e-mail headers. It accepts anything the mail method
# in ActionMailer accepts.
def headers
{
:subject => "My Contact Form",
:to => "your_email#example.org",
:from => %("#{name}" <#{email}>)
}
end
end
Note that your class is named ContactFormsController and Rails is looking for ContactFormController. You need to pay careful attention to the pluralization in Rails.
Controllers are plural.
Models are singular.
Routes are plural in most cases.
The pluralization of classes must always match the file name.
So why is Rails looking for ContactFormController? Because your routes are not defined properly:
get 'contact_form/new'
get 'contact_form/create'
get 'contact_forms/new' is the proper route for a form to create a new resource. You don't create resources with GET so get rid of get 'contact_form/create'.
resources :contact_forms
Is actually all that you need.
So to fix this error you should:
rename contact_form_controller.rb -> contact_forms_controller.rb.
change your route definition.
request /contact_forms/new instead.
We're trying to set up rails routes with the parameters separated by more then just forward-slash symbols.
As an example:
someexample.com/SOME-ITEM-for-sale/SOME-PLACE
For the following path we'd like to extract SOME-ITEM and SOME-PLACE strings as parameters whilst identifying which controller to run it all against with the "-for-sale/" part.
I've been playing with variations on :constraints => {:item => /[^\/]+/} constructs but without any success. Am I looking in the right place? Thanks!
UPDATE
In the end I went with this solution:
get ':type/*place' => 'places#index', as: :place , :constraints => {:type => /[^\/]+-for-sale/}
And then recovered the full "SOME-ITEM-for-sale" sting for parsing in the controller using
params[:type]
Hope that helps someone!
friendly_id is what you want:
#Gemfile
gem 'friendly_id', '~> 5.1.0'
$ rails generate friendly_id
$ rails generate scaffold item name:string slug:string:uniq
$ rake db:migrate
#app/models/item.rb
class Item < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
end
The above will give you a slug column, which FriendlyId will look up any requests you send to the app:
#config/routes.rb
resources :items, path: "" do
resources :places, path: "" #-> url.com/:item_id/:id
end
Although the params will still be id (unless you use the param option of resources, but FriendlyId will override both your routes and model to use the slug instead:
<%= link_to "Item Place", items_place_path(#item, #place) %> #-> url.com/item-name-information/place-name-information
Update
If you wanted to have a "dynamic" routing structure, you'll be able to use the following (this requires the history module of FriendlyId):
#config/routes.rb
#...
get '/:item_id/:place_id', to: SlugDispatcher.new(self), as: :item #-> this has to go at the bottom
#lib/slug_dispatcher.rb
class SlugDispatcher
#http://blog.arkency.com/2014/01/short-urls-for-every-route-in-your-rails-app/
##########################################
#Init
def initialize(router)
#router = router
end
#Env
def call(env)
id = env["action_dispatch.request.path_parameters"][:item_id]
slug = Slug.find_by slug: id
if slug
strategy(slug).call(#router, env)
else
raise ActiveRecord::RecordNotFound
end
end
##########################################
private
#Strategy
def strategy(url)
Render.new(url)
end
####################
#Render
class Render
def initialize(url)
#url = url
end
def call(router, env)
item = #url.sluggable_type.constantize.find #url.sluggable_id
controller = (#url.sluggable_type.downcase.pluralize + "_controller").classify.constantize
action = "show"
controller.action(action).call(env)
end
end
####################
end
This won't work out the box (we haven't adapted it for nested routes yet), but will provide you the ability to route to the appropriate controllers.
In the end we went with this solution:
get ':type/*place' => 'places#index', as: :place , :constraints => {:type => /[^\/]+-for-sale/}
The router command only gets activated if the :type parameter contains "-for-sale" in the string
And then we recovered the full "SOME-ITEM-for-sale" sting for parsing in the controller using
params[:type]
Hope that helps someone!
First I am sorry for my English
I am using Friendly_id gem to create Clean URL and it work just fine but instead of having a URL like this http://localhost:3000/profile/jack-sparo I want a URL like this http://localhost:3000/profile/1/jack-sparowhere 1 is the user_id, so how can I do it?
this is my config/routes
get "profiles/show"
get '/profile/:id' => 'profiles#show', :as => :profile
get 'profiles' => 'profiles#index'
and this is my Profile controller
def show
#user= User.find_by_slug(params[:id])
if #user
#posts= Post.all
render action: :show
else
render file: 'public/404', status: 404, formats: [:html]
end
end
If you have an id of the record in URL anyway, you don't need
Friendly_id gem. You need to tune routes.
But maybe you would be happy with something like this instead?
http://localhost:3000/profiles/1-john-smith
If so, you need to override to_param method in User model like
this:
class User < ActiveRecord::Base
def to_param
"#{id}-#{name}".parameterize
end
end
Now the profile_path(profile) helper will generate URL like
http://localhost:3000/profiles/1-john-smith
And, with this request, the User.find(params[:id]) in controller
will find profile with id 1 and cut all other stuff which was in URL.
So
http://localhost:3000/profiles/1-mojombo-smith
will link to the same profile as
http://localhost:3000/profiles/1-john-smith
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!