I'm just started with rails and until now I was very happy with it, but there is one thing I can't figure out.
I have some ActiveRecords models in a namespace "Monitor", and I have some controllers in a Namespace "Settings". What I want to accomplish is that I can use the namespaced models in my settings controllers/forms.
I've got this:
/config/routes.rb
namespace :settings do
resources :queues, :channels
end
/app/controllers/settings/queus_controller.rb
class Settings::QueuesController < ApplicationController
def new
#queue = Monitor::Queue.new()
render 'form', :layout => false
end
def create
#queue = Monitor::Queue.new(post_params)
if (#queue.save)
#status = 'added'
render 'success'
else
render 'form', :layout => false
end
end
def edit
#queue = Monitor::Queue.find(params[:id])
render 'form', :layout => false
end
...
end
/app/models/monitor/queue.rb
module Monitor
class Queue < ActiveRecord::Base
end
end
/app/views/settings/form.html.erb
<%= form_for #queue do |f| %>
...
<% end %>
Now Rails is complaining about a missing method : monitor_queues_path or Rails generates a path like /monitor/queues instead of /settings/queues(:new/edit).
What am I missing here?
Aah I found it!
This post gave me the proper solution:
Rails namescoped model object without module prefix route path
The problem came from the prefix from the ActiveRecord class:
module Monitor
class Queue < ActiveRecord::Base
end
end
This should be
module Monitor
class Queue < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new("Monitor::Queue", nil, "Queue")
end
end
end
After changing this I only needed to change the form_for in the correct way:
<%= form_for [:settings, #queue] do |f| %>
And that fixes it :D
You are using nesting for your Queue models. Therefore your form_for call needs to know about the parent model too. So in your case you nested Queue under Setting so you will need to provide a setting object as well. I'm guessing in your controller you made a #setting variable. If this is the case then the following code will work for you.
<%= form_for [#setting, #queue] do |f| %>
<%# Your form code here -%>
<% end -%>
I found a solution from my friend #mkhairi he said to use this on the parent model :
class YourParentModel < ApplicationRecord
def self.use_relative_model_naming?
true
end
end
then you can use back ur lovely short path.
Source :
https://coderwall.com/p/heed_q/rails-routing-and-namespaced-models
Related
I'm having a hard time trying to get setup some custom form objects in a new Rails 6 project I am building. I suspect this may be due to namespacing issues but I can't yet tell for sure.
app/views/saasy/signups/new.html.erb
<%= form_with(model: [ :saasy, #signup ], url: saasy_signups_path(#signup), local: true) do |signup_form| %>
<%= fields_for :account, #signup.account do |account_fields| %>
Organization name: <%= account_fields.text_field :organization %>
<% end %>
<%= signup_form.submit %>
<% end %>
app/controllers/saasy/signups_controller.rb
class Saasy::SignupsController < ApplicationController
def new
#signup = Saasy::SignupForm.new
end
def create
#signup = Saasy::SignupForm.new(signup_form_params)
#signup.register
end
private
def signup_form_params
params
.require(:saasy_signup_form)
.permit(account_attributes: [:organization])
end
end
config/routes.rb
Rails.application.routes.draw do
namespace :saasy do
resources :signups, only: [:new, :create]
end
end
app/forms/saasy/signup_form.rb
module Saasy
class SignupForm
include ActiveModel::Model
attr_accessor :user, :account
delegate :attributes=, to: :user, prefix: true
delegate :attributes=, to: :account, prefix: true
def initialize(params= {})
super(params)
#user = Saasy::User.new(params)
#account = Saasy::Account.new(params)
end
def register
# eventually do actual signup stuff here
end
end
end
However, whenever I test it I get back the following message: param is missing or the value is empty: signup_form
The params hash looks like this:
{
"authenticity_token"=>"BhhvRaYKf220afExocQ//LIY1jszVsXs+lThFeUFKvr6ciVBsa+22mSxwO3yT6mK2uOsWSCKx9gL6WIaGmmvSg==",
"account"=>{"organization"=>"Example Name"},
"commit"=>"Create Signup form"
}
I've tried a whole lot of general messing around solutions like playing with the form_with in the view and changing route names etc but I've not had any luck so far. Any advice would be really appreciated!
This doesn't have anything to do with namespaces. You're just requiring the wrong param key.
def signup_form_params
params
.require(:signup_form)
.permit(account_attributes: [:organization])
end
Rails gets the "key" for the inputs by calling model_name.param_key on the model that you pass. The param key does not take into account the module nesting of the class. Neither should it really as thats an implementation detail and not nessicarily part of the "public api" that your app exposes via HTTP. Your code organization and the routes / http parameters of your application are two very different things.
You can override the key by providing the scope: option to form_with.
<%= form_with(model: [ :saasy, #signup ], scope: :saasy_signup_form, local: true) do |signup_form| %>
<%= fields_for :account, #signup.account do |account_fields| %>
Organization name: <%= account_fields.text_field :organization %>
<% end %>
<%= signup_form.submit %>
<% end %>
But IMHO its just silly.
I'm following the Rails tutorial and making changes where appropriate, with the intention that my tutorial project will become a full-fledged production app after the completion of the tutorial.
I've run into a snag with the second model portion of the tutorial. Here is how I've written my second model.
In my policy.rb:
class Policy < ApplicationRecord
has_one :insured
end
In my insured.rb:
class Insured < ApplicationRecord
belongs_to :policy
end
In my routes.rb:
resources :policies do
resource :insured
end
In my insureds_controller.rb:
def create
#policy = Policy.find(params[:policy_id])
# next line is raising the error
#insured = #policy.insured.create(insured_params)
redirect_to #insured
end
private
def insured_params
params.permit(:name, :address, :phone, :email)
end
I've inspected the #policy object with render plain: #policy.inspect and can confirm that ActiveRecord is retrieving the policy correctly. When I inspect the attributes of #policy, using render plain: #policy.attribute_names.inspect, I don't see an insured attribute, which I thought Rails was supposed to automatically manage for me. In the tutorial, an article has_many :comments, and a comment is supposedly easily created and associated with the parent article with this call: #article.comments.create(comment_params). I also noticed that the tutorial uses params.require(:comment).permit(...) while I have to use params.permit(...), after inspecting the params hash I saw that the :insured attributes existed in the top-level of the hash, instead of being tied to an :insured key within the hash.
I tried manually saving and assigning the #insured object like so:
def create
#policy = Policy.find(params[:policy_id])
#insured = Insured.new(insured_params)
if #insured.save
#policy.insured = #insured
redirect_to #insured
end
end
Only to run into the following error in my .../insureds/new.html.erb:
<h1>New Insured</h1>
<h1><%= #policy.policy_number %></h2>
<%= render 'form' %>
<%= link_to 'Cancel', policy_path(#policy) %>
Which derives from my partial form .../insureds/_form.html.erb:
# the following line raises the error
<%= form_with model: #insured, local: true do |form| %>
# html omitted for brevity
<% end %>
Error: 'undefined method insureds_path'. This is weird because when I inspect the HTML I can see the form action for this view is /policies/[:id]/insured.
Sorry for the massive wall of text, I wanted to show you guys that I did try to figure out what is going wrong.
There is an error in your config/routes.rb file:
resources :policies do
# change it for:
collection do
get 'insured', to: 'policies#show_insured', as: 'show_policy_insured'
# maybe unnecessary to be here
# get 'insured/new', to: 'insureds#new', as: 'new_policy_insured'
# post 'insured/create', to: 'insureds#create', as: 'create_policy_insured'
# delete 'insured/delete', to: 'insureds#delete', as: 'delete_policy_insured'
end
end
# add resources here
resources :insureds
In policy_controller.rb:
def show_insured # 'policy/:id/insureds/
end
In insureds_controller.rb:
def show # '/insureds/:id'
end
def create
...
redirect_to show_policy_insured && return if #insured_policy
end
# before_filter or before_action
#policy = Policy.find(params[:id])
#insured_policy = #policy.insured
Check it and run this to see your routes:
$ bundle exec rake routes
get /policies/:id/insured => 'policies_controller#show_insured'
get /insureds/:id => 'insureds_controller#show'
get /insured/new => 'insureds_controller#new'
post /insureds/create => 'insureds_controller#create'
delete /insureds/:id/delete => 'insureds_controller#delete'
#maguri, that's not all necessary. The stumbling block I was running into was that Rails couldn't automatically determine the correct routes. When I provided my own urls in the form_with declarations, everything went smoothly.
Observe the following change in my _form.html.erb for the Insured model, which belongs_to Policy, which has_one Insured.
<%= form_with model: #insured, url: policy_insured_path(#policy) local: true do |form| %>
In my updated insureds_controller.rb file, using #Phlip's suggestion:
def create
#policy = Policy.find(params[:policy_id])
#insured = #policy.create_insured(insured_params)
if #policy.insured.save
redirect_to policy_insured_path(params[:policy_id])
else
render 'new'
end
end
This allows me to keep routes.rb clean and simple:
resources :policies do
resource: insured
end
Thank you for your answer, it helped me discover the problem was with my routes.
Suppose I have defined in my routes the following line
resources :a, controller: 'b', as: 'a'
and the following controller
class BController < ApplicationController
...
def new
#b = B.new
end
def edit
#b = B.find(params[:id])
end
end
Is there a way to write a form partial so that I can use it on both the new and edit views? For example, if I write
<%= form_for #b do |f| %>
...
<% end %>
Rails gives me a routing error.
Inelegant Solution
I came up with an inelegant solution to write the BController as
class BController < ApplicationController
...
def new
#b = B.new
#url = bs_path
end
def edit
#b = B.find(params[:id])
#url = b_path
end
end
and then write the form as
<%= form_for(#b, url: #url) do |f| %>
...
<% end %>
I wonder if there is a more "Rails" way to accomplish this though without having to write in the #url variable
Seems like you might be looking for the :path option to the resources...if I followed your example correctly, you have a model B and you want to access it at /a or /a/:id and be able to use a_path(instance_of_b)...so I feel like you want your route to be
resources :b, as: 'a', path: 'a'
and I believe that'll do what you're wanting.
I'm trying to make a form that will post to a database, I'm really struggling at the moment and i'm getting this error.
NameError in AddController#index
uninitialized constant AddController::Newevents
Could you advise what i would need to do?
Heres all the code i have
Form
<%= simple_form_for(#newevent) do |f| %>
<%= f.input :eventname, required: true %>
<%= f.input :eventdate %>
<%= f.input :eventimage %>
<%= f.button :submit %>
<% end %>
controller
class AddController < ApplicationController
def index
#newevent = Newevent.new
end
end
Model
class Newevent < ActiveRecord::Base
def event_params
params.require(:Newevent).permit(:eventname, :eventdate, :eventimage)
end
end
Routes
resources :add
Edit
i now have this error undefined methodnewevents_path'` after changing this
#newevents = Newevent.new
It seems that you miscopied your code here. The error message indicates that your index method actually looks like this
def index
#newevent = Newevents.new
end
Remove the s from the end of Newevent and it should work.
RE: your edit
Your routes declare that you have a resource named add, if you want to show and create your Newevent objects, then you should create a controller for that. Declare resources :newevents in your routes and create a controller to handle it.
You should research RESTful routes, because that's what Rails's resource routing works best with. The form to create a new object should be displayed by the new action and not index.
You should be using create method instead of index if you are using POST http method. index will be called if you are using GET method and it shouldn't be used to post the form data. Refer this link for more information on rails routing.
class AddController < ApplicationController
def create
#newevent = Newevent.new
end
end
I have two classes (Impressions and Replies) which inherit from the parent class Comment:
class CommentsController < ApplicationController
. . . .
end
class ImpressionsController < CommentsController
. . . .
end
class RepliesController < CommentsController
. . . .
end
In my view, I want them to render the same way. Right now, I'm approaching it like this:
<%= render #comment %>
Ideally, this would render the partial "/comments/_comment", but instead Rails want to render things like "/impressions/_impression" or "/replies/_replies." Is there any way to strong arm Rails into do "/comments/_comment"?
With :collection you can render a collection of objects. Given a single object you should us :object instead.
<%= render partial: '/comments/comment', object: #impression %>
The :as is not necessary as long as the partial is named 'comment'. If you name your partial e.g. 'my_comment' then #impression would be accessible via the local variable 'my_comment' and you would have to use :as to define a different local name.
However in your case I would prefer to define a partial path for the Impression and Replies model as follows (Rails >3.2.?):
class Impression < ActiveRecord::Base
...
def to_partial_path
"comments/comment"
end
end
Then you can use the standard rendering with an object or collection
<%= render #comment %>
I think smth like this can help:
<%= render :partial => '/comments/comment', :collection => #impressions,
:as => :comment %>