How to fix FriendlyID duplicate content for :id and :slug - friendly-id

FriendlyID is consistently showing duplicate content for both /slug and /1. In other words, the correct page is loading for the friendly slug (/new-york), but it's loading the same content for the old, unfriendly slug (/11).
Here's my current configuration:
#config/routes.rb
resources :groups, path: ''
get 'groups/:id' => redirect("/%{id}")
#app/models/group.rb
class Group < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
end
#app/controllers/groups_controller.rb
def show
#group = Group.friendly.find(params[:id])
end
As a potential workaround, I've found putting this in my controller does redirect the bad slugs (/11) to the good slugs (/new-york), but it feels wrong for many reasons (routing outside routes.rb, likely unintended consequences, complex solution for a common problem = probably not the right one).
if request.path != group_path(#group)
return redirect_to #group, :status => :moved_permanently
end
What is the right way to make FriendlyID either (1) redirect :id calls to :slug or (2) simply 404 them?

Thanks to this fantastic comment on Medium, I now have a fully functional and very elegant solution which solves my initial problem (duplicate pages with /new-york and /11) as well as allowing two root-level slug structures to coexist.
get '/:id', to: 'groups#show', constraints: proc {|req| FriendlyId::Slug.where(sluggable_type: 'Group').pluck(:slug).include?(req.params[:id])}, as: :group
get '/:id', to: 'custom_pages#show', constraints: proc {|req| FriendlyId::Slug.where(sluggable_type: 'CustomPage').pluck(:slug).include?(req.params[:id])}, as: :custom_page

Related

Figuring out rails routes, getting undefined method for my detail_path

My data of a datatable:
def data
theusers.map do |usermap|
[
h(usersmap.spriden_last_name),
h(usermap.spriden_first_name),
h(usermap.spriden_id),
link_to(usermap.gobtpac_username, detail_path(usermap.spriden_id))
]
end
end
the above code resides in app\datatables\helpdesk_datatable.rb
The above works mostly I know it is getting the data, the error I get is with the detail_path
Error on "undefined method" for detail_path... This means it is not building the router dynamically right, correct?
Or I am passing in the wrong thing I tried to pass in usermap.spriden.id and just banner user, same issue. I am really not sure how routes work apparently. I have a details_controller.rb in controllers that has a show method in it and i have the views/details/show.html.erb which will show the data that was passed into the route, at least I thought. But is it just an ID or an object? so if it just an id i have to look it up again in
the show method right? How do routes like this look? I am using devise and cancan too here is my routes file:
NmsuMyaccount::Application.routes.draw do
authenticated :user do
root :to => 'home#index'
match 'home', :to => 'home#index', :via => :get
end
#get 'show-details' => "details#show", as: 'show_details'
resources :details
devise_for :users
resources :users
# In order for an unauthorized user access this controller#action, this needs to be in a scope, but I don't know why.
devise_scope :user do
match 'home', :to => 'home#index', :via => :get
end
end
Also hitting the end point localhost:3000 is an error, I have to goto /home, although devise does work just fine. So thought I was close but for the life of me cannot get the detail_path to work, and I thought it was a plural issue so tried details, and just detail no path etc. No dice.
I don't believe that you have access to the route helpers that Rails provides inside your custom class. So you have to manually include the module inside your class. Something like:
link_to(usermap.gobtpac_username, Rails.application.routes.url_helpers.detail_path(usermap.spriden_id))
Or:
include Rails.application.routes.url_helpers
# Use it like you are using.
See here for more information about the subject:
Can Rails Routing Helpers (i.e. mymodel_path(model)) be Used in Models?

How can I serialize DataMapper::Validations::ValidationErrors to_json in Sinatra?

I'm developing a RESTful API using Sinatra and DataMapper. When my models fail validation, I want to return JSON to indicate what fields were in error. DataMapper adds an 'errors' attribute to my model of type DataMapper::Validations::ValidationErrors. I want to return a JSON representation of this attribute.
Here's a single file example (gotta love Ruby/Sinatra/DataMapper!):
require 'sinatra'
require 'data_mapper'
require 'json'
class Person
include DataMapper::Resource
property :id, Serial
property :first_name, String, :required => true
property :middle_name, String
property :last_name, String, :required => true
end
DataMapper.setup :default, 'sqlite::memory:'
DataMapper.auto_migrate!
get '/person' do
person = Person.new :first_name => 'Dave'
if person.save
person.to_json
else
# person.errors - what to do with this?
{ :errors => [:last_name => ['Last name must not be blank']] }.to_json
end
end
Sinatra::Application.run!
In my actual app, I'm handling a POST or PUT, but to make the problem easy to reproduce, I'm using GET so you can use curl http://example.com:4567/person or your browser.
So, what I have is person.errors and the JSON output I'm looking for is like what's produced by the hash:
{"errors":{"last_name":["Last name must not be blank"]}}
What do I have to do to get the DataMapper::Validations::ValidationErrors into the JSON format I want?
So, as I was typing this up, the answer came to me (of course!). I've burned several hours trying to figure this out, and I hope this will save others the pain and frustration I've experienced.
To get the JSON I'm looking for, I just had to create a hash like this:
{ :errors => person.errors.to_h }.to_json
So, now my Sinatra route looks like this:
get '/person' do
person = Person.new :first_name => 'Dave'
if person.save
person.to_json
else
{ :errors => person.errors.to_h }.to_json
end
end
Hope this helps others looking to solve this problem.
I know, I am answering this late, but, in case you are just looking for just validation error messages, you can use object.errors.full_messages.to_json. For example
person.errors.full_messages.to_json
will result in something like
"[\"Name must not be blank\",\"Code must not be blank\",
\"Code must be a number\",\"Jobtype must not be blank\"]"
This will rescue on client side from iterating over key value pair.

Rails current_path Helper?

I'm working on a Rails 3.2 application with the following routing conditions:
scope "(:locale)", locale: /de|en/ do
resources :categories, only: [:index, :show]
get "newest/index", as: :newest
end
I've a controller with the following:
class LocaleController < ApplicationController
def set
session[:locale_override] = params[:locale]
redirect_to params[:return_to]
end
end
I'm using this with something like this in the templates:
= link_to set_locale_path(locale: :de, return_to: current_path(locale: :de)) do
= image_tag 'de.png', style: 'vertical-align: middle'
= t('.languages.german')
I'm wondering why there doesn't exist a helper in Rails such as current_path, something which is able to infer what route we are currently using, and re-route to it include new options.
The problem I have is using something like redirect_to :back, one pushes the user back to /en/........ (or /de/...) which makes for a crappy experience.
Until now I was storing the locale in the session, but this won't work for Google, and other indexing services.
I'm sure if I invested enough time I could some up with something that was smart enough to detect which route matched, and swap out the locale part, but I feel like this would be a hack.
I'm open to all thoughts, but this SO question suggests just using sub(); unfortunately with such short and frequently occurring strings as locale short codes, probably isn't too wise.
If you are using the :locale scope, you can use url_for as current_path:
# Given your page is /en/category/newest with :locale set to 'en' by scope
url_for(:locale => :en) # => /en/category/newest
url_for(:locale => :de) # => /de/kategorie/neueste
In case somebody looks here, you can use request.fullpath which should give you all after domain name and therefore, will include locale.

How to have a gem controller handle multiple arbitrary models?

I have four models that I allow commenting on by four separate comment controllers. Those four comment controllers do essentially the same thing and vary only slightly.
In an attempt to remove duplication of the four commenting controllers which are essentially all the same, I've created a Rails Engine as a gem to arbitrarily handle commenting on any arbitrary model that I specify in the routes.rb.
So in my routes.rb file I can now use:
comments_on :articles, :by => :users
with comments_on implemented as follows in my gem:
def comments_on(*resources)
options = resources.extract_options!
[snip of some validation code]
topic_model = resources.first.to_s
user_model = options[:by].to_s
# Insert a nested route
Rails.application.routes.draw do
resources topic_model do
resources "comments"
end
end
end
The routes show up in 'rake routes' and requests correctly get routed to my gem's 'CommentsController' but that's where my gem's functionality ends.
What is the best way detect the context in my gem CommentsController so I can process requests specific to how comments_on was called?
More specifically, how would I implement an index action like the following, having it context aware?
def index
#article = Article.find(params[:article_id])
#comments = ArticleComment.find(:all, :conditions => { :article_id => #article.id })
end
Thanks for the help!
You could specify the topic as an extra parameter in your routes:
Rails.application.routes.draw do
resources topic_model do
resources "comments", :topic_model => topic_model.to_s
end
end
Then your controller could be written like this:
def index
#topic = topic
#comments = topic.comments
end
protected
def topic
m = params[:topic_model]
Kernel.const_get(m).find(params["#{m.underscore}_id"])
end
You could move a lot of the logic out of the controller and into the model as well. topic.comments could be a named scope that all of these models should implement.
I've done similar patterns in the past and there's usually an edge-case that breaks this idea down and you end up doing more 'meta' programming than is wise.
I'd recommend making a base controller, then making simplistic controllers that inherit from that, or try to split these common behaviors into modules.

Is there a way to check if a record was built by another model in active record?

When using accepts_nested_attributes_for, I got stuck when having a validation which required the original to be present. The code will help clear up that sentence.
class Foo < ActiveRecord::Base
has_one :bar
accepts_nested_attributes :bar
end
class Bar < ActiveRecord::Base
#property name: string
belongs_to :foo
validates_presence_of :foo #trouble line!
end
#now when you do
foo = Foo.create! :bar_attributes => {:name => 'steve'}
#you get an error because the bar validation failed
I would like to write a validation that goes something like...
class Bar < ActiveRecord::Base
validates_presence_of :foo, :unless => :being_built_by_foo?
end
I am currently using rails3.beta4
Thank you
Alas I don't have an answer to this post, but the I came up with another way so I didn't need the validation.
Since bar should never be without a foo then any request to create a bar without a foo_id is an error. In the real example a foo is a project, and bar is a bid. It is a nested resource, but I wanted to give access to json apps to be able to query the info from the /bids location so the router looked like.
resources :bids
resources :projects do
resources: bids
end
and then I just had to make sure all html access used project_bids_path or form_for [:project,#bid] etc. This next part is largely untested but so far the desired behavior is there. I got the idea from Yehuda's post on generic actions http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/
#I'm sure there is a better way then map.connect
map.connect "projects/invalid_id", :controller => "projects", :action => "invalid_id"
resources :projects
resources :bids
end
#couple of changes from Yehuda
def redirect(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
path = args.shift || block
path_proc = path.is_a?(Proc) ? path : proc {|params| path % params }
status = options[:status] || 301
lambda do |env|
req = Rack::Request.new(env)
#Get both the query paramaters and url paramaters
params = env["action_dispatch.request.path_parameters"].merge req.params
url = path_proc.call(params.stringify_keys)
#Doesn't add the port back in!
#url = req.scheme + '://' + req.host + params
#content-type might be a bad idea, need to look into what happens for different requests
[status, {'Location' => url, 'Content-Type' => env['HTTP_ACCEPT'].split(',').first}, ['Moved Permanently']]
end
end
def bid_path
redirect do |params|
if params['project_id']
"/projects/#{params['project_id']}/bids/#{params['id']}"
else
'/projects/invalid_id'
end
end
end
match "bids", :to => bid_path
match "bids/:id", :to => bid_path
however, after doing all of this I most definitely don't think it worth it. I think nested_attributes breaks things and can be improved if that validation doesn't work, but after looking through the code for a little while I'm not sure exactly how to fix it or if it's worth it.
first of all, when using nested_attributes, you'll get the presence of the container. in the example: when you save Foo and there's also a nested form for Bar, then Bar is built by Foo.
I think there's no need to make this kind of validation if you're sure to use Bar only in contexts with Foo.
btw, try to write validation as follow (new preferred syntax for Rails3):
validates :foo, :presence => true
hope this helps,
a.

Resources