Generating nested routes in a custom generator - ruby-on-rails

I'm building a generator in rails that generates a frontend and admin controller then adds the routes to the routes file. I can get the frontend working with this:
m.route_resources controller_file_name
but I can't figure out how to do the same for the nested admin route (admin/controller_file_name). Anyone know how to generate these routes?

Looking at the code for route_resources, it doesn't look like it will do anything beyond a bog-standard map.resources :foos.
Instead, let's write our own method to deal with this issue, based on the original
def route_namespaced_resources(namespace, *resources)
resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
sentinel = 'ActionController::Routing::Routes.draw do |map|'
logger.route "#{namespace}.resources #{resource_list}"
unless options[:pretend]
gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
"#{match}\n map.namespace(:#{namespace}) do |#{namespace}|\n #{namespace}.resources #{resource_list}\n end\n"
end
end
end
We can start this off as a local method in your generator, which you can now call with:
m.route_namespaced_resources :admin, controller_file_name

Related

How can I correct the routing error in Ruby on Rails app

I always get routing errors in this Ruby file on Rails to project. It shows me routing error as you can see in below screenshot.
All gems are installed properly it shows me the home page but do not show me the check file or show file page.
Here is my Routes.rb code:
Rails.application.routes.draw do
get 'home/submission3'
post 'home/checkFile'
get 'home/showFiles'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
Here is an image of the error shown:
Here is my controller code:
# C:\Users\attari\RubymineProjects\submission3\app\controllers\home_controller.rb
require 'docx'
require 'find'
class HomeController < ApplicationController
def submission3
end
def checkFile
word = params[:folder][:word]
path = params[:folder][:path]
#files = checkWord(word, path)
puts "FILES ARE #{#files}"
redirect_to home_showFiles_path(#files)
# puts "WORD IS #{word} PATH IS #{path}"
end
def save
#submissions = submission3.new
end
def checkWord(userInput, folderInput)
count = 0
if (Dir.exist?(folderInput))
docx_file_paths = []
filtered_files = []
Find.find(folderInput) do |path|
docx_file_paths << path if path =~ /.*\.docx$/
end
(0..docx_file_paths.length - 1).each { |i|
d = Docx::Document.open(docx_file_paths[i])
d.each_paragraph do |p|
string_ = p.to_s()
if (string_.include? userInput)
puts docx_file_paths[i]
filtered_files.append(docx_file_paths[i])
count = count + 1
puts count
else
puts "No word Matched"
end
end
puts count
}
return filtered_files
else
puts "No Directory exist"
end
end
def create
#submission3 = submission3.new(params[:word])
if #submission3.save
redirect_to new_submission_path
end
end
private
def book_params
params.require(:book).permit(:title)
end
end
How can I fix this routes.rb error?
If you look at the error message, you can see that the filename is being appended directly to the route name (/home/showFiles.C:%5CUsers...). You need to add an id to the route so that Rails knows to expect something there. Your route should look something like this:
get 'home/show_files/:file_name'
[Note that you need to define :file_name in the context where this will be accessed (your controller).]
Even better would be to use a model as the basis for your route. So, if you have (for example) a File model, you would use:
resources :files
and Rails will generate a whole pile of routes for you automatically (or you can limit the routes by doing something like resources :files, only: [:show, :index, :create, :delete]).
Basically, I think you need to read up more on how Rails, and routes specifically, are supposed to work. RailsGuides is probably a good place to start. If you work with Rails and its conventions, you'll find your life a lot easier! Rails prefers convention over configuration!
Also, on a side issue, you're not following Rails convention in naming your methods and variables. CamelCase is used in object names (like class HomeController) but not method names or variable names - Rails uses snake_case for these. Again, if you stick to Rails convention (show_files not showFiles, for example), you'll find things go a lot smoother. Following the Rails conventions means Rails will seamlessly stitch things together for you; fail to follow them and Rails will be a complete nightmare.
Based on the error, it looks like you are trying to access /submission3 route even though you have defined /home/submission3 route. Updating the route should fix your routing error.

Dynamically add route to beginning of rails routes

For a testing helper I am working on I need to inject a route into the beginning of the routes. To add it at the end is easy:
test_routes = Proc.new do
get "/#{route_root}/:id", to: "#{route_root}#test"
end
Rails.application.routes.eval_block(test_routes)
The problem is that often there are "catch all" routes at the end of the application routes list, so I need to inject this as the FIRST route.
I have been using the technique here: How to dynamically add routes in Rails 3.2
but its hacky, and after a gem update last night it broke (not sure why yet, but its breaking inside of the routes.clear!) so I am looking for a less hacky solution.
Why not just create your own module like this:
module DynamicRouter
def self.routes router
router.get "/#{route_root}/:id", to: "#{route_root}#test"
end
end
and in your routes.rb just call your function:
# in routes.rb
# first line
DynamicRouter::routes(self)
That's how we do with some of our custom gems. You can also monkey patch the router, but it's more hacky.

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 )

Rails route scope default based on current_user

I have a route something like:
scope ":department", department: /admin|english|math/, defaults: { department: 'admin' }
Is it possible to make the default department for this route to be based on the current_user.department.name?
If this is not possible, what is another way of solving my problem. The problem is that I want all links to default to the current scope unless otherwise noted. I'm currently doing the following in LOTS of places:
# links to /math/students
link_to 'students', students_path(department: current_user.department.name.downcase)
If I understand correctly, what you want is to be able to write:
link_to 'students', students_path
and have the department option automatically set based on the current user.
Here's a solution that's like some of the others that have been offered: define helpers for each route that requires a department. However, we can do this programmatically.
Here we go:
app/helpers/url_helper.rb
module UrlHelper
Rails.application.routes.routes.named_routes.values.
select{ |route| route.parts.include?(:department) }.each do |route|
define_method :"department_#{route.name}_path" do |*args|
opts = args.extract_options!
if args.size > 0
keys = route.parts - [:department]
opts.merge!(Hash[*keys.zip(args).flatten])
end
opts.reverse_merge!(department: current_user.department.name.downcase)
Rails.application.routes.url_helpers.send(:"#{route.name}_path", opts)
end
end
end
You now have helper methods like department_students_path for every route that has a :department path segment. These will work just like students_path -- you can pass in opts, you can even set the :department explicitly and it will override the default. And they stay up to date with changes to your routes.rb without you having to maintain it.
You might even be able to name them the same as the original helpers, i.e.,
define_method :"#{route.name}_path"
without having to prefix them with department_--I didn't do that because I'd rather avoid naming collisions like that. I'm not sure how that would work (which method would win the method lookup when calling it from a view template), but you might look into it.
You can of course repeat this block for the _url helper methods, so you'll have those in addition to the _path ones.
To make the helpers available on controllers as well as views, just include UrlHelper in your ApplicationController.
So I think this satisfies your criteria:
You can call a helper method for paths scoped to :department which will default to the current_user's department, so that you don't have to explicitly specify this every time.
The helpers are generated via metaprogramming based on the actually defined named routes that have a :department segment, so you don't have to maintain them.
Like the built-in rails url_helpers, these guys can take positional args for other path segments, like, department_student_path(#student). However, one limitation is that if you want to override the department, you need to do so in the final opts hash (department_student_path(#student, department: 'math')). Then again, in that case you could always just do student_path('math', #student), so I don't think it's much of a limitation.
Route contraints can accept a proc, so you could solve this problem with the following "hack":
concern :student_routes do
# all your routes where you want this functionality
end
# repeat this for all departments
scope ":department", constraints: lambda{|req| req.session[:user_id] && User.find(req.session[:user_id]).department.name.downcase == "english"}, defaults: { department: 'english' } do
concerns :student_routes
end
Route concerns is a feature of rails 4. If you don't use rails 4, can you get the feature with this gem: https://github.com/rails/routing_concerns.
You can also just copy all the routes for students to all of the scopes.
You could use
link_to 'students', polymorphic_path([current_user.department.name.downcase, :students])
This will call
math_students_path
So to work I suppose you should add in your routes
scope ':department', as: 'department' do
...
as key generates helpers of the form: department_something_path
As that line is too long you could extract that to a helper
# students path taking into account department
def students_path!(other_params = {}, user = current_user)
polymorphic_path([user.department.name.downcase, :students], other_params)
Then you will use in your views
link_to 'students', students_path!
What about using nested routes? You could implement this with:
resources :departments do
resources :students
end
If you want to use a friendly url (like "math" instead of an id) can you add the following to the department model:
# in models/department.rb
def to_param
name.downcase
end
Remember name have to be unique for each department. Ryan B have made a railscast about this. You could also use a gem like friendly id.
You can now add the following to your views:
# links to /math/students
link_to 'students', department_students_path(current_user.department)
If you use this often, then create a helper:
# helpers/student_helpers.rb
def students_path_for_current_user
department_students_path(current_user.department)
end
# in view
link_to 'students', students_path_for_current_user
I ended up specifying it in the ApplicationController with:
def default_url_options(options={})
{department: current_user.department.name}
end
The path helpers will fill have the :department parameter filled in automatically then...

How to implement "short" nested vanity urls in rails?

I understand how to create a vanity URL in Rails in order to translate
http://mysite.com/forum/1 into http://mysite.com/some-forum-name
But I'd like to take it a step further and get the following working (if it is possible at all):
Instead of:
http://mysite.com/forum/1/board/99/thread/321
I'd like in the first step to get to something like this: http://mysite.com/1/99/321
and ultimately have it like http://mysite.com/some-forum-name/some-board-name/this-is-the-thread-subject.
Is this possible?
To have this work "nicely" with the Rails URL helpers you have to override to_param in your model:
def to_param
permalink
end
Where permalink is generated by perhaps a before_save
before_save :set_permalink
def set_permalink
self.permalink = title.parameterize
end
The reason you create a permalink is because, eventually, maybe, potentially, you'll have a title that is not URL friendly. That is where parameterize comes in.
Now, as for finding those posts based on what permalink is you can either go the easy route or the hard route.
Easy route
Define to_param slightly differently:
def to_param
id.to_s + permalink
end
Continue using Forum.find(params[:id]) where params[:id] would be something such as 1-my-awesome-forum. Why does this still work? Well, Rails will call to_i on the argument passed to find, and calling to_i on that string will return simply 1.
Hard route
Leave to_param the same. Resort to using find_by_permalink in your controllers, using params[:id] which is passed in form the routes:
Model.find_by_permalink(params[:id])
Now for the fun part
Now you want to take the resource out of the URL. Well, it's a Sisyphean approach. Sure you could stop using the routing helpers Ruby on Rails provides such as map.resources and define them using map.connect but is it really worth that much gain? What "special super powers" does it grant you? None, I'm afraid.
But still if you wanted to do that, here's a great place to start from:
get ':forum_id/:board_id/:topic_id', :to => "topics#show", :as => "forum_board_topic"
Take a look at the Rails Routing from the Outside In guide.
maybe try something like
map.my_thread ':forum_id/:board_od/:thread_id.:format', :controller => 'threads', :action => 'show'
And then in your controller have
#forum = Forum.find(params[:forum_id])
#board = #forum.find(params[:board_id])
#thread = #board.find(params[:thread_id])
Notice that you can have that model_id be anything (the name in this case)
In your view, you can use
<%= link_to my_thread_path(#forum, #board, #thread) %>
I hope this helps

Resources