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
Related
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
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.
Have a recipient and sender, both of the same class(Message) for a messaging system in rails. Want to set the params for both i.e. if user creates a message sender by default is the user_id and recipient will be the contact selected from the users contact list.
Currently the database is only receiving a user_id to the recipient_id column which is wrong and should be to sender_id column. Sender_id receives nothing.
After reading, some say not to amend the params as this is bad practice. So set a hidden field in the message view (like the body and title) yet this isn't pushing in to the database.
Two questions, is this process an appropriate rails practice? (ask this as new to rails) If not: can you advise another path or direction? If so: any ideas/thoughts why this isn't saving in to the database?
user model
class User < ActiveRecord::Base
has_many :messages, class_name: "Message", foreign_key: "recipient_id"
has_many :sent_messages, class_name: "Message", foreign_key: "sender_id"
has_many :contacts, dependent: :destroy
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates_presence_of :firstname, allow_blank: false
validates_presence_of :surname, allow_blank: false
end
message model
class Message < ActiveRecord::Base
belongs_to :sender, class_name: "User", foreign_key: "sender_id"
belongs_to :recipient, class_name: "User", foreign_key: "recipient_id"
validates_presence_of :body, :title
end
Messages controller
class MessagesController < ApplicationController
before_action :message, only: [:show]
before_action :authenticate_user!
def index
#messages = current_user.messages
end
def new
#message = Message.new
end
def create
current_user.messages.create(message_params)
redirect_to '/messages'
end
def show
end
private
def message_params
params.require(:message).permit(:title, :body, :sender_id, :recipient_id)
end
def message
#message = Message.find(params[:id])
end
end
message/new view
<%= form_for #message do |f| %>
<%= hidden_field_tag :sender_id, current_user.id %>
<%= f.text_field :title %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
schema
ActiveRecord::Schema.define(version: 20160517131719) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "contacts", force: :cascade do |t|
t.string "firstname"
t.string "surname"
t.string "email"
t.integer "phone"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.integer "user_id"
end
add_index "contacts", ["user_id"], name: "index_contacts_on_user_id", using: :btree
create_table "messages", force: :cascade do |t|
t.string "title"
t.text "body"
t.integer "sender_id"
t.integer "recipient_id"
t.datetime "created_at"
t.datetime "updated_at"
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.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "firstname"
t.string "surname"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_foreign_key "contacts", "users"
end
Try changing your form to this:
<%= form_for #message do |f| %>
<%= f.hidden_field :sender_id, value: current_user.id %>
<%= f.text_field :title %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
Currently the database is only receiving a user_id to the recipient_id
column which is wrong and should be to sender_id column.
In your create action, you have current_user.messages.create(message_params). This creates a message record in the DB with the foreign key's(i.e, recipient_id in your case) value with the parent's(user) id. This is the reason, the recipient_id gets the value of user's id.
Sender_id receives nothing.
This is because the hidden_field set for sender_id is not wrapped with the form builder instance. You need to change
<%= hidden_field_tag :sender_id, current_user.id %>
to
<%= f.hidden_field :sender_id, current_user.id %>
currently I am creating a snapchat like application by using Rails. I get the error:
undefined method `sender_id=' for nil:NilClass
while I am trying to submit the new message form, I don't really know where I make the mistake. Moreover, I am not sure about the logic of the create action in my messages_controller, basically I just want the sender sends a message to the people in their friendlist by choosing one or more recipients at the same time(of course, the recipients must be in their friendlist, and I have already accomplished this feature). However, because I still haven't figured out how can I pass the the params recipient_id to the form(I am using simple form gem and devise gem by the way), so I hardcoded the recipient_id in my create action. So,all of suggestions/advices are welcome.
Here are my files:
messages_controller.rb:
class MessagesController < ApplicationController
def new
#message = Message.new
end
def create
#message.sender_id = current_user
#message.recipient_id = current_user.friendships.friend_id
#message = Message.new(message_params)
if #message.save?
flash[:success] = 'Message sent successfully'
redirect_to welcome_profile_path
else
render 'new'
end
end
private
def message_params
params.require(:message).permit(:body, :sender_id, :recipient_id, :user_id)
end
end
messages/new.html.erb:
<h1>Create New Message</h1>
<%= simple_form_for #message do |f| %>
<%= f.input :body %>
<%#= f.association :user, :as => :hidden, :input_html => { :value => current_user.id }, :include_blank => false %>
<%= f.button :submit, "Send Message", class: "btn btn-secondary" %>
<% end %>
message.rb:
class Message < ActiveRecord::Base
belongs_to :user, foreign_key: :recipient_id
belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'
validates_presence_of :body
end
user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :username,
:presence => true,
:uniqueness => {
:case_sensitive => false
}
validate :validate_username
def validate_username
if User.where(email: username).exists?
errors.add(:username, :invalid)
end
end
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
has_many :messages, dependent: :destroy
end
schema.rb:
ActiveRecord::Schema.define(version: 20160316170009) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "messages", force: :cascade do |t|
t.text "body"
t.boolean "read"
t.integer "sender_id"
t.integer "recipient_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "messages", ["user_id"], name: "index_messages_on_user_id", using: :btree
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.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", unique: true, using: :btree
add_foreign_key "messages", "users"
end
That error is because you don't have #message defined before setting #message.sender_id in your create action. Move #message = Message.new(message_params) to the top of the create action before setting #message.sender_id as follows:
class MessagesController < ApplicationController
...
def create
#message = Message.new(message_params)
#message.sender_id = current_user
#message.recipient_id = current_user.friendships.friend_id
if #message.save?
flash[:success] = 'Message sent successfully'
redirect_to welcome_profile_path
else
render 'new'
end
end
private
def message_params
params.require(:message).permit(:body, :sender_id, :recipient_id, :user_id)
end
end
The second part of the question - current_user is available in your controllers, so you don't need a hidden field for that. You can call #message.sender_id = current_user.id in your create action as you currently have. Next, to set #message.recipient_id, you could update your new.html.erb as follows:
# new.html.erb
<h1>Create New Message</h1>
<%= simple_form_for #message do |f| %>
<%= f.input :body %>
<%# Updated the following line. You could chose to create a hidden field here, or however you wish to implement this in your view as long as you specify `recipient` as the association or `recipient_id` as the field. %>
<%= f.association :recipient, :include_blank => false %>
<%= f.button :submit, "Send Message", class: "btn btn-secondary" %>
<% end %>
With the above setup your create action could be updated to:
# app/controllers/messages_controller.rb
def create
#message = Message.new(message_params)
#message.sender_id = current_user
if #message.save?
flash[:success] = 'Message sent successfully'
redirect_to welcome_profile_path
else
render 'new'
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"