Descriptive URL, different pages, but same controller ( domain.com/cars/wheels) - ruby-on-rails

Custom routes for the same controller. I have many semi-static pages in my app (actually stored in my database with a group and page name field), they are grouped by product and then subject, for example
Cars: tires, Wheels, Radio, Windshield
Homes: Doors, Windows, Roof
Products and Services: data services
I would prefer not to make a new controller for each group. However, I am trying to get different URL paths that are descriptive. For example:
domain.com/cars/tires_and_spokes
domain.com/cars/wheels
domain.com/homes/doors_and_knobs
domain.com/homes/windows
domain.com/products_and_services/data_service
currently, all I have is
domain.com/pages/cars_tires_and_spokes
etc.
but I prefer the former.
Routes:
pages_from_DB =[
{group:"cars", name:"tires and spokes"}
{group:"cars", name:"wheels"}
{group:"homes", name:"tires and spokes"}
{group:"homes", name:"windows"}
]
pages = %w[
cars_tires_and_spokes
cars_wheels
homes_doors_and_knobs
homes_windows
products_and_services_data_service
]
pages.each do |page|
get page, controller: "pages", action: page
end
Controller:
class PagesController < ApplicationController
pages_from_DB =[
{group:"cars", name:"tires and spokes"}
{group:"cars", name:"wheels"}
{group:"homes", name:"tires and spokes"}
{group:"homes", name:"windows"}
]
pages = %w[
cars_tires_and_spokes
cars_wheels
homes_doors_and_knobs
homes_windows
products_and_services_data_service
]
pages.each do |page|
define_method(page) do
end
end
end

Looks like you've missed the point of nested resources:
#config/routes.rb
resources :groups, path: "", only: [] do
resources :pages, path: "" #-> url.com/:group_id/:id
end
This will direct any user to the pages controller, to which they're able to pull both the Group and ID from their respective models:
#app/controllers/pages_controller.rb
class PagesController < ApplicationController
def show
#group = Group.find params[:group_id]
#page = #group.pages.find params[:id]
end
end
--
This should be accompanied by the following models:
#app/models/group.rb
class Group < ActiveRecord::Base
has_many :pages
end
#app/models/page.rb
class Page < ActiveRecord::Base
belongs_to :group
end
If you wanted to treat the routes with a slug (instead of id), you'll want to look at friendly_id:
#Gemfile
gem "friendly_id"
$ rails generate friendly_id
$ rails generate scaffold group title:string slug:string:uniq
$ rails generate scaffold page title:string slug:string:uniq
$ rake db:migrate
#app/models/group.rb
class Group < ActiveRecord::Base
has_many :pages
extend FriendlyId
friendly_id :title, use: [:slugged, :finders]
end
#app/models/page.rb
class Page < ActiveRecord::Base
belongs_to :group
extend FriendlyId
friendly_id :title, use: [:slugged, :finders]
end
This will allow you to use:
<%= link_to group_pages_path(#group, #page) %>
# -> url.com/group-name/page-title
Update
The above code was based on the idea that you would be able to put your pages into the database (as you should). If you don't want to do that, there is a wildcard route you may be able to use:
#config/routes.rb
get "*group/:page", to: "pages#show"
If the pages were "semi-static" (still don't know what that means), you'd then be able to render the various views as required:
#app/controllers/pages_controller.rb
class PagesController < ApplicationController
def show
group = params[:group]
render "#{params[:group]}/#{params[:page]"
end
end
The above would give you the following link:
url.com/path/to/your/group/this-is-the-page-id
Depending on your group / sub-group structure, it should give you the ability to call the various views. I don't agree with it but it's apparently what you wanted.
--
Custom Middleware
We also created custom middleware which has some of the functionality for this:
#config/routes.rb
get "*group/:page", to: PageDispatcher.new
#app/controllers/pages_controller.rb
class PagesController < ApplicationController
cattr_accessor :pages #-> PagesController.pages
##pages = %w[
cars_tires_and_spokes
cars_wheels
homes_doors_and_knobs
homes_windows
products_and_services_data_service
]
end
#lib/page_dispatcher.rb
class PageDispatcher
#Init
def initialize(router)
#router = router
end
#Env
def call(env)
group = env["action_dispatch.request.path_parameters"][:group]
page = env["action_dispatch.request.path_parameters"][:page]
if PagesController.pages.include? page
strategy(slug).call(#router, env)
else
raise ActiveRecord::RecordNotFound
end
end
##########################################
private
#Strategy
def strategy(url)
Render.new(url)
end
####################
#Render
class Render
def initialize(url)
#url = url
end
def call(router, env)
controller = PagesController
action = "show"
controller.action(action).call(env)
end
end
####################
end

Related

Shrine - Download Endpoint with Account-based authentication

I'm using Shrine with Rails, with the download_endpoint plugin. I have this working with route-based authentication via Devise, but I'd like to go one step further and add account-based authentication so that users can only access their own images.
Rails.application.routes.draw do
get "/img/*rest", to: "download#confirm_response"
end
This uses the URL /img/xxx – but I'm trying to use something like /img/account_id/xxx, so this is my updated routes.rb:
Rails.application.routes.draw do
get "/img/:account_id/*rest", to: "download#confirm_response"
end
Here is my updated Controller, I'm not sure exactly what to amend in my set_rack_response method in order to correctly pull in my account_id. I have the rack_response plugin loaded, is it possible to set this up?
class DownloadController < ApplicationController
def confirm_response
#account = Account.find(params[:account_id])
set_rack_response #account.to_rack_response
end
private
def set_rack_response((status, headers, body))
self.status = status
self.headers.merge!(headers)
self.response_body = body
end
end
#Janko EDIT:
I've updated my routes and controller, I have 2 models that have image files (photos and logos) that I would love to use download_endpoint for both.
I feel like I'm missing where to add :account_id to the download_url itself.
Would love for the URL to be /img/:account_id/xxx but what do you think would work best given this setup?
# Account.rb
class Account < ApplicationRecord
has_many :photos
has_many :logos
end
# Photo.rb
class Photo < ApplicationRecord
belongs_to :account
include PhotoUploader::Attachment(:photo_file)
end
# Logo.rb
class Logo < ApplicationRecord
belongs_to :account
include LogoUploader::Attachment(:logo_file)
end
# Updated download_controller.rb
class DownloadController < ApplicationController
def confirm_response
#account = Account.find(params[:account_id])
#photo = #account.photos.find(params[:photo_id])
set_rack_response #photo.to_rack_response
end
private
def set_rack_response((status, headers, body))
self.status = status
self.headers.merge!(headers)
self.response_body = body
end
end
# Updated config/routes.rb
Rails.application.routes.draw do
get "/img/:account_id/:photo_id/*rest", to: "download#confirm_response"
end
# Updated config/initializers/shrine.rb
Shrine.plugin :download_endpoint, prefix: "img", host: [:cloudfront]
Shrine.plugin :rack_response
# Updated views/photos/show.html.erb
<%= image_tag(#photo.photo_file.download_url) %>

Route not find - Rails

Rails 3.2
In my controllers/admin/accounts_receivables_contoller.rb, I have:
class Admin::AccountsReceivables < Admin::ApplicationController
def index
...
end
and in one of the views, I have:
= link_to admin_accounts_receivables_path
In my config/routes.rb, I have:
namespace :admin do
resources :accounts_receivables do
collection do
get 'admin_report'
get 'customer_report'
post 'process_invoices'
end
end
end
rake routes, produces:
admin_accounts_receivables GET admin/accounts_receivables(.:format) admin/accounts_receivables#index
However, when I click on the link, I get (in the browser, but no entry in the log file):
uninitialized constant Admin::AccountsReceivablesController
I do not have a corresponding AccountsReceivable model, as I don't need it.
Any ideas?
The class should be named AccountsReceivablesController and you should nest the class explicitly instead of using the scope resolution operator so that it has the correct module nesting:
module Admin
class AccountsReceivablesController < ApplicationController
def index
# ...
end
end
end
When you use the scope resolution operator class Admin::AccountsReceivablesController - the module nesting is resolved to the point of definition which is Main (the global scope) and not Admin. For example:
module Admin
FOO = "this is what we expected"
end
FOO = "but this is what we will actually get"
class Admin::AccountsReceivablesController < Admin::ApplicationController
def index
render plain: FOO
end
end
See The Ruby Style Guide - namespaces.
class Admin::AccountsReceivables < Admin::ApplicationController
should be...
class Admin::AccountsReceivablesController < Admin::ApplicationController

Generating generic paths for STI models in Rails 4

Suppose I have a Rails 4 application that manages Widget objects, and using Simple Table Inheritance I have specialisations Widget::Foo and Widget::Bar.
I would like to manage all my Widget objects through a single WidgetsController.
I have the following models:
class Widget < ActiveRecord::Base; end
class Widget::Foo < Widget
# Foo specific details...
end
class Widget::Bar < Widget
# Bar specific details...
end
And a simple controller:
class WidgetsController < ApplicationController
def index
#widgets = Widget.all
end
def show
#widget = Widget.find(params[:id])
end
end
My routes include
resources :widgets, only: [:index, :show}
In my index.html.haml I have something like:
- #widgets.each do |widget|
= link_to "View your widget!", [#widget]
Which is where everything goes wrong.
Between url_for and polymorphic_path Rails will attempt to find a widget_foo_path, rather than using the extant widget_path.
I would rather not add additional routes, or controllers, and I would prefer not to specify the url helper manually. Is there a way to tell Rails that Widget::Foo and Widget::Bar objects should be linked to using the widget_path helper?
I ended up resolving the issue by creating a mixin:
module GenericSTIRoutes
def initialize(klass, namespace = nil, name = nil)
super(klass, namespace, name)
superklass = klass
while superklass.superclass != ActiveRecord::Base
superklass = superklass.superclass
end
#param_key = _singularize(superklass.name)
#route_key = ActiveSupport::Inflector.pluralize(#param_key)
#singular_route_key = #param_key.dup
#route_key << "_index" if #plural == #singular
end
end
And then modifying Widget.model_name as follows:
def self.model_name
#_model_name ||= Class.new(ActiveModel::Name) do
include GenericSTIRoutes
end.new(self, nil)
end

Rails accepts_nested_attributes_for always creates the nested models, but does not update them

Given the following:
class WebsitesController < ApplicationController
# POST /websites/save
# POST /websites/save.json
def save
Website.exists?(name: params[:website][:name]) ? update : create
end
# POST /websites
# POST /websites.json
def create
#server = Server.find_or_create_by_name(params[:server_id])
#website = #server.websites.new(params[:website])
#etc... #website.save
end
# PUT /websites/1
# PUT /websites/1.json
def update
#website = Website.find_by_name(params[:website][:name])
#etc... #website.update_attributes
end
end
The client does not have any IDs of these models
The request that gets sent only has the names, but not the ids.
And the following models
class Website < ActiveRecord::Base
serialize :website_errors
attr_accessible :plugins_attributes
has_many :plugins
accepts_nested_attributes_for :plugins
end
class Plugin < ActiveRecord::Base
belongs_to :website
end
When I make a POST request to /websites/save.json, the Website gets updated correctly if it exists, but the Plugins that belong to it always get recreated causing duplicate content in the Database. Why does this happen? I redirect to the update action which calls update_attributes so how can it be that it does not update it? I take it that it's because no ID is given with the request.
Can I make the Controller listen to plugin_name instead of plugin_id?
Modify your controller to have this:
def update
#website = Website.find_by_name(params[:website][:name])
if #website.update(params)
redirect_to website_path(#website)
else
render :edit
end
end
Also, if you're using strong_parameters, you'll need this at the bottom of your controller:
params.require(:website).
permit(
:name,
...,
plugins_attributes: [
:name,
...,
]
)
end

How do I create a custom resource path in rails?

I have a users controller in my application, routed with:
map.resources :users
This has my user pages living at /users/1, and so forth.
I'd like my user pages to live at /users/blake, etc.
What's the right way to do this in rails, such that I can say link_to(#user) and the correct path is generated?
In model:
class User < ActiveRecord::Base
def to_param
login
end
end
In controller:
class UsersController < ApplicationController
def show
#user = User.find_by_login(params[:id])
#...
end
end
to_param in model is used by ActionPack to construct url for this object. And in controller you need to fetch your model by this field.

Resources