Rails 4. Different routing for few models (relatd and not) - ruby-on-rails

I am now working on website SEO optimization and what I am required to do is proper routing for links to be very seo friendly. I have read lots of information about routing, but it messed up in my head and I stuck.
So I have Store model which belongs to StoreType model, to City model and District model + District belongs_to :city.
I need to have routes like this:
/stores/store_type_name/ - store_type 'show' action(list of stores by type)
/stores/city_name/store_type_name/ - store_type 'show' action(list of stores by city&type)
/stores/city_name/district_name/store_type_name/ - store_type 'show' action(list of stores by city&district&type)
/stores/city_name/store_type_name/store_name - store 'show' action
The only solution I came up with for now is:
Routes.rb
namespace :stores do
get ':transliterated', to: 'store_types#show'
get ':transliterated/:name_en', to: 'store_types#city'
get ':transliterated/:name_en/:id', to: 'store_types#district'
end
With controller like this:
def district
#store_type = StorerType.find_by_transliterated(params[:transliterated])
#city = City.find_by_name_en(params[:name_en])
#district = District.find_by_id(params[:id])
if #store_type && #city && #district
stores = #store_type.stores.where(city_id:#city.id)
#stores = stores.where(district_id:#district.id)
else
redirect_to root_path
end
end
That works well but 1) I can not now add route for last example(store show page) as route is looking for :transliterated params in that namespace and redirects if record is not found. 2) I understand that this solution is bad and can be done much better, I just do not know how. Give me an advice please.
PS. Actually there is routing implemented on the site already so I am looking for the solution for those 4 urls listed above only, without touching anything else there.

Resourceful
Firstly, let me define the basis of all your routing for you...
Rails' routing structure is known as being resourceful - meaning based around resources / objects. As with Ruby being an object-orientated language, Rails is an object-orientated framework; the routes are no exception to this:
This means anything you do with your routes has to be resource-based, as follows:
#config/routes.rb
namespace :stores do
resources :store_types, only: [:show], path: "" do #-> domain.com/stores/:id -> store_types#show
get :name_en, action: :city #-> domain.com/stores/:store_type_id/:name_en
get :name_en/:id, action: :district #-> domain.com/stores/:store_type_id/:name_en/:id
end
end
This will give you the ability to send the traffic directly to your store_types controller without having all sorts of crazy routes all over the place
--
friendly_id
Something else to consider is a gem called friendly_id
friendly_id basically allows you to define / call routes with slugs, rather than ids. The difference is that the routes remain the same - it's the data, and the handling of that data, which changes
Typically in Rails, you'll create routes like this: domain.com/controller/:id
When you send people to links, they'll hit domain.com/controller/1 for example. Friendly_ID basically facilities the ability to send people to domain.com/controller/your_name, handling it in exactly the same way as you would with an ID:
#app/models/your_model.rb
Class YourModel < ActiveRecord::Base
friendly_id :name, use: [:slugged, :finders]
end
This will allow you to call:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def show
#model = Model.find params[:id]
end
end

You can use some static strings in the urls to help to identify the actions, for examples:
namespace :stores do
get 'type/:transliterated', to: 'store_types#show'
get 'type/:transliterated/city/:name_en', to: 'store_types#city'
get ':transliterated/:name_en/:id', to: 'store_types#district'
end

Related

Rails getting routes name in Route.rb with controller actions

I'm a real beginner of rails.
Can I get multiple routes from one controller + many actions?
For example,
resources :something
get "something#index", "something#show", "something#update"...etc.
I'm just curious if there is a command to get route name from the actions.
For example, in a controller named "pledges",
class PledgesController < ApplicationController
def home
end
def abc
end
def defg
end
def hijk
end
end
Can any commands get "pledges#home", "pledges#abc", "pledges#defg","pledges#hijk" ?
To add custom, "non-RESTful" routes to a resource, you could do the following:
resources :pledges do
collection do
get :foo
end
member do
put :bar
end
end
collection-defined routes will produce results against Pledge as a whole – think the index route.
member-defined routes will produce results against an instance of Pledge – think the show route.
This would produce the following routes for you:
foo_pledges GET /pledges/foo(.:format pledges#foo
bar_pledge PUT /pledges/:id/bar(.:format) pledges#bar
pledges GET /pledges(.:format) pledges#index
POST /pledges(.:format) pledges#create
new_pledge GET /pledges/new(.:format) pledges#new
edit_pledge GET /pledges/:id/edit(.:format) pledges#edit
pledge GET /pledges/:id(.:format) pledges#show
PATCH /pledges/:id(.:format) pledges#update
PUT /pledges/:id(.:format) pledges#update
DELETE /pledges/:id(.:format) pledges#destroy
You will have to define all of the custom actions, if there are not restful (but I would highly recommend that you follow the rest conventions). For example:
get 'pledges' => 'abc'
post 'pledges' => 'defg'
put 'pledges' => 'hijk

Rails routing: Scope using a database field

I am creating an multitenant app based on ideas from Ryan Bigg's book "Multitenancy with Rails". In this book, the tenants has their own subdomain. This approach is not applicable in my case, so I'm trying to scope by a slug of the account's name instead.
So instead of URLs like http://account-name.myapp.com, i want http://myapp.mydomain.com/account-name/. The subdomain is reserved for the app itself, because I want to be able to have more than one app on my domain.
Here's a piece of my routes.rb:
scope module: 'accounts' do
resources :customers do
resources :notes
end
end
To achieve my goal, i try to follow the routing guide on rubyonrails.com (the last code snippet in chapter 4.5), and change the above code to:
scope ':slug', module: 'accounts' do
resources :customers do
resources :notes
end
end
slug is an attribute in the accounts table in the database, so if an account is called "My Business", the slug will typically be "my-business".
This change seems to correct my routes:
customers GET /:slug/customers(.:format)
.. but it also seems to break my site, as the slug is not fetched from the database. I can't seem to wrap my mind around how this scope':slug', module: 'accounts' works. Is Rails supposed to automatically recognize :slug as an attribute of the Accoounts table? If not, can anyone please help me find a way to use the account's slug in my URLs?
I have googled around for a couple of days now, and read numerous answers here on Stackoverflow. Nothing helped, so any pointers is greatly appreciated. :-)
EDIT:
The relevant controllers are set up like this:
controllers/accounts/base_controller.rb
controllers/accounts/customers_controller.rb
controllers/accounts/products_controlelr.rb
controllers/accounts_controller.rb
controllers/application_controller.rb
The accounts_controller.rb only has actions for new and create at this point.
The accounts/base_controller.rb look like this:
module Accounts
class BaseController < ApplicationController
before_action :authorize_user!
def current_account
#current_account ||= Account.find_by_slug(params[:slug])
end
...
end
end
I addded this to my Account model:
def to_param
slug
end
Before i tried to implement scope ':slug' in my routes, everyting worked when logged in users where directed to myapp.mydomain.com/dashboard and navigated to i.e. myapp.mydomain.com/customers. Now it works with myapp.mydomain.com/account-name/dashboard, but as soon as I try to navigate to a view that use helpers like new_customer_path, i get the error:
No route matches {:action=>"show", :controller=>"accounts/customers", :id=>nil, :slug=>#
I hope this makes my issue clearer. :-)
I am not sure whether your routes is set up correctly or not because you didn't post your controller source code, but basically how it works if very simple. If you are using the current routes you set up what you should do is create an account_customers_controller.rb file in controllers\account\folder, and it should look like this:
class Accounts::CustomersController < ActionController::Base
def show
#account = Account.find_by_slug(params[:slug])
...
end
end

Rails 4 - Routing & Namespaces?

Ok, so - What did I do wrong? I feel like I'm missing something very simple...
REQUESTS belongs to USER
USER has many REQUESTS
I'm logged in as current_user (id=3) and want to list my requests:
<%= link_to("My Requests", user_requests_path(current_user)) %>
That link goes to /users/3/requests, but it shows ALL requests, not just those belonging to me.... ???
routes.rb
resources :users do
resources :requests
end
rake routes:
user_requests GET /users/:user_id/requests(.:format) requests#index
This isn't related to your routes, then, it's a scoping problem on your ActiveRecord query. You probably have something like the following in RequestsController:
def index
#requests = Request.all
end
But what you need to have is something more like the following:
def index
#requests = current_user.requests
end
If your Request resource can be accessed independently of users (i.e. there's a use-case for Request.all or /requests) you should actually do a separate namespaced controller (e.g. Users::RequestsController) to handle user-specific requests. Your routes will then need to specify the namespace for the user-specific requests as well.

StackOverflow Style Routes with Smart Redirects

StackOverflow seems to have this style of routes for questions:
/questions/:id/*slug
Which is easy enough to achieve, both in routes and to_param.
However, StackOverflow seems to also redirect to that path when just an ID is passed.
Example:
stackoverflow.com/questions/6841333
redirects to:
stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/
Same goes for any variation of the slug
stackoverflow.com/questions/6841333/some-random-stuff
Will still redirect to the same URL.
My question is: Is this type of redirection typically handled in the controller (comparing the request to the route) or is there a way to do this in routes.rb?
The reason I wouldn't think this possible in the routes.rb file is that typically, you don't have access to the object (so you couldn't get the slug based off the ID, right?)
For anyone interested, Rails 3.2.13 and also using FriendlyID
Ok, so I think I've got this.
I was looking into doing something with middleware, but then decided that's probably not the place for this type of functionality (since we need to access ActiveRecord).
So I ended up building a service object, known as a PathCheck. The service looks like this:
class PathCheck
def initialize(model, request)
#model = model
#request = request
end
# Says if we are already where we need to be
# /:id/*slug
def at_proper_path?
#request.fullpath == proper_path
end
# Returns what the proper path is
def proper_path
Rails.application.routes.url_helpers.send(path_name, #model)
end
private
def path_name
return "edit_#{model_lowercase_name}_path" if #request.filtered_parameters["action"] == "edit"
"#{model_lowercase_name}_path"
end
def model_lowercase_name
#model.class.name.underscore
end
end
This is easy enough to implement into my controller:
def show
#post = Post.find params[:post_id] || params[:id]
check_path
end
private
def check_path
path_check = PathCheck.new #post, request
redirect_to path_check.proper_path if !path_check.at_proper_path?
end
My || in my find method is because in order to maintain resourceful routes, I did something like...
resources :posts do
get '*id' => 'posts#show'
end
Which will make a routes like: /posts/:post_id/*id on top of /posts/:id
This way, the numeric id is primarily used to look up the record, if available. This allows us to loosely match /posts/12345/not-the-right-slug to be redirected to /posts/12345/the-right-slug
The service is written in a universal fashion, so I can use it in any resourceful controller. I have't found a way to break it yet, but I'm open to correction.
Resources
Railscast #398: Service Objects by Ryan Bates
This Helpful Tweet by Jared Fine

Confused about routes setup -- Rails 3.1

I think I'm running across a conflict due to names:
Two models: store coupon
Url needed that will display coupons: http://localhost/coupons/:store_name ('coupons' is written in the url, not replaced with anything)
Controller name: coupons_controller
Here is what I have in my routes right now:
match '/coupons/:store_name' => 'coupons#index', :as => :stores
When I try to do redirect stores_path(store) in another controller, I get this error:
No route matches {:controller=>"coupons"}
Any clues? I'm new to rails so I bet it's a silly mistake.
UPDATE
Is there a central place to tell the dynamic _path() functions to use a specific url structure? i.e. Instead of having to do the following everywhere:
redirect_to stores_path(:store_name => store.store_name)
Instead using just:
redirect_to stores_path(store)
yes you can, redefine to_param in your model:
class Store < ...
def to_param
store_name
end
end
redirect_to stores_path(:store_name => store)
should work if it doesn't (cannot confirm right now), you should be able to do the (little hacky)
redirect_to stores_path+"?store_name=yourstorename"
Doing it the restful way, you should probably have something like this (in your routes):
resources :stores do
resources :coupons # this will give you e.g. /stores/:store_id/coupons for the coupons#index action
end
If you want to use the store name instead of the ID, just search SO for using "slug" or have a look here: getting a 'name' based URL in RESTful routes instead of an id based url or ID + Slug name in URL in Rails (like in StackOverflow)

Resources