I'm trying to add a nested attributes to my signup form (generated by devise), but it didn't work.
To give a brief explanation:
A user has many locations, and each location has a label name (home,eg.) and an address.
What I wanted to do here is to allow new user to register their location(nested attributes) in the signup form.
In other words, to create a new location, which is associated with the new user.
*I have already set up strong params and controllers, by referring to stackoverflow threads and other sources.
Here's my codes:
User Model
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :locations, dependent: :destroy
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true
accepts_nested_attributes_for :locations
end
Location Model
class Location < ApplicationRecord
belongs_to :user
validates :label, presence: true
validates :address, presence: true
geocoded_by :address
after_validation :geocode, if: :will_save_change_to_address?
end
My Signup Form
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<div class="form-inputs">
..........
<%= f.simple_fields_for :location do |p| %>
<%= p.input :address %>
<%= p.input :label, collection: ['Home', 'Work'] %>
<% end %>
<%= f.button :submit, "Sign up", class: "btn btn-long" %>
</div>
<% end %>
Registration Controllers (Devise)
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
def new
build_resource({})
self.resource.locations.build
respond_with self.resource
end
protected
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :first_name, :last_name, :password, :photo, locations_attributes: [:label, :address]])
end
end
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :first_name, :last_name, :password, :photo, locations_attributes: [:label, :address]])
end
end
DB Schema
ActiveRecord::Schema.define(version: 2019_08_30_001623) do
create_table "locations", force: :cascade do |t|
t.string "label"
t.text "address"
t.float "latitude"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.float "longitude"
t.index ["user_id"], name: "index_locations_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "first_name"
t.string "last_name"
t.boolean "admin"
t.text "preference", default: "no preference"
t.integer "default_location"
t.integer "radius"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Please let me know if you have any additional question or if you need anymore info.
Appreciate all your helps
Related
Hi I have an app that uses devise for authentication and devise invitable.
On sign up the user creates an account.
class Account < ApplicationRecord
belongs_to :user, class_name: "owner", foreign_key: "owner_id"
has_many :users, dependent: :destroy
has_many :clients, dependent: :destroy
end
The user signs up and is given the role admin by default on create!
class User < ApplicationRecord
has_merit
enum role: [:user, :tech, :admin, :manager]
has_one :account, foreign_key: 'owner_id'
accepts_nested_attributes_for :account
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :invitable, :registerable,
:recoverable, :rememberable, :validatable
end
I am confused on how I can manage the user has_one :account as owner (user signs up)and belongs_to: account as employee (user is invited)
Schema
create_table "accounts", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "owner_id", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.integer "role"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "sash_id"
t.integer "level", default: 0
t.bigint "account_id"
t.index ["account_id"], name: "index_users_on_account_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
app/views/devise/registrations/new.html.erb
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :email,
required: true,
autofocus: true,
input_html: { autocomplete: "email" }%>
<%= f.simple_fields_for :accounts do |a| %>
<%= a.input :name %>
<% end %>
<%= f.input :password,
required: true,
hint: ("#{#minimum_password_length} characters minimum" if #minimum_password_length),
input_html: { autocomplete: "new-password" } %>
<%= f.input :password_confirmation,
required: true,
input_html: { autocomplete: "new-password" } %>
</div>
<div class="form-actions">
<%= f.button :submit, "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
would you recommend a join table account_users account:references user:references... or is there a simple way to do this?
I thought about an Admin Devise model but that makes login a bit of a pain.
You would do better by having Account as the parent and User as the child like so:
Account has_many Users
So what you could do is in your User model create a callback to check for the presence of an account and create one if it's blank.
before_validation :create_account_if_blank
def create_account_if_blank
if self.account.blank?
ApplicationRecord.transaction do
account = Account.create!(name: self.full_name)
some_other_thing = Something.create!(name: 'test')
end
end
end
Then when you create another user from your "Admin" account, just set the the current account from the controller.
You could even do something like this:
current_account.users.create(your parameters here)
Put the current_account function in your application controller.
The current_account function would look like this:
def current_account
return current_user.account
end
I think you can use STI instead to have Owner class and Employee class both inherit from User and role as inheritance_column, then you can make polymorphic relation between the roles and the account
class Employee < User
has_one :account, as: :accountable
end
class Owner < User
has_one :account, as: :accountable
end
# do the same with the other roles it gives you more flexibility to have different behaviour for every role than using only User class
Account < ApplicationRecord
belongs_to :accountable, polymorphic: true
end
I am trying to sign up using devise for an application I am making. The form submit action is sending the correct params but I am failing on one presence validation. That presence validation is a checkbox field where a user selects on sign up whether they are single or not.
Here is the code for views/devise/registrations/new.html.erb
<div class="radio">
<%= f.label :relationship_status%><br />
<%= radio_button_tag(:relationship_status, "single") %>
<%= label_tag(:single, "Single") %>
<%= radio_button_tag(:relationship_status, "relationship") %>
<%= label_tag(:relationship, "In a relationship") %>
</div>
Here is the ApplicationController where I specify the params to be passed aside from the defaults provided by devise:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name,
:username, :birthday,
:relationship_status])
end
end
Here is my user model where I set basic presence validators
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :name, :email, :username,
:birthday,:relationship_status, presence: true
end
Finally, here is an image of clicking Sign Up once I fill the form:
ActiveRecord::Schema.define(version: 2019_06_05_024141) do
create_table "users", force: :cascade do |t|
t.string "name", null: false
t.string "username", null: false
t.datetime "birthday", null: false
t.string "relationship_status", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
As you can see in the picture relationship_status is clearly set to single. Yet I keep getting an error saying that it can't be blank. I am stumped and some help would be appreciated. Thank you.
could you try with this.
<div class="radio">
<%= f.label :relationship_status%><br />
<%= f.radio_button(:relationship_status, "single") %>
<%= label_tag(:single, "Single") %>
<%= f.radio_button(:relationship_status, "relationship") %>
<%= label_tag(:relationship, "In a relationship") %>
</div>
I don't know how this was fixed, but my approach was to confirm with byebug. While doing so, I executed rails generate devise:controllers [scope] and modified my route.rb with the following:
Rails.application.routes.draw do
devise_for :users, controllers: {
sessions: 'users/sessions'
}
end
This fixed it.
I have a little app that is using devise and apartment gems.
I have User(devise) who has one organization(tenant_name).
The organization has one :owner, class_name 'user'
I want to use the Devise registration form to also create the tenant and assign to the admin.
I think I have read that many tutorials on devise apartment / devise custom controllers / devise nested attributes that I have confused myself.
Registrations form
app/views/devise/registrations/new.html.erb
<div class="row">
<div class="col-lg-4 col-md-6 ml-auto mr-auto">
<h1 class="text-center">Sign Up</h1>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render partial: 'devise/shared/error_messages', resource: resource %>
<div class="form-group">
<%= f.email_field :email, autofocus: false, class: 'form-control', placeholder: "Email Address" %>
</div>
<div class="form-group">
<%= f.simple_fields_for :organizations do |o| %>
<%= o.input :name, placeholder: "Organization Name", warning: "Cant Be Changed", label: false %>
<% end %>
</div>
<div class="form-group">
<%= f.password_field :password, autocomplete: "off", class: 'form-control', placeholder: 'Password' %>
</div>
<div class="form-group">
<%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control', placeholder: 'Confirm Password' %>
</div>
<div class="form-group">
<%= f.submit "Sign up", class: "btn btn-primary btn-block btn-lg" %>
</div>
<% end %>
<div class="text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
User Model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :organization, dependent: :destroy
after_create :init_organization
accepts_nested_attributes_for :organization
private
def init_organization
self.create_organization!
end
end
Organization Model
class Organization < ApplicationRecord
has_one :owner, class_name: 'User'
has_many :organizations_users
has_many :users, through: :organizations_users
has_many :clients
after_create :create_tenant
def tenant_name
"#{self.id}"
end
private
def create_tenant
Apartment::Tenant.create(self.tenant_name)
end
end
I know that I need to alter my create method on my devise controller so i generated devise custom controllers with from here
rails generate devise:controllers users
and added the custom sanitizers
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
# def new
# super
# end
# POST /resource
def create
super
end
# GET /resource/edit
# def edit
# super
# end
# PUT /resource
# def update
# super
# end
# DELETE /resource
# def destroy
# super
# end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
# def cancel
# super
# end
# protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:email, organizations: [:name]])
end
# If you have extra params to permit, append them to the sanitizer.
# def configure_account_update_params
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
# end
# The path used after sign up.
# def after_sign_up_path_for(resource)
# super(resource)
# end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end
schema
ActiveRecord::Schema.define(version: 2019_05_12_083957) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
enable_extension "uuid-ossp"
create_table "Organizations_Users", id: false, force: :cascade do |t|
t.uuid "Organization_id", null: false
t.uuid "User_id", null: false
end
create_table "clients", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.uuid "organization_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["organization_id"], name: "index_clients_on_organization_id"
end
create_table "equipment", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.uuid "site_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["site_id"], name: "index_equipment_on_site_id"
end
create_table "organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.uuid "user", null: false
end
create_table "sites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.uuid "client_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["client_id"], name: "index_sites_on_client_id"
end
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "clients", "organizations"
add_foreign_key "equipment", "sites"
add_foreign_key "sites", "clients"
end
when i go through the sign up process i get unknown attribute 'organizations' for User.
Update:
I added two new migration to add reference to user on the organization, and organization to the user.
I did this to create the association for the owner, should i have called the reference owner
class AddUserToOrganization < ActiveRecord::Migration[5.2]
def change
add_reference :organizations, :user, type: :uuid, null: false, index: true, foreign_key: true
end
end
class AddOrganizationToUser < ActiveRecord::Migration[5.2]
def change
add_reference :users, :organization, type: :uuid, null: false, index: true, foreign_key: true
end
end
My Schema now looks like so:
ActiveRecord::Schema.define(version: 2019_05_13_223120) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
enable_extension "uuid-ossp"
create_table "Organizations_Users", id: false, force: :cascade do |t|
t.uuid "Organization_id", null: false
t.uuid "User_id", null: false
end
create_table "clients", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.uuid "organization_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["organization_id"], name: "index_clients_on_organization_id"
end
create_table "equipment", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.uuid "site_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["site_id"], name: "index_equipment_on_site_id"
end
create_table "organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.uuid "user_id", null: false
t.index ["user_id"], name: "index_organizations_on_user_id"
end
create_table "sites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "name"
t.uuid "client_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["client_id"], name: "index_sites_on_client_id"
end
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.uuid "organization_id", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["organization_id"], name: "index_users_on_organization_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "clients", "organizations"
add_foreign_key "equipment", "sites"
add_foreign_key "organizations", "users"
add_foreign_key "sites", "clients"
add_foreign_key "users", "organizations"
end
I followed the direction from to change my user/ registrations controller to :
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
def new
super
#organization = Organization.new
end
# POST /resource
def create
super
end
# GET /resource/edit
# def edit
# super
# end
# PUT /resource
# def update
# super
# end
# DELETE /resource
# def destroy
# super
# end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
# def cancel
# super
# end
# protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:email, organizations: [:name]])
end
# If you have extra params to permit, append them to the sanitizer.
# def configure_account_update_params
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
# end
# The path used after sign up.
# def after_sign_up_path_for(resource)
# super(resource)
# end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end
and my form now
<%= simple_form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => {:class => 'form-horizontal' }) do |f| %>
<%= render partial: 'devise/shared/error_messages', resource: resource %>
<%= f.input :email, autofocus: false, class: 'form-control', placeholder: "Email Address", label: false %>
<%= f.simple_fields_for :organization do |o| %>
<%= o.input :name, placeholder: "Organization Name", warning: "Cant Be Changed", label: false %>
<% end %>
<%= f.input :password, autocomplete: "off", class: 'form-control', placeholder: 'Password', label: false %>
<%= f.input :password_confirmation, autocomplete: "off", class: 'form-control', placeholder: 'Confirm Password', label: false %>
<%= f.button :submit, "Sign up", class: "btn btn-primary btn-block btn-lg" %>
<% end %>
my problem now is that i cannot see the organization name field in the view of the form.
In has_one relation you should use the singular form:
<div class="form-group">
<%= f.simple_fields_for :organization do |o| %>
<%= o.input :name, placeholder: "Organization Name", warning: "Cant Be Changed", label: false %>
<% end %>
</div>
I think the rest of your code should work as is!
Edit: how to instantiate a new org
You indicated that the form is empty, that's because there is no org instance. Do the following in your user controller:
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
GET /resource/sign_up
def new
super
#user.organization = Organization.new
end
# POST /resource
def create
super
end
end
I am unable to insert data in user_preferences table where as I am getting all the attributes in params. I tried inserting value from console by following way since association between User has_one user_preference and UserPreference belongs_to User:
user = User.find(1)
user.user_preferences.title = "MyTitle"
I am getting undefined method "title"
user_preference.rb
class UserPreference < ActiveRecord::Base
belongs_to :user
def self.bgcolor_options
[["Orange", "#FF3300"], ["Green", "#00FF00"], ["Blue", "#0000FF"], ["Pink", "#FF0066"], ["Yellow", "#FFFF00"], ["White", "#FFFFFF"]]
end
def self.font_options
[["Times New Roman", "Times New Roman"], ["Verdana", "Verdana"],["Arial", "Arial"],["sans-serif", "sans-serif"]]
end
end
user_preferences_controller.rb
class UserPreferencesController < ApplicationController
def new
#user_preference = UserPreference.new
end
def create
#user_preference = UserPreference.new(user_pref_params)
#user_preference.save unless user_signed_in?
render 'user_preferences/new'
end
def edit
end
def update
end
private
def user_pref_params
params.require(:user_preference).permit(:title, :bgcolor, :font, :description)
end
end
routes.rb
Rails.application.routes.draw do
resources :user_preferences
post "/user_preferences/new"
devise_for :users
devise_scope :user do
authenticated :user do
root :to => 'user_preferences#new', as: :authenticated_root
end
unauthenticated :user do
root :to => 'devise/registrations#new', as: :unauthenticated_root
end
end
user_preferences/new.html.erb
<%= form_for #user_preference, :url => { :action => "create" } do |u|%>
<div style="background-color:#{current_user.user_preference.bgcolor.nil? ? '#FFFFFF' : current_user.user_preference.bgcolor}">
<p>
<%= u.label :title %><br>
<%= u.text_field :title %>
</p>
<p>
<%= u.label :description %><br>
<%= u.text_field :description %>
</p>
<p> <%= u.label :back_ground_color %><br>
<%= u.select :bgcolor, options_for_select(UserPreference.bgcolor_options) %>
</p>
<p>
<%= u.label :font %><br>
<%= u.select :font, options_for_select(UserPreference.font_options) %>
</p>
<br >
<p>
<%= u.submit %>
</p>
<hr >
<div style="background: #{current_user.user_preferences.bgcolor};"></div>
<div style="background-color:#{current_user.user_preferences.font.nil? ? 'Arial' : current_user.font}">
This is the changes made in background
</div>
</div>
<% end %>
schema.rb
ActiveRecord::Schema.define(version: 20150422034042) do
create_table "user_preferences", force: :cascade do |t|
t.text "title"
t.string "font"
t.text "description"
t.string "bgcolor"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Adding User.rb code
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
has_one :user_preference
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
You're accessing the wrong association on user:
user.user_preferences.title = "MyTitle"
user_preferences is a has_many association, and returns multiple objects. The error itself also mentions this (something like ActiveRecord_Associations_CollectionProxy).
Simply access the has_one association and it will work:
user.user_preference.title = "MyTitle"
for the console part its not a javascript if you want to set to relation you have to use a variabel to assign, and not to use user_preference(s) note the s and its has_one relation and it should be user_preference and in your User Model it should be
# User Model
has_one :user_preference
# Console
user = User.first
preferences = user.user_preference
preferences.title = "MyTitle"
preferences.save
user.reload.preferences.title # should be "MyTitle"
So based on Ryan Bates rails cast (http://railscasts.com/episodes/196-nested-model-form-revised) I am creating a nested form. The part of the rails application that I am trying to get to work ideally does the following:
Let a current user ask a question
Then provide an answer for that question afterwards
I managed to get everything working, except that when I try to submit the form once everything is filled out, I keep getting the following error:
undefined method `meter_id' for nil:NilClass
app/models/answer.rb:13:in `associate_with_meter_id'
app/controllers/questions_controller.rb:13:in `create'
I believe I know what is wrong, but I am not sure how to fix it. The meter_id is returning an undefined value, because it is not being passed the correct value. Here is the method that associates the meter_id (of answers) with the meter_id (of users):
def associate_with_meter_id
self.meter_id = user.meter_id
end
Here is a partial of my user model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:home_size_sf, :meter_id, :avg_monthly_kwh, :discovery_score,
:questions_attributes, :answers_attributes
has_many :data_records, :foreign_key => :meter_id, :primary_key => :meter_id, :class_name => "DataRecord"
has_many :questions
has_many :answers
accepts_nested_attributes_for :questions, :answers
Here is the questions model
class Question < ActiveRecord::Base
attr_accessible :description, :taxonomy, :user_id, :answers_attributes
belongs_to :user
has_many :answers
accepts_nested_attributes_for :answers
validates :description, presence: { :on => :create }
validates :taxonomy, presence: { :on => :create }
def relevance_score
rand
end
end
Here is the questions controller
class QuestionsController < ApplicationController
respond_to :html, :json
def index
#question = current_user.questions.new
#questions = current_user.questions.all
end
def create
#question = current_user.questions.new(params[:question])
if !params[:update_button]
if #question.valid?
if params[:next_button] || !#question.save
render 'index'
elsif !params[:next_button] && params[:submit_button] && #question.save
flash[:success] = "Your question and answer have been saved."
respond_with #question, :location => questions_path
end
else
render 'index'
end
else
render 'index'
end
end
def next
#question = current_user.unanswered.first
#answer = Answer.new(:question => #question, :user => current_user)
respond_to do |format|
format.js
end
end
end
answers model
class Answer < ActiveRecord::Base
attr_accessible :value, :user_id, :meter_id, :question_id
belongs_to :user
belongs_to :question
validates :value, presence: true, :numericality => true
before_save :associate_with_meter_id
def associate_with_meter_id
self.meter_id = user.meter_id **(<-- line 13 from the error message)**
end
end
answers controller
class AnswersController < ApplicationController
respond_to :html, :json
def index
#answers = current_user.answers
end
def create
#answer = current_user.answers.create(params[:answer])
if #answer.save
flash[:notice] = "Thanks for for answer. Please continue with your input...."
respond_with #answer, :location => root_url
end
end
end
database schema
ActiveRecord::Schema.define(:version => 20120210184340) do
create_table "answers", :force => true do |t|
t.integer "meter_id"
t.integer "user_id"
t.integer "question_id"
t.float "value"
t.float "what_if_value"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "data_records", :force => true do |t|
t.datetime "timestamp"
t.float "value"
t.integer "meter_id"
t.string "status_code"
end
create_table "questions", :force => true do |t|
t.string "description"
t.string "taxonomy"
t.string "coeff"
t.float "rsquare", :default => 0.0
t.string "rank"
t.string "responses"
t.string "skips"
t.string "avganswer"
t.float "pval", :default => 0.0
t.float "quality", :default => 0.0
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "setup_constants", :force => true do |t|
t.float "exp_model", :default => 0.0
t.float "exp_pval_const", :default => 0.0
end
create_table "users", :force => true do |t|
t.integer "meter_id"
t.float "home_size_sf", :default => 1000.0
t.text "notifications"
t.float "avg_monthly_kwh"
t.float "ee_score"
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", :default => 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
end
Note:
In the answers model (above), the line:
self.meter_id = user.meter_id
Associates the meter_id of the answer model with the meter_id of the user model. I believe this is where the issue is. I tried changing the above line to:
self.meter_id = 2
And then everything worked fine, so it's obvious that the user.meter_id is undefined, so I'm not sure how to pass that value through the nested form? I tried using a hidden field but with no luck (the following is a nested fields_for :answers, within a form_for #questions):
<fieldset>
<%= f.label "Yes" %>
<%= f.radio_button :value, 1 %>
<%= f.label "No" %>
<%= f.radio_button :value, 0 %>
<%= f.hidden_field :user_id %>
<%= f.hidden_field :question_id %>
<%= f.hidden_field :meter_id %>
</fieldset>
First of all, you don't want to pass the current user in the view for security reasons. Instead, you want to do it from the controller.
A starting point (assuming you have current_user):
#answer = current_user.answers.create(params[:answer].merge(:user => current_user))
From there, it's up to you how to pass the user to the answer model. You could, however, use this:
self.meter_id = question.user.meter_id
Assuming that's appropriate.