How to use another attribute of a Rails resource in the URL? - ruby-on-rails

I am trying to follow http://railscasts.com/episodes/63-model-name-in-url to achieve URLs that look like:
/dog/<custom field of dog>
instead of
/dog/1
Where "1" is the internal primary key of the Dogs table. The custom field I want happens to be another integer in the Dogs field.
My code:
dogs_controller.rb:
load_and_authorize_resource :except => [:index]
def show
Rails.logger.info("Hello")
#dog = Dog.find_by_custom_field(params[:id])
end
dog.rb:
def to_param
custom_field
end
In particular, when I try to load /dogs/<custom_field>, it still insists on using that integer as the primary key lookup, instead of looking up on the custom field. So I get a Couldn't find Dog with id=<custom_field>. error
Interestingly, the logger line also never gets printed when I try to do this. However, when I remove the load_and_authorize_resource (CanCan) line, then it works. What is going on here?

for using a different attribute other than id pass :find_by option
load_and_authorize_resource :except => [:index] , :find_by => :custom # will use find_by_custom!(params[:id])
for more info read cancan manual for controller methods
http://rdoc.info/github/ryanb/cancan/master/CanCan/ControllerAdditions/ClassMethods

Related

Rails 4.1.2 - to_param escapes slashes (and breaks app)

I use in my app to_param to create custom URL (this custom path contains slashes):
class Machine < ActiveRecord::Base
def to_param
MachinePrettyPath.show_path(self, cut_model_text: true)
end
end
The thing is, that since Rails 4.1.2 behaviour changed and Rails doesn't allow to use slashes in the URL (when use custom URL), so it escapes slashes.
I had such routes:
Rails.application.routes.draw do
scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do
resources :machines, except: :destroy do
collection do
get :search
get 'search/:ad_type(/:machine_type(/:machine_subtype(/:brand)))', action: 'search', as: :pretty_search
get ':subcategory/:brand(/:model)/:id', action: 'show', as: :pretty
patch ':subcategory/:brand(/:model)/:id', action: 'update' # To be able to update machines with new rich paths.
end
end
end
end
I tried by recommendation in the thread to use glob param just for show method to make sure it works:
resources :machines, except: :destroy do
#...
end
scope format: false do
get '/machines/*id', to: "machines#show"
end
But it absolutely doesn't work. I still have such broken links:
http://localhost:3000/machines/tractor%2Fminitractor%2Fmodel1%2F405
Of course, if I replace escaped slashes on myself:
http://localhost:3000/machines/tractor/minitractor/model1/405
And try to visit path, then page'll be opened.
Any ideas how can I fix that?
I've been having the same problem when using the auto-generated url helpers. I used a debugger to trace the new behavior to its source (somewhere around ActionDispatch::Journey::Visitors::Formatter), but didn't find any promising solutions. It looks like the parameterized model is now strictly treated as a single slash-delimited segment of the path and escaped accordingly, with no options to tell the formatter otherwise.
As far as I can tell, the only way to get the url helper to produce the old result is to use your original routes file and pass each segment separately, something like:
pretty_machine_path(machine.subcategory, machine.brand, machine.model, machine.id)
This is ugly as hell and obviously not something you'll want to do over and over. You could add a method to MachinePrettyPath to generate the segments as an array and explode the result for the helper (say, pretty_machine_path(*MachinePrettyPath.show_path_segments(machine))) but that's still pretty verbose.
Between the above headaches and the "You're Doing it Wrong" attitude from the devs in that Rails ticket you linked to, the simplest option for me was to bite the bullet and write a custom URL helper instead of using to_param. I've yet to find a good example of the "right" way to do that, but something like this bare-bones example should serve the purpose:
#app/helpers/urls_helper.rb
module UrlsHelper
def machine_path(machine, options = {})
pretty_machine_path(*MachinePrettyPath.show_path_segments(machine), options)
end
end
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
helper :urls #for the views
include UrlsHelper #for controllers
#...
end
If you're sure the returned url is safe you should add .html_safe to the returned string:
MachinePrettyPath.show_path(self, cut_model_text: true).html_safe
(didn't see anywhere else where it can be escaped but check all the flow in your app, maybe manually testing method by by method)
How about defining the url yourself?
def to_param
"#{ subcategory.title.parameterize }/#{ brand.name.parameterize }/#{ model.name.parameterize) }/#{ id }"
end
And then in your routes something like this:
get 'machines/*id', to: "machines#show"
You also have to split your params[:id] when you do a find on your model.
Machine.find( params[:id].split("/").last )

howto find_by ownkey the exact entry in rails

we have an show-action which should find by the own_key the right entry. The own_key is generated as UUIDTools::UUID.timestamp_create().to_s.
The following question is now here.
class ListController < ApplicationController
respond_to :html, :xml, :json
def show
#list = List.find_by_own_key(params[:own_key])
respond_with(#list)
end
end
the routes are here so generates
resources :lists
match '/:id' => 'list#show'
why did we get also an entry back if we only type one simple letter after the /?
The own_key look so f6d47c20-a276-11e1-b127-68a3c454c2b4. So if we type an /lists/f i get the entry with an f own_key. how can we manage that we only get the entry with the own_key?
Could it run by an contraint?
thanks for the help if anone can help us?
Marcus
From your routes, params[:id] will contain the I'd to search for, however you're using params[:own_key] which will be nil. Instead of searching for the record with the specified value of own_key your code will always fetch the row with a null own_key.
Change your code to use params[:id] and you should be ok
U can use constrainsts for route or check params[:own_key] in controller
:constraints => {:own_key=> /regexp pattern for uuid/}

MongoMapper -- Float updated via form: how to map "" => nil (instead of 0.0)?

I have a rails3 project, using mongodb + MongoMapper. I have a model with a float value, which the user can set via a form. When the form is submitted, if no value is given for foo_val, the param is passed as the empty string, which winds up setting the attribute value to 0.0, which is not what I want. I want to differentiate between a user submitted value of "0", and user submitted "null" value (""), i.e. clearing the attribute.
How can I accomplish this?
class Foo
include MongoMapper::Document
key :foo_val, Float
end
I think your answer works - you could move it to the before_save action of the Document. Another thing you could try is validates_numericality_of :foo_val, :allow_nil => true
That might work, supposedly it casts empty strings to nil.
Ok, here is what I've figured out. There might well be a better way to do this:
I added a before filter in the Foo controller, to hook a function that checks my float params before create, update, edit, and save like so:
class FooController < ApplicationController
before_filter :nilify_float_params, :only => [:new, :create, :update, :edit]
...
protected
def nilify_float_params
if params[:foo]
params[:foo][:foo_value] = nil if params[:foo][:foo_value].empty?
end
end
end

Accessing a resource in routes.rb by using attributes other than Id

I have the following in my routes.rb
map.resources :novels do |novel|
novel.resources :chapters
end
With the above defined route, I can access the chapters by using xxxxx.com/novels/:id/chapters/:id.
But this is not what I want, the Chapter model has another field called number (which corresponds to chapter number). I want to access each chapter through an URL which is something like
xxxx.com/novels/:novel_id/chapters/:chapter_number. How can I accomplish this without explicitly defining a named route?
Right now I'm doing this by using the following named route defined ABOVE map.resources :novels
map.chapter_no 'novels/:novel_id/chapters/:chapter_no', :controller => 'chapters', :action => 'show'
Thanks.
:id can be almost anything you want. So, leave the routing config untouched and change your action from
class ChaptersControllers
def show
#chapter = Chapter.find(params[:id])
end
end
to (assuming the field you want to search for is called :chapter_no)
class ChaptersControllers
def show
#chapter = Chapter.find_by_chapter_no!(params[:id])
end
end
Also note:
I'm using the bang! finder version (find_by_chapter_no! instead of find_by_chapter_no) to simulate the default find behavior
The field you are searching should have a database index for better performances

Overriding Rails to_param?

How do I get the to_param method to deliver keyword slugs all the time? I have trouble getting it to work with this route:
map.pike '/auction/:auction_id/item/:id', :controller => 'items', :action => 'show'
Earlier the overridden to_param was working for
'items/1-cashmere-scarf'
but fails with 'auction/123/item/1'
Update:
I'm not sure if the syntax is correct[(edit: it's correct: it works :-)], or even efficient.... but using haml, I found that the following code works to generate the desired link ('auction/:auction_id/item/:id')
- for auction in #auctions.sort{|a, b| a.scheduled_start <=> b.scheduled_start}
-for item in #items
- unless auction.current_auction
... pike_path(auction.auction_id, item)
I'm not sure whether I understand your question. (it's 3:41 AM here)
From what I see, you directly access auction_id method, instead of using pike_path(auction, item) that'd use #to_param.
Also, it might fail for auction/123/item/1 because you haven't changed your controller.
I think it'd be helpful to describe how to get working slugs.
Broadly speaking, if you override #to_param, IDs no longer works. It means, that if you go with slugs, every time polymorpic URL is generated (eg, link_to object, object), it passes to_param's value. It is worth noting that you must change your controller as well.
Personally I think that the best way to generate slugs easily is to use techno-weenie's permalink_fu, adding has_permalink to your model, and then, override to_param. For example
class Auction < ActiveRecord::Base
has_permalink :title, :slug
end
assuming that you have slug, a string field, and want to slugize your title.
You also need to adjust your controller:
class AuctionsController < ApplicationController
def show
#auction = Auction.find_by_slug(params[:id]) || raise(ActiveRecord::RecordNotFound)
respond_to do |format|
format.html # show.html.erb
end
end
Then, you can generate routes, in the views, this way:
link_to #action, #action
By the way, you should NOT sort your actions in the view. The best way is to use named_scope.

Resources