Namespaced Form Objects in Rails - ruby-on-rails

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.

Related

Following Rails tutorial, undefined method 'create' for nil:NilClass, with nested resource

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.

name error uninitialized constant path in rails

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

Trying to add a foreign relationship to my users model with Devise, then update it

This is for Rails 4.04 and Ruby 2.1. I'd like to allow my users to have addresses. So I generated an address table and gave it columns (NUMBER, STREET, CITY, STATE). Now when I go to the following url, I'd like to be able edit this information:
webapp.com/users/edit/
However I noticed it only showed the same old information (name, password, email). So I went to the view and added simple_fields for my new relationship so the view now looks like this:
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<div class="form-inputs">
<%= f.input :email, required: true, autofocus: true %>
<%= f.input :name, required: false %>
<%= f.simple_fields_for :addresses do |a| %>
<%= a.input :number %>
<%= a.input :street %>
<%= a.input :city %>
<%= a.input :state %>
<%= a.input :country %>
<% end %>
<%end%>
However it still doesn't generate the fields needed for address. I think this is because none of my users currently have any addresses attached to their account profile (because this migration was just created). However, in this case there should be blank fields generated so I can ADD address information.
I feel like I need to do something in the Users#Edit action like this
#users.each do |user|
user.address.build
end
Is that right? How can I override the users controller because this was created by Devise and I don't actually have a users controller (I looked for it it and couldn't find it).
UPDATE
Ok, I'm getting closer. I had to create my own controller to override Devise's default registrations controller as explained in the second answer of this stack overflow article:
Override devise registrations controller
So now I am getting into that controller which currently looks like this:
class Users::RegistrationsController < Devise::RegistrationsController
def edit
super
end
end
However, when I get to my view, it's still SKIPPING the block that starts like this:
<%= f.simple_fields_for :addresses do |a| %>
However, if I go manually into my DB and add a record in the addresses table and link it to my currently_signed in user via the foreign key, then the block does not get skipped. So whats the best way to generate this connection if the address record does not yet exist? Is it the build method? e.g.
user.address.build
in the controller
SOLUTION
Yes, I needed to added this method to my new registrations_controller.rb file
def edit
if resource.addresses.size == 0
resource.addresses.build
end
super
end
It is now working the way I intended it.
You need to do it like this:
#app/models/user.rb
Class User < ActiveRecord::Base
has_one :address
before_create :build_address, unless: Proc {|x| x.address.present? } #-> not sure about the if statement
end
#app/models/address.rb
Class Address < ActiveRecord::Base
belongs_to :user
end
--
Devise does come with Controllers
These controllers are not shown in your app (they are installed with the Devise gem, but only visible in production):
- confirmations_controller.rb
- omniauth_callbacks_controller.rb
- passwords_controller.rb
- registrations_controller.rb
- sessions_controller.rb
- unlocks_controller.rb
--
You Don't Need Them
Whilst you can override these controllers, you won't need to, as your edit action will be tied to the users controller:
#config/routes.rb
resources :users
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def edit
#user = User.find params[:id]
end
end
#app/views/users/edit.html.erb
<%= form_for #user do |f| %>
...
<%= f.fields_for :address do |a| %>
<% end %>
<% end %>
Update
Sorry for not explaining. You can use resources :users with devise_for :users:
#config/routes.rb
devise_for :users
resources :users, only: [:edit, :update]
If this does not work, you may need to change the path_names argument for devise, like so:
#config/routes.rb
devise_for :users, path_names: { sign_in: 'login', password: 'forgot', confirmation: 'confirm', unlock: 'unblock', sign_up: 'register', sign_out: 'logout'}
-
Form
The form can have resource, but I think you need to work out what you're trying to change. If you're trying to change a devise object, then use the resource helper; but if you're trying to change the User model directly - I'd be partial to changing that!
The issue I think you have is if you're using resource, it's going to route to Devise controller actions. I would try setting #user in the edit action of your users controller, and use the conventional way to update
it looks like you didn't made the relation between those models add to addresses column called user_id and add to user model:
has_many :addresses
and into addresses:
belongs_to :users
then in your view you can build this form using nested attributes see this post:
Rails 4 Nested Attributes Unpermitted Parameters
another option that you can do is to show that addresses form after the user already signed in then when he update the form find the user_id with current_user and build the record using this id but using strong params in Rails 4 is recommended to solve your issue.

Weird Rails 4 RESTful URL after form error

I have a RESTful controller inside of a namespace called dashboard, so my URL looks like this:
/dashboard/member
/dashboard/member/edit
Something weird is happening when I submit the member form with a validation error... it shows the error like it's suppose to, but when it goes to the PATCH url "/dashboard/member" it comes with an ".2" in the end:
/dashboard/member.2
the "2" is the ID of the record.
The funny thing is that I did everything correctly and it works great, this ".2" is the only thing that is bothering my head.
My Controller
class Dashboard::MembersController < ApplicationController
load_and_authorize_resource :class => Member
before_filter :authenticate_member!
def show
end
def edit
#member ||= current_member
end
def update
#member ||= current_member
if #member.update_attributes(member_params)
flash[:success] = "Profile updated"
redirect_to dashboard_member_path
else
render "edit"
end
end
private
def member_params
params.require(:member).permit(:first_name, :last_name, :address, :city, :state, :country, :zipcode, :home_phone, :cell_phone)
end
end
My Route
namespace :dashboard do
resource :member, only: [:show, :edit, :update]
end
If you meant to use resource :member (instead of resources :member) then you should know that it always looks up without referencing an ID.
Here is how the routes would be created without id's:
edit_dashboard_member GET /dashboard/member/edit(.:format) dashboard/members#edit
dashboard_member GET /dashboard/member(.:format) dashboard/members#show
PATCH /dashboard/member(.:format) dashboard/members#update
PUT /dashboard/member(.:format) dashboard/members#update
When you are sending a PATCH request make sure that you don't pass an argument with it. If you pass an argument then it would be interpreted as format(like .html, .js etc). In your case you passed an argument as 2 or a member with an id 2
For eg:
PATCH request to dashboard_member_path(2)
The route was matched against PATCH /dashboard/member(.:format) dashboard/members#update
2 is interpreted as (.:format) because there is no :id part.
I had to remove the #member from my form, so... I had this:
<%= form_for(#member, url: dashboard_member_path(#member), html: {method: "patch", class: "form-horizontal"}) do |f| %>
And it became this:
<%= form_for(#member, url: dashboard_member_path, html: {method: "patch", class: "form-horizontal"}) do |f| %>
Now, there's IDs being passed to the URL.
Thanks guys!

Namespaces route and model path rails

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

Resources