We use Rails' caches_action method along with Redis to implement caching of common pages.
It's a very dynamic site, so pages with differing URL params must remain distinct in the cache (e.g. ?page=1 vs ?page=2). But as long as the URLs match, the content can be cached and served to multiple visitors.
Since a very high portion of our traffic comes from Adwords ads, many of our users were arriving at our pages with a gclid param in the URL, which rendered the caching useless. I worked around this by excluding the gclid from the cache path used to identify unique URLs:
caches_action :search,
:expires_in => 10.minutes,
:cache_path => Proc.new { |c| c.params.except('gclid') }
This worked great for a while, until I realized that our Analytics data was off because the HTML we were caching included internal links with the gclid param from previous visits.
So the solution to this is pretty straightforward- every time I generate a link I need to manually exclude gclid, like:
link_to 'some page', params.except('gclid')
My question is: is there any way to automatically exclude gclid from all generated links? It would be really handy to not have to do this manually throughout the site.
Here's what I came up with. Turns out that link_to ends up calling url_for anyway, so that seems the best place to hook it in.
module ActionDispatch
module Routing
module UrlFor
alias :original_url_for :url_for
def url_for(options = {})
options = options.except(:gclid) if options.kind_of? Hash
original_url_for options
end
end
end
end
This answer by #jdc helper me get there, as did #MurifoX's comment.
Related
I'm working on a project where users can upload videos through a simple form and additionally by FTP to a certain directory and then simply choose the file from the FTP directory instead of uploading it through the form.
I got the following, pretty standard setup for a videos_controller:
# routes.rb
resources :videos
# new.html.rb
form_for(#video) do |f|
...
end
The restful actions in the controller are all working and just standard behaviour. The upload works, that's not the problem. The problem is if I do the following:
# routes.rb
resources :videos do
member do
post :from_ftp
end
end
# new.html.rb
form_for(#video, :url => from_ftp_video_url) do |f|
...
end
I get the error: No route matches {:action=>"from_ftp", :controller=>"videos"}, because the generated route looks like this:
from_ftp_video POST /videos/:id/from_ftp(.:format) videos#from_ftp
which seems right, since it's a member route. But I don't need the :id part of the URL, since I'm creating a new Video object, not through a form but simply by using the file from the FTP directory... So it basically is another create action, that's why I would like to do it as a POST request...
So how do I tackle this the best way?
Although the selected answer is correct for Vapire's situation, it doesn't necessarily answer the title question. If you came here looking for how to get member actions without an ID because you don't need an ID, the answer is a little different.
Say you implemented authentication that sets current_user. You let users edit their own profile only. In that case users/:id/edit doesn't make sense because :id is dictated by the current_user method. In this case /users/edit makes more sense.
You can change your routes.rb file to create member actions without an id in the path.
...instead of this...
resources :user
...use this (note the plurality of resource)...
resource :user
The way to understand member and collection routes is this:
Member routes do something to an object that you have.
Collection routes do something to the set of all objects.
So when we consider what the create route would be, it's a collection route, because it's adding a new object to the collection!
So your from_ftp method should also be a collection route, because it's adding to the collection.
Also, you might want to consider if you can accommodate the FTP functionality within your existing create method - it might be neater.
NOTE: I have read Routing From the Inside Out AND the Engine Yard blog post on routing.
I'm building a fantasy sports league, I have a League model that supports the seven basic restful operations, and they all work fine.
I've added the following my routes.rb
resources :leagues do
member do
get :invite
post :sendem
end
Later in the file I have a resources :leagues for the basic actions
and when I rake routes I can see:
invite_league GET /leagues/:id/invite(.:format) {:action=>"invite", :controller=>"leagues"}
sendem_league POST /leagues/:id/sendem(.:format) {:action=>"sendem", :controller=>"leagues"}
which is what I would expect. I have two functions in the League controller: "invite" which creates the form for collecting email addresses, and "sendem" which invokes a mailer to actually send the invitations.
The get for /league/:id/invite does indeed produce the form.
The form tag I am using looks like:
<%= form_tag(sendem_league_path, :method => "post") do %>
and yet the HTML that is rendered looks like:
<form accept-charset="UTF-8" action="/leagues/1" class="edit_league" id="edit_league_1" method="post">
And hence on submit generates a PUT which is completely wrong. (It should post to the sendem function.) My change to the routes file appears above the generic resources :leagues line, so it should have a higher priority.
I'm sure there is something dead-simple that I missed but I'm out of ideas. (And hair).
You should not use form_tag for manipulating resources.
You should use form_for.
Check out form helper guide - section 2 "Dealing with Model Objects". It takes care of deducing whether to use POST or PUT for a model object. For example, if your model object is new, it will use post on "resources"'s URL. if it is already existing database entity, it will use PUT to that "resource"'s URL.
ARGH some form handling error code at the top (form for #league) created a second form on the page for editing.... (left out of code snippets above for brevity). Original code seems to work as expected with that other code commented out. Thanks to vladdruzh for convincing me I was on the right track and to Salil for making me think to read the rendered HTML top to bottom.
This is an incredibly newbish question, but I can't seem to find the answer.
I'm building an app that utilizes external APIs heavily, and I'm fairly new to Rails, so it's still a little rough to get around. I can't, for the life of me, figure out how to accept user input and execute a function in my app without writing to a model.
For example, I just want to let a user type in a Twitter username and have it display on the page. I know how to make a form to cache the search in a model, but I can't figure out how to just... make a function happen on a page. I've been breaking my brains on this for several days now.
Please help? :/
You don't need a model to use Rails, but if you don't need ActiveRecord at all, you might benefit from a lighter framework like Sinatra. That doesn't answer your question, but it's worth thinking about if you really have no database requirement for your application.
It sounds like you're just trying to access non-resourceful user input, which is accessible in the controller via the params hash. So, assuming you have set up a valid route for the form action, you use your controller to extract GET or POST parameters.
For example:
# You define a non-resourceful route in routes.rb for your form action.
get 'twitternames/show'
# Form action directs user to GET the following route after filling in the form.
http://example.com/twitternames/show?user=foo
# The controller action extracts the data.
def show
#user = params[:user]
# render the view unless you tell rails to do something else
end
# show.html.erb
<%= #user %>
Creating the right route is the key. Once you've defined a route that can break a URL into the proper segments, the rest will fall into place.
The owner of a site that I am working on has asked me to make the About Us page editable (by her, through a web interface). In fact, there are 5 pages in total that she wants to make editable - About Us, Terms of Service, and so on.
In the old implementation, when these pages were static view files, I had all the URLs coded into routes.rb
scope :controller => :home do
get :about
get :terms
# etc ...
end
Now that these different actions are fetching data from the DB (or wherever) it seems like the standard RESTful approach might be to make a Pages resource and consolidate all the views into a show action.
That doesn't feel quite right. Individual resources aren't usually hardwired into the site the way an About Us page is - the contents of the page might change, but the page itself isn't going anywhere, and there are links to it in the footer, in some of our emails, etc.
Specifically, factoring out the individual routes from the PagesController would raise the following problems:
I couldn't used named route helpers like about_path
The routes for permanent pages on the site would be stored in the database, which means that...
maintenance would probably be a headache, since that is not the normal place to keep routes.
So currently I think that the best approach is to leave these URLs coded into routes.rb, and have separate controller actions, each of which would fetch its own page from the DB.
Can anyone share some insight? How do you deal with data that's not totally static but still needs to be hard-wired into the site?
If you are going to allow markdown, I like the idea of a Pages controller and model. If your layout feels like all 5 pages should have a similar feel, then I'd go with one template that populates with the user generated content and appropriate navigation.
My choice would be to set the routes, make the views (including routing), and populate the views with the user generated markdown.
Without knowing more about your site, it's hard to say, but my preference is not to allow users to generate pages that reflect the site identity (About, terms, etc.) unless that's what they are paying for.
We have an app with a large number of non-RESTful, verb-oriented controllers. I remember that long ago, before Rails got the REST religion, you could get automatic routes and helpers for those. Is there any way to still do this? It's a pain to keep adding GETs every time we add an action, and our app (or perhaps our collective development brain) just doesn't lend itself to RESTfulness.
You can use the "magic route", I believe it's still in the routes file by default, but if you don't have it here it is:
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
# match ':controller(/:action(/:id(.:format)))'
You can still use a default route like this:
match ':controller(/:action(/:id))'
to match paths like
/monkey/play
/monkey/see/1
/monkey/hear/1
/monkey/eat/1