Update model attributes in a new controller - ruby-on-rails

I have two models implemented User and Order with their own Controllers being used by AdminController.
Now I need to edit/update this model from Client with a ClientController.
I've created the following so far:
class Client::BaseController < ApplicationController
end
class Client::OrdersController < Client::BaseController
end
class ClientArea::UsersController < ClientArea::BaseController
end
I tried to add a new action to UserController in order to get the attributes of the User model and then change its password:
def update_password
#user = User.find(current_user.id)
end
and added the following to my view/client/user/update_password
<%= simple_form_for #user do |f| %>
<%= f.input :email, :label => 'Your email please' %>
<%= f.input :crypted_password, :hint => 'No special characters.' %># <%= f.input :remember_me, :as => :boolean %>
<%= f.button :submit %>
<% end %>
I know this is not the write code to update some attribute, but just with this code I'm already getting this error:
undefined method `individual_path' for #<#:0x007ffef8d06930>
I believe this error comes from the ClientController not-knowing the Model User, but I'm not sure, and I have no idea of how I could do this.

Related

Using form_for with "becomes" loses child association

When building out a form that handles STI, if I use becomes to transform the object to its parent class, I lose the ability to use nested fields with it.
I have two models
class Login < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :login
accepts_nested_attributes_for :login
end
I also have a few subclasses of User.
class Consumer < User
end
class Admin < User
end
class Agent < User
end
Initially I had problems with the routing, since Rails would assume that I wanted a route specific to the current class rather than the parent class, so I used #user.becomes(User), which is apparently the way to handle that. For the most part it works fine, however this causes #user.login to disappear.
Controller
class Admin::UsersController < AdminController
load_and_authorize_resource
before_filter :authenticate_user!
def index
render 'index'
end
def new
#user = User.new
#user.build_login
render 'new'
end
def create
#user = User.new(user_params)
if #user.save
flash[:notice] = "Account confirmation instructions sent to #{#user.login.email}"
redirect_to new_user_path
else
flash.now[:error] = #user.errors.full_messages.to_sentence
# At this point, I can confirm that #user.login still exists...
render 'new'
end
end
private
def user_params
params.require(:user).permit(
:type,
:dealership_id,
login_attributes: [
:email
])
end
end
Here's the most relevant form view bit
<%= simple_form_for(#user.becomes(User), html: {class: "user-form"}) do |f| %>
<%= f.simple_fields_for :login do |l| %>
<div class="field">
<%= l.label :email %><br />
<%= l.email_field :email %>
</div>
<% end %>
<div class="field">
<%= f.label :type %>
<%= f.select :type, options_for_select(current_user.types_can_create), include_blank: "- Select -", class: "form-control", id: "select_type" %>
</div>
<div class="actions">
<%= f.submit "Register" %>
</div>
<% end %>
The text field for :email doesn't display because #user.login is now nil. Is this expected behavior when using becomes?
Having only used becomes once before, I can only attest to my scant experience -- whenever you use it, it essentially invokes a new instance of the class.
I'm not sure as to the specifics, but the bottom line is that I would surmise that your #user.becomes(User) is overriding #user.build_login...
Returns an instance of the specified klass with the attributes of the current record.
--
In your case, I would set the path explicitly (as you're using User anyway):
<%= simple_form_for #user, url: user_path, method: :post html: {class: "user-form"} do |f| %>
In Rails 5, this can be solved with
<%= form_with scope: :user,
model: #user,
url: #user.id ? user_path(user) : users_path,
local: true do |f| %>
...
...
<% end %>
The previous code instructs the FormBuilder to fill in the fields with the #user attributes, but it also instructs it to submit to the main route (not the inherited ones) and, with scope:, it also instructs it to name the fields using the User class name, not the child class names.

How to validate multiple models in a single transaction?

In my rails (4.1.6) app, I have a contact model that has_one :address, :email
I construct a contact and related address and email in a single form using fields_for:
views/contacts/new.html.erb
<%= form_for #contact, ... %>
...
<%= fields_for :address do |address_fields| %>
<%= address_fields.text_field :street, ... %>
<%= address_fields.text_field :city, ... %>
...
<% end %>
<%= fields_for :email do |email_fields| %>
<%= email_fields.text_field :display_name, ... %>
<%= email_fields.text_field :mail_id, ... %>
<% end %>
...
<% end %>
I want email to be required, while address is optional. In other words, if email is not provided, none of the 3 models should be created, but if only email is provided, the email and contact must be created.
One way that does work is to validate the params manually in the contacts_controller#create before constructing anything, and flash[:error] and return without saving if email is not specified, or save it if all is well:
contacts_controller.rb
def create
#contact = Contact.new
if(params_email_valid? params)
#contact.save!
#email = Email.create(...)
#email.save!
...
else
flash[:error] = 'Email must be specified to save a contact'
redirect_to :root
end
end
private:
def params_email_valid? params
!(params[:email][:display_name].blank? || params[:email][:mail_id].blank?)
end
Another way that may work is to drop down to SQL and validate everything through direct SQL calls in a transaction.
However, both of these are not 'the rails way', since validations belong in the models. So, I am trying to use some combination of validates_presence_of, validates_associated and custom validators to validate this scenario. The problem here is that model level validation of associated models requires either self to be already saved in the database, or the associated model to be already saved in the database. Is there a way to validate all these models in a single transaction?
Considering you have appropriate validations in the models:
class Contact <
has_many :addresses
has_many :emails
#add
accepts_nested_attributes_for :addresses, :emails #you can add some validations here to like reject_all if blank? see the docs
end
class Address <
belongs_to :contact
end
class Email <
belongs_to :contact
end
In your CompaniesController
def new
#contact = Contact.new
#contact.addresses.new
#contact.emails.new
end
def create
#contact = Contact.new(contact_params)
if #contact.save
#redirect add flash
else
#add flash
#render action: new
end
protected
def contact_params
#permit(#contact_fields, address_attributes: [#address_fields], email_attributes: [#email_fields])
end
And you would like to modify your form like this
<%= form_for #contact, ... do|f| %>
...
<%= f.fields_for :address do |address_fields| %>
<%= address_fields.text_field :street, ... %>
<%= address_fields.text_field :city, ... %>
...
<% end %>
<%= f.fields_for :email do |email_fields| %>
<%= email_fields.text_field :display_name, ... %>
<%= email_fields.text_field :mail_id, ... %>
<% end %>
...
<% end %>
So accepts_nested_attributes helps you validate the child as well as the parent and adds [child]_attributes getters and setters, So normally in your form what was contact[email][display_name] will become contact[email_attributes][display_name]

Rails 4 Nested Forms Simple_form_for & simple_field_for

I'm just trying to generate a simple nested form, like so:
<%= simple_form_for #profile do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :phone_number %>
<%= f.simple_fields_for :addresses do |p| %>
<%= p.input :street %>
<%= p.input :city %>
<%= p.input :state, collection: us_states %>
<%= p.input :zip_code %>
<% end %>
<%= f.button :submit %>
My models:
class Profile < ActiveRecord::Base
belongs_to :customer
has_many :addresses
accepts_nested_attributes_for :addresses
end
class Address < ActiveRecord::Base
belongs_to :profile
end
My controller:
class ProfilesController < ApplicationController
before_action :authenticate_customer!
def new
#profile = Profile.new
end
end
Unfortunately that nested attribute addresses doesn't populate anything on the page, I would expect to see fields like street or city but I get nothing.
However, if I change <%= f.simple_fields_for :addresses do |p| %> to <%= f.simple_fields_for :address do |p| %> the fields display correctly.
Unfortunately doing this causes issues because I can't use the accepts_nested_attributes_for helper as outlined in the docs (as far as I can tell). Any idea why this isn't working?
The reason is because nested forms require created objects to work. It looks like Profile gets instantiated but Address does not.
class ProfilesController < ApplicationController
before_action :authenticate_customer!
def new
#profile = Profile.new
#profile.addresses.create # this will create the address object that the nested form will use
end
end
I think you will need to create Profile as well rather than create an instance of it.
#profile = Profile.create
I've just been working with nested forms myself and this is how it worked for me.
The solution was to build the profile and the addresses in the #new action for it to work. Revised working code:
class ProfilesController < ApplicationController
before_action :authenticate_customer!
def new
#profile = current_customer.build_profile
#profile.addresses.build
end
end
You'll need to look at how your params come through, but since I have a has_many, they came through hashed with a key of a record id.

Rails STI: implementing child class edit form

There is a lot of discussion around Rails 3 STI and how to use forms, but no definitive answers on StackOverflow. I seem to have run into a similar issue and have attempted the other solutions with no results.
I have two models with the following inheritance set up:
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name, #more follows
Waiter.rb
class Waiter < User
On the form at /waiters/users/[:id]/edit, I am attempting to create a form for editing the waiter. However, I am getting the following error upon loading the edit view:
undefined method `waiter_path' for #<#<Class:0x007fbd08cef9d8>:0x007fbd09532fa0>
This is my controller found at /app/controllers/admin/waiters/users_controller.rb:
def edit
form_info
#user = Waiter.find(params[:id])
end
def update
#user = Waiter.find_by_id(params[:id])
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully assigned Waiter."
redirect_to admin_waiters_users_url()
else
form_info
render :action => 'edit'
end
end
And this is the form located in the edit view:
<%= simple_form_for #user do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :email %>
<%= button_tag(type: 'submit', class: "btn btn-primary") do %>
<i class="icon-ok icon-white"></i> Save
<% end %>
<% end %>
What am I doing wrong here with STI and routing?
UPDATE: here is my rake routes:
admin_waiters_users GET /admin/waiters/users(.:format) admin/waiters/users#index
POST /admin/waiters/users(.:format) admin/waiters/users#create
new_admin_waiters_user GET /admin/waiters/users/new(.:format) admin/waiters/users#new
edit_admin_waiters_user GET /admin/waiters/users/:id/edit(.:format) admin/waiters/users#edit
admin_waiters_user GET /admin/waiters/users/:id(.:format) admin/waiters/users#show
PUT /admin/waiters/users/:id(.:format) admin/waiters/users#update
You should use your routes to see what routes you have defined:
You can run your routes with:
rake routes
I can not see your routes but perhaps waiter_path does not exist.
Perhaps is user_waiter_path(#user) or other router.
Please paste your routes for that the people on stackoverflow can help to you.
I can not see the route waiter_path on your routes, If you have waiter_path inside of your edit view you have remove it.
Also, you can specify what controller and action hit,
<%= simple_form_for #user, :url => { :controller => "admin/waiters/users", :action => "update"} do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :email %>
<%= f.button :submit, "save", class: "btn btn-primary"%>
<% end %>
You can check with f.button instead button_tag
Regards!

Using has_one with condition to find related object, but how do you create the related object?

I have an Account model with the following :
has_one :primary_user, :class_name => "User", :conditions => "role = 'primary_user'"
So #account.primary_user looks for a user with a role of primary_user.
When creating a new account, I want to be able to create a new primary_user. What is the "Rails way" to do that ?
Do I need to create a primary_user= method?
Here is my create form ..
<%= semantic_form_for #account do |f| %>
<%= f.input :account_name %>
<%= f.semantic_fields_for :primary_user do |user| %>
<%= user.input :email %>
<% end %>
<% end %>
If I submit this form I get
ActiveRecord::AssociationTypeMismatch (User(#2159789500) expected, got ActiveSupport::HashWithIndifferentAccess(#2159703820)):
Thanks
Little cleaner:
has_one :primary_user, :class_name => "User", :conditions => { :role => "primary_user" }
Then straight solution:
<%= semantic_form_for #account do |f| %>
<%= f.input :account_name %>
<%= f.semantic_fields_for :primary_user, primary_user || build_primary_user do |user| %>
<%= user.input :email %>
<% end %>
<% end %>
But I suggest you to add this into new action in controller
#account.build_primary_user
This builds on top of what #fl00r says in his post... I agree the proper place to make build the objects is in the controller. In the new action on your AccountsController, you just need to add a line like this:
#account.primary_user ||= #account.build_primary_user
So your new action would look something like this (plus anything extra you added):
def new
#account = Account.new
#account.primary_user ||= #account.build_primary_user
end
Then your code for the create form should work as is.

Resources