I try to modify existing routes in rails application to make it more readable for human and for google.
Existing route example: http://localhost:3000/search_adv?locale=de&q[home_type_eq]=1
To: http://localhost:3000/bowling?locale=de
How to create these routes without 'big' code modifying?
Where home_type=1 parameter corresponds to bowling.
home_type=2 to restaurant and so on.
Altogether six such parameters.
In routes.rb: get 'search_adv' => 'pages#search_adv'
In controller:
def search_adv
if params[:search_adv].present? && params[:search_adv].strip != ""
session[:loc_search] = params[:search_adv]
end
if session[:loc_search] && session[:loc_search] != ""
#rooms_address = Room.where(active: true).paginate(page: params[:page], per_page: 10).near(session[:loc_search], 1000, order: 'distance')
else
#rooms_address = Room.where(active: true).paginate(page: params[:page], per_page: 10)
end
Your question shows how you are thinking about rails which is not the correct way and I would also suggest what Tom Lord suggested but there is a way to do what you want to do, although it would require major refactoring of your code base and not worth it:
You can add a M, V and C each for the home_types (restaurant, bowling etc.) and then redirect from search_adv method to that controller route based on params.
For example:
You hit http://localhost:3000/search_adv?locale=de&q[home_type_eq]=1 and then in search_adv you can
if params[the exact params containing your value] == 1
redirect_to bowlings_path(locale: 'de')
end
The user will not feel it as the redirection will happen on the back-end but the route later will look like:
http://localhost:3000/bowlings?locale=de
Related
I have a resource :posts, which I show one at a time in show.html.erb
Suppose I have ten posts, each with an :id going from 1-10. If I delete post #2, then my posts will be 1,3,4,5,6,7,8,9,10. If I create ten posts and delete them all, then the next post :id would be [1,3..10,21] but I would only have 11 posts.
I want to show the post number that's in the application and put it in the view against a total number of posts. So if you were looking at post #3, it might have an :id of 3, but it is post #2 in the database.
Here's what I tried so far:
posts_controller.rb
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.all.count.to_i
#posts_array = Post.pluck(:id).to_a
...
end
views/posts/show.html.erb
<%= #post.id %> of <%= #total_posts %> /
models/post.rb
def next
Post.where("id > ?", id).order(id: :asc).limit(1).first
end
def prev
Post.where("id < ?", id).order(id: :desc).limit(1).first
end
However, showing the :id of a resource is a security issue so I don't know how to do it better.
How can I make it so the show.html.erb view only shows the current index order of the total amount of resources as compared to the post_id?
An efficient way to do this could be
# app/controllers/posts_controller.rb
def show
#post = Post.friendly.find(params[:id])
#total_posts = Post.count
#post_index = Post.where("id <= ?", #post.id).count
end
# app/views/posts/show.html.erb
. . .
<%= #post_index %> of <%= #total_posts %>
. . .
You should avoid loading all posts (or even their id) if you can. This will become more and more expensive as the number of posts grows and will eventually become a bad bottleneck for performance.
If you're trying to find the 'array index' of a record (so to speak) you can do this:
Agency.order(id: :asc).offset(params[:index]).limit(1)
You don't really want to do any other way because then it will load EVERY record into rails which will be very slow. It's better to ask the database for only a single record (which is what 'offset' does). Just replace params[:index] with whatever the name of the params is, whether its params[:id], etc.
I did just want to address one thing you said:
However, showing the :id of a resource is a security issue so I don't know how to do it better
That's not a security issue. The app should be designed in a way where the ID of a resource is not special or "secret." If you have an ID of a record, your controller should work such that it "authorizes" certain actions and won't let you do something you're not supposed to (like a user deleting a post).
If you REALLY need to do this, then just hide the ID and use a slug instead, like example.com/this-is-a-post-slug. This can be done quite easily
Edit To answer your specific question...
ids = Agency.order(id: :asc).pluck(:id)
#post_index = ids.find_index(#post.id)
#next_post = ids[#post_index + 1]
#prev_post = ids[#post_index - 1]
You can now use #post_index in your view.
Note: #prev_post and #next_post will be nil when the page doesn't exist (i.e. the "next post" when you're on the last page), so you will need to check that.
Just try it:
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.count # this will return integer type data
#posts_array = Post.pluck(:id) # you don't need to_a as .pluck returns array
...
For the next part you could write:
def next
self.class.where("id > ?", id).limit(1).first # this use of id is secured.
end
def prev
self.class.where("id < ?", id).order(id: :desc).limit(1).first
end
Let's say I have a page which lists articles. The code in the controller used to be
# articles#index
#articles = Article.paginate(page: params[:page], per_page: 10, order: :title)
and my test was like
# spec/requests/article_pages_spec
Article.paginate(page: 1, per_page:10, order: :title).each do |a|
a.should have_selector('h3', text: a.title)
end
Ok fine. Now my code changes a bunch. The index is like
#articles = Article.find(:all, conditions: complicated_status_conditions)
.sort_by { |a| complicated_weighted_rating_stuff }
.select { |a| complicated_filters }
.paginate(...)
Or something. So what should my request spec now look like? I don't want to just copy and paste the application code into the test, but at the same time, the conditions and ordering are now fairly complex, so testing the existence and order of all the expected elements will definitely fail unless I emulate the index controller.
What's the best way to do this, avoid testing so specifically, copy in the application code? Refactor the query to some central place like a model and re-use it in the tests?
# articles#index
#articles = Article.paginate(page: params[:page], per_page: 10, order: :title)
The way we test this is not by writing Article.paginate(page: params[:page], per_page: 10, order: :title) again in the spec. The spec must test the result of your program code, not copying over your program code itself!
Long story short - you must just call articles#index controller, and afterwards just check the #articles variable. i.e.
# We usually call this as a controller spec
# spec/controllers/articles_controller
# But feel free to put it anywhere you want
describe ArticlesController do
it "should ..." do
get :index
# assigns[:articles] will give the #articles variable contents
assigns[:articles].each do |a|
response.should have_selector('h3', text: a.title)
end
end
end
This way, you directly test using the #articles variable itself, without having to do a second query (which both consumes unnecessary time, as well as results in copying over code).
If you want to test the actual query itself, then since your query is complicated, you should write a spec like the following:
it "should ..." do
# Create 10 articles in the database
# out of which only 5 are expected to match the expected output
article1 = Article.create! ...
...
article10 = Article.create! ...
get :index
# Test whether the articles are correctly filtered and ordered
assigns[:articles].should == [article5, article3, article7, article1, article4]
Edit: Footnote
Edit 2: Added extra example for testing the actual query
I've started creating a model based solution for creating short URLs, but I'm wondering if it wouldn't be better to do it in it's own collection (using mongoid) build an index for the tokens between models then search? Or if there's a gem that exists instead of rolling my own solution.
Right now i'm using Mongoid::Token which generates a unique token (ie cUaIxBu) for the particular collection and then using an additional letter (->(c)UaIxBu) to figure how which controller to route the particular request to.
Any ideas or pointers?
In this example alternatedoma.in/cUaIxBu would point to realdomain.com/cities/1234
routes
get '/:id' => 'home#tiny_url', :constraints => { :domain => 'alternatedoma.in' }
controller
def tiny_url
case params[:id].slice!(0)
when 'f'
#article = Article.find_by_token(params[:id])
redirect_to feature_url(#article)
when 'c'
#city = City.find_by_token(params[:id])
redirect_to city_url(#city)
when 'p'
#place = Place.find_by_token(params[:id])
redirect_to feature_url(#place)
end
end
We're employing an almost identical system in an application that I'm currently working on - and it seems to be working out okay (so far!). The only thing I could think of, is that you could boil down your LoC, as well as easily adding support for other models (if required) in the future:
supported_models = {:a => Article, :c => City, :p => Place}
prefix = params[:id].slice!(0).to_sym
if supported_models.has_key?(prefix)
#obj = supported_models[prefix].find_by_token(params[:id])
redirect_to send(:"#{supported_models[prefix].name.underscore}_url", #obj)
end
Obviously, this would require your routing helpers to follow the the same naming as your models. I.e: Article > article_url, City > city_url, etc.
In Rails3, is there a way to check if the page I'm rendering now was requested from the same application, without the use of the hardcoded domain name?
I currently have:
def back_link(car_id = '')
# Check if search exists
uri_obj = URI.parse(controller.request.env["HTTP_REFERER"]) if controller.request.env["HTTP_REFERER"].present?
if uri_obj.present? && ["my_domain.com", "localhost"].include?(uri_obj.host) && uri_obj.query.present? && uri_obj.query.include?('search')
link_to '◀ '.html_safe + t('back_to_search'), url_for(:back) + (car_id.present? ? '#' + car_id.to_s : ''), :class => 'button grey back'
end
end
But this doesn't check for the "www." in front of the domain and all other possible situations.
It would also be nice if I could find out the specific controller and action that were used in the previous page (the referrer).
I think you're looking at this the wrong way.
If you look around the web, find a site with a search feature, and follow the link you'll see a param showing what was searched for.
That's a good way to do it.
Doing it by HTTP_REFERER seems a bit fragile, and won't work, for example, from a bookmark, or posted link.
eg.
/cars/12?from_search=sports+cars
then you can just look up the params[:from_search]
If you really need to do it by HTTP_REFERER then you probably dont have to worry about subdomains. Just;
def http_referer_uri
request.env["HTTP_REFERER"] && URI.parse(request.env["HTTP_REFERER"])
end
def refered_from_our_site?
if uri = http_referer_uri
uri.host == request.host
end
end
def refered_from_a_search?
if refered_from_our_site?
http_referer_uri.try(:query)['search']
end
end
Try something like this:
ref = URI.parse(controller.request.env["HTTP_REFERER"])
if ref.host == ENV["HOSTNAME"]
# do something
To try and get the controller/action from the referring page:
ActionController::Routing::Routes.recognize_path(url.path)
#=> {:controller => "foo", :action => "bar"}
Create an internal_request? method utilizing request.referrer.
Compare the host and port of the request.referrer with your Application's host and port.
require 'uri' # Might be necesseary.
def internal_request?
return false if request.referrer.blank?
referrer = URI.parse( request.referrer )
application_host = Rails.application.config.action_mailer.default_url_options[ :host ]
application_port = Rails.application.config.action_mailer.default_url_options[ :port ]
return true if referrer.host == application_host && referrer.port == application_port
false
end
And then call it like this where you need it, most likely in application_controller.rb:
if internal_request?
do_something
end
Some caveats:
This might need to be modified if you're using subdomains. Easy, though.
This will require you to be setting your host and port for ActionMailer in your configuration, which is common.
You might want to make it the reverse, like external_request? since you're likely handling those situations uniquely. This would allow you to do something like this:
do_something_unique if external_request?
I am using search logic to filter results on company listing page. The user is able to specify any number of parameters using a variety of named URLs. For example:
/location/mexico
/sector/technology
/sector/financial/location/argentina
Results in the following respectively:
params[:location] == 'mexico'
params[:sector] == 'technology'
params[:sector] == 'financial' and params[:location] == 'argentina'
I am now trying to cleanup or 'DRY' my model code. Currently I have:
def self.search(params)
...
if params[:location]
results = results.location_permalink_equals params[:location] if results
results = Company.location_permalink_equals params[:location] unless results
end
if params[:sector]
results = results.location_permalink_equals params[:sector] if results
results = Company.location_permalink_equals params[:sector] unless results
end
...
end
I don't like repeating the searchs. Any suggestions? Thanks.
This is how I would write it:
[params[:location], params[:sector]].reject(&:nil?).each do |q|
results = (results ? results : Company).location_permalink_equals q
end
There's plenty of other ways, just an idea. Has the benefit of making it easy to add say params[:street] or something.
I don't think you can really DRY that up much when sticking to SearchLogic... I'd suggest to refine your routes to directly emit *_permalink as parameter names and do something like this:
Company.all :conditions => params.slice(:location_permalink, :sector_permalink)
or
Company.find :all, :conditions => params.slice(:location_permalink, :sector_permalink)
Documentation link: http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/Slice.html