Rails creates routes from objects - ruby-on-rails

I have a routes file that looks something like this (It's very deeply nested...I know):
scope :admin, module: :admin do
namespace :breadth do
resources :areas, as: 'areas' do
resources :sequences, as: 'sequences'
end
end
end
When running rake routes I get a back all of the routes including one called breatdh_area_sequences (which is exactly what I want). The problem is that when I create a form rails builds up the wrong url based on the parameters I'm giving it:
= form_for [:breadth, breadth_sequence.area, breadth_sequence] do |f|
...
This gives me:
undefined method `breadth_breadth_area_breadth_sequences_path' for #<#<Class:0x007fe55bcf16b8>:0x007fe55d9a3f68>
Exactly how does rails take this array of parameters and create a URL path out of it? I'm assuming it calls a method on each object? Is this something I can override in order to get the named route I'm expecting (not overriding :url on form_for)

Since you want to name your objects differently from the routes, it won't work. Your routes/controllers need to be named just as your models are named, if your model is BreadthArea (as the route believes it is) your route should also be named in the same way.

In order to make this work I had to override the self.model_name methods on both BreadthArea and BreadthSequence. This method is how form_for figures out the URL when an object (or an array of objects) is used at the first argument.
class BreadthArea < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, "Area")
end
...
end
class BreadthSequence < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, "Sequence")
end
...
end

Related

Ruby on rails No method error.

I'm running into the infamous No Method Error. I've worked my way through a number of examples here on STOF but I can't see an error in my code that stands out. I've checked that rake routes matches what I think should be happening and the paths provided from using resources in the routes.db file seem to be correct. I know I'm missing some small detail but I can't for the life of me see it now. Any help would be appreciated.
My Controller code:
class GenevarecordsController < ApplicationController
def index
#genevarecords = GenevaRecord.all.page(params[:page]).per(5)
end
def new
#genevarecord = GenevaRecord.new
end
end
My routes:
Rails.application.routes.draw do
root 'genevarecords#index'
resources :genevarecords
end
You have a naming discrepency between your model and your controller / routes.
your model is GenevaRecord, underscored makes it geneva_record. However your controller only has a single capital letter at the beginning: Geneverecords which underscored would be genevarecords. Therefore when you pass your model to the form it tries to use a controller / routes helpers with the same naming format as the model, which would be geneva_records_controller ie. GenevaRecordsController.
What you need to do is match your controller and routes to the same naming format as your model:
class GenevaRecordsController < ApplicationController
#...
end
Rails.application.routes.draw do
#...
resources :geneva_records
end
You need to take Did you mean? section seriously,
Anyway, if you closely look at ruby syntax following is the representation for the class name,
AbcDef and equivalent snake case is abc_def
In your case,
Your model is named as GenevaRecord but your controller is GenevarecordsController
change it to GenevaRecordsController, also you need to match it's equivalent snake case in routes...
Rails.application.routes.draw do
root 'geneva_records#index'
resources :geneva_records
end
So, when you pass #genevarecord to the form it is initialized as GenevaRecord.new and searches for geneva_records_path which is undefined because you have defined it as genevarecords_path which doesn't match you model (resources)..
Hope it helps in understanding..

Rails form_for route module undefined method

I'm having a issue with scopes.
I've defined
# routes.rb
resources :asientos, module:'asientos'
# app/models/asientos/asiento.rb
module Asientos
class Asiento < ActiveRecord:Base
end
end
# app/controllers/asientos/asientos_controller.rb
module Asientos
class AsientosController < ApplicationController
def new
#asiento = Asientos::Asiento.new
end
end
end
# app/views/asientos/asientos/new
<%= form_for(#asiento) do |f| %>
...
rake routes
asientos GET /asientos(.:format) asientos/asientos#index
POST /asientos(.:format) asientos/asientos#create
new_asiento GET /asientos/new(.:format) asientos/asientos#new
edit_asiento GET /asientos/:id/edit(.:format) asientos/asientos#edit
asiento GET /asientos/:id(.:format) asientos/asientos#show
PATCH /asientos/:id(.:format) asientos/asientos#update
PUT /asientos/:id(.:format) asientos/asientos#update
DELETE /asientos/:id(.:format) asientos/asientos#destroy
Now whenever the form tries to render, i get
undefined method `asientos_asiento_index_path' for #<#<Class:0x000000065b3b40>:0x00000006ba5f30>
I've seen some of the answers like
form_for and scopes, rails 3
Module route in Rails with form_for(#object)
But none of them present a clear solution, o suggest some kind of patching.
Furthermore, form_for now generates asientos_ prefix, and in my controller now i have to rename also params.require(:asientos) to params.require(:asientos_asientos) ... not pretty...
Any suggestions (besides undoing namespacing) would be much appreciated. Thanks in advance.
Edit
It seems like by adding the following to the module definition, the route is generated as expected, without the "scope"
module Asientos
def self.use_relative_model_naming?
true
end
...
end
But it still wants an asientos_index_path... "undefined method `asientos_index_path'"
Well, after diving into tons of articles, and code i found the answer and as some posts pointed it has to do with Inflections.
My app has custom inflections for spanish, in which inflections look like:
Usuario -> Usuarios
Asiento -> Asientos
ItemAsiento -> ItemsAsiento
you'll notice it pluralizes the first word instead. That being said, the following piece of code from rails source extracted from rails/activemodel/lib/active_model/naming.rb shows the internals of whats happening
def initialize(klass, namespace = nil, name = nil)
#name = name || klass.name
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if #name.blank?
#unnamespaced = #name.sub(/^#{namespace.name}::/, "") if namespace
#klass = klass
#singular = _singularize(#name)
#plural = ActiveSupport::Inflector.pluralize(#singular)
#element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(#name))
#human = ActiveSupport::Inflector.humanize(#element)
#collection = ActiveSupport::Inflector.tableize(#name)
#param_key = (namespace ? _singularize(#unnamespaced) : #singular)
#i18n_key = #name.underscore.to_sym
#route_key = (namespace ? ActiveSupport::Inflector.pluralize(#param_key) : #plural.dup)
#singular_route_key = ActiveSupport::Inflector.singularize(#route_key)
#route_key << "_index" if #plural == #singular
end
Since my class is under Asientos namespace, the #name becomes Asientos::Asiento, #unnamespaced = "Asiento", #singular = "asientos_asiento", #plural = "asientos_asiento" and here lays the issue. #route_key gets suffixed if plural and singular are both equal.
But why are those equal? Since Spanish inflections pluralize first word, and it does not know of namespaces, the "asientos_asiento" is considered plural (true from an inflections stand point but wrong since first part is namespace not model name).
I guess here conventions played me, since convetion seems to assume last part is always the model name, thus english plural will always work just fine.
This should not happen since rails is already detecting the namespace, and it should not rely on the #name itself but strip the namespace and then singularize and pluralize without the namespace.
Monkey patch... here we go...
Thanks to everybody.
you need to specify the module/namespace in the form_for helper like this:
<% form_for [:asientos, #asiento] do |f| %>

JSONAPI resources: serialize a namespaced model

I'm trying to use JSONAPI Resources in a Rails engine and I've defined DokiCore::Tenant (the model) in doki_core/app/models/tenant.rb and DokiCore::TenantResource in doki_core/app/resources/tenant_resource.rb. When I attempt to serialize to hash, I encounter the following error:
NoMethodError: undefined method tenant_path' for #<Module:0x007f9d04208778>
from /Users/typeoneerror/.rvm/gems/ruby-2.2.2#doki/gems/jsonapi-resources-0.6.1/lib/jsonapi/link_builder.rb:77:inpublic_send'
The resource uses model_name to let it know where the model actually is:
module DokiCore
class TenantResource < JSONAPI::Resource
model_name 'DokiCore::Tenant'
# ...
end
end
I'm trying to output the hash for a tenant like so:
tenant = DokiCore::Tenant.find(1);
resource = DokiCore::TenantResource.new(tenant, nil);
serializer = JSONAPI::ResourceSerializer.new(DokiCore::TenantResource);
serializer.serialize_to_hash(resource);
which is where the error happens.
How can I get the links to work correctly and/or disable them? I assume this is there it adds the URL to the resource as a link under the "links" key in the outputted json.
Sorted this out. If your routes are namespaced in any way, your resources also need to be namespaced to match. My routes look something like:
namespace :api do
namespace :v1 do
resources :tenants
end
end
So the resource needs to be namespaced the same way:
tenant = DokiCore::Tenant.find(1);
resource = DokiCore::API::V1::TenantResource.new(tenant, nil);
serializer = JSONAPI::ResourceSerializer.new(DokiCore::API::V1::TenantResource);
serializer.serialize_to_hash(resource);
Another simple way to serialize your namespaced data is by using the jsonapi-utils gem. You will just need to do something like this:
class API::V1::UsersController < API::V1::BaseController
def index
jsonapi_render json: User.all
end
end
The gem is based on JSON API Resources, bringing a Rails way to get data serialized with JSON API's specs.

rails path helper not recognized in model

In my rails application I have a teams model. My route.rb file for teams looks like this:
resources :teams
In my teams_controller.rb file the line team_path(Team.first.id) works however the team_path url helper is not recognized in my model team.rb. I get this error message:
undefined local variable or method `team_path' for # <Class:0x00000101705e98>
from /usr/local/rvm/gems/ruby-1.9.3-p392/gems/activerecord-4.1.1/lib/active_record/dynamic_matchers.rb:26:in `method_missing'
I need to find a way for the model to recognize the team_path path helper.
You should be able to call the url_helpers this way:
Rails.application.routes.url_helpers.team_path(Team.first.id)
Consider solving this as suggested in the Rails API docs for ActionDispatch::Routing::UrlFor:
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including Rails.application.routes.url_helpers in your class:
#
# class User < ActiveRecord::Base
# include Rails.application.routes.url_helpers
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
In the case of the Team model from the question, try this:
# app/models/team.rb
class Team < ActiveRecord::Base
include Rails.application.routes.url_helpers
def base_uri
team_path(self)
end
end
Here is an alternative technique which I prefer as it adds fewer methods to the model.
Avoid the include and use url_helpers from the routes object instead:
class Team < ActiveRecord::Base
delegate :url_helpers, to: 'Rails.application.routes'
def base_uri
url_helpers.team_path(self)
end
end
Models are not supposed to be dealing with things like paths, redirects or any of that stuff. Those things are purely constructions of the view or the controller.
The model really should be just that; a model of the thing that you are creating. It should fully describe this thing, allow you to find instances of it, make changes to it, perform validations upon it... But that model wouldn't have any notion of what path should be used for anything, even itself.
A common saying in the Rails world is that if you're finding it difficult to do something (like call a path helper from a model) you are doing it wrongly. Take this to mean that even if something is possible, if it is hard to do in Rails it is likely not the best way to do it.
to add on the previous answer you can use Rails.application.routes.url_helpers just add in route :as like the following example:
get "sessions/destroy/:param_id", as: :logout
so you can use it as following:
Rails.application.routes.url_helpers.logout_path(:param_id => your_value)
Hopefully, this would help

Overriding Rails Default Routing Helpers

I'm writing an app where I need to override the default routing helpers for a model. So if I have a model named Model, with the corresponding helper model_path() which generates "/model/[id]". I'd like to override that helper to generate "/something/[model.name]". I know I can do this in a view helper, but is there a way to override it at the routing level?
You can define to_param on your model. It's return value is going to be used in generated URLs as the id.
class Thing
def to_param
name
end
end
The you can adapt your routes to scope your resource like so
scope "/something" do
resources :things
end
Alternatively, you could also use sub-resources is applicable.
Finally you need to adapt your controller as Thing.find(params[:id]) will not work obviously.
class ThingsController < ApplicationController
def show
#thing = Thing.where(:name => params[:id).first
end
end
You probably want to make sure that the name of your Thing is unique as you will observe strange things if it is not.
To save the hassle from implementing all of this yourself, you might also be interested in friendly_id which gives you this and some additional behavior (e.g. for using generated slugs)
You need the scope in routes.rb
scope "/something" do
resources :models
end

Resources