I'm following this video tutorial and learning how to create authentication from scratch:
http://www.youtube.com/watch?v=O5RDisWr_7Q
Here is my migration file for User:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.string :password_hash
t.string :password_salt
t.timestamps
end
end
end
And my controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:users])
if #user.save
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
end
end
And finally my Model:
class User < ActiveRecord::Base
attr_accessible :email, :password_hash, :password_salt
before_save :encrypt_password
validates_confirmation_of :password
validates :password, presence: true
validates :email, presence: true
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
Now, I think I know why this error is firing; obviously the #user.save call is trying to save the value in password to the password field in the User table, but that field doesn't exist in the database. In the video he mentions that to fix this bug I should just add: attr_accessible :password to my model and it should work, but I get the following error:
NoMethodError in UsersController#create
undefined method `password' for #
app/controllers/users_controller.rb:8:in `create'
Any suggestions? I just would like to take advantage of the validations that come with using a strongly typed model instead of loose html fields.
You have attr_accessible :password_hash, :password_salt, but I think it should be attr_accessible :password together with attr_accessor :password since you need a virtual attribute password on which you work in your encrypt_password method. So:
class User < ActiveRecord::Base
attr_accessible :email, :password
attr_accessor :password
end
attr_accessor creates the virtual attribute which is not available as a database field (therefore virtual).
attr_accessible is a security mechanism to white-list attributes which are allowed to be set through mass-assignment like you do with User.new(params[:users])
Related
I am using the following Gems:
'paperclip'
'aws-sdk', '~> 2.3'
I would like to save images to S3, but am unable to get them to save.
model/user.rb
class User < ApplicationRecord
# This method associates the attribute ":avatar" with a file attachment
has_attached_file :avatar, styles: {
thumb: '100x100>',
square: '200x200#',
medium: '300x300>'
}
# Validate the attached image is image/jpg, image/png, etc
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
end
migration
class AddAvatarToUsers < ActiveRecord::Migration[5.1]
def self.up
add_attachment :users, :avatar
end
def self.down
remove_attachment :users, :avatar
end
end
controllers/users_controller.rb
class UsersController < ApplicationController
def edit
#user = User.find_by(id: params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
flash[:notice] = "Edit successfully"
redirect_to("/users/#{#user.id}")
end
end
private
def user_params
params.require(:user).permit(:avatar, :name, :email, :phone_number, :description, :college_name)
end
end
Why is the image avatar not being stored in the database?
Should I add any database columns? What should I do? I would appreciate any input to help me solve this problem.
Paperclip must be configured to use S3 to store the objects (images). It will store metadata associated with these in the database, but not the images.
See Paperclip Documentation
You will also need to set an access policy for your S3 bucket, and define on the User model how they are to be addressed from S3.
I'm following this tutorial on how to nest other Models in my Devise registration form. I'm getting an error in my New controller:
'NoMethodError in Users::RegistrationsController#new undefined method `languages_user=' for #'.
Languages_Users is a join table, and I'm wondering if this is the reason it isn't working, but I don't understand what the solution is. I want to add 2 different records of Languages_Users when the user signs up.
Models:
User.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :role
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100#" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
validates_presence_of :first_name, :last_name, :location, :nationality, :bio
before_save :assign_role
def assign_role
self.role = Role.find_by name: "user" if self.role.nil?
end
has_many :languages_users
has_many :languages, :through => :languages_users
accepts_nested_attributes_for :languages_users
Language.rb
class Language < ActiveRecord::Base
has_many :languages_users
has_many :users, :through => :languages_users
end
Langauges_user.rb
class LanguagesUser < ActiveRecord::Base
belongs_to :user
belongs_to :language
validates_presence_of :user_id, :language_id, :level
validates :user_id, :uniqueness => {:scope => :language_id, :message => 'can only delcare each language once. Please change the level of the language in Manage Languages.'}
end
Controllers:
registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
def new
build_resource({})
self.resource.languages_user = LanguagesUser.new[sign_up_params]
respond_with self.resource
end
def create
#user_id = current_user.id
super
end
def sign_up_params
allow = [:email, :password, :password_confirmation, [languages_user_attributes: [:language_id, :user_id, :level]]]
end
end
Relevant sections of User's new.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<%= f.fields_for :langauges_user do |lu| %>
<%#= lu.text_field :language_id %>
<%= lu.collection_select(:language_id, Language.order('language ASC').all, :id, :language) %><br>
<%= lu.hidden_field languages_user[level], value: 1 %>
<% end %>
<%= f.submit "Sign up" %>
<% end %>
Relevant routes
Rails.application.routes.draw do
resources :languages_users
devise_for :users, controllers: { registrations: "users/registrations" }
resources :users
get 'languages_users/:id/sign_up', to: 'languages_users#sign_up', as: 'sign_up'
end
I'm still learning - so please let me know if you need to see anything else. Thanks!
I'm not that up to speed on Devise as I only recently started using it myself, but if I understand correctly it's not a Devise related problem - just harder to get a fix on because of Devise's self.resource abstraction.
You've deviated from your tutorial in an important respect: in the tutorial a User creates a Company, but the Company has_many :users. In your case the User creates a LanguagesUser, but here, the User has_many :languages_users. This means new syntax. This line, that's causing your error currently:
self.resource.languages_user = LanguagesUser.new[sign_up_params]
Needs to be along the lines of:
self.resource.languages_users.build(sign_up_params) #but see below re sign_up_params
Or if you want to save the associated resource right off the bat (I assume not, since you're not at the moment), you can use create or create! instead of build.
Aside
You may run into different trouble with your sign_up_params method, which also appears to have deviated from the tutorial - it doesn't actually use the allow array to whitelist any params, at least as written in your question. In any case, note they didn't use it when instantiating the Company, so it may not be fit for purpose when building your LanguagesUser, either.
A simple call to sign_up_params[:languages_user_attributes] should get you over the line, once you've fixed the sign_up_params method. Or you can set the nested object up with its own params whitelist.
I am new to ROR and using active admin for generating a small app. I have belongs to and has many relation ships defined in the models. My app is working good for the pages where belongs to does not apply. In the page where belongs to comes into action I have drop down created by Active Admin, however when I select the values from drop down and save the form, values selected from drop down is not getting saved. below are my model controller and active admin pages codes. Please help me fix this issue.
ActiveAdmin.register Componentdetail do
models:
class Preference < ActiveRecord::Base
def permitted_params
params.permit preference: [:prefname, :prefdisplay, :helptext]
end
def preference_params
params.require(:preference).permit(:prefname, :prefdisplay, :helptext)
end
attr_accessible :prefname, :prefdisplay, :helptext
has_many :prefcomprelation
#accepts_nested_attributes_for :prefcomprelation, :allow_destroy => true
def display_name
return self.prefname
end
def input
self.id
end
end
class Prefcomprelation < ActiveRecord::Base
def permitted_params
params.permit prefcomprelation: [:comment, :preference, :componentdetail]
end
def prefcomprelation_params
params.require(:prefcomprelation).permit(:comment, :preference, :componentdetail)
end
# def to_s
# description
# end
attr_accessible :comment, :preference, :componentdetail
#acts_as_list column: :preference, :scope => :preference
belongs_to :preference
#acts_as_list column: :componentdetail, :scope => :componentdetail
belongs_to :componentdetail
validates :comment, presence: true
validates :preference, presence: true
validates :componentdetail, presence: true
#controller do
def new
#prefcomprelation = Prefcomprelation.new
end
def update
#prefcomprelation = Prefcomprelation.new(precomprelation_params)
#prefcomprelation.save
end
def create
#prefcomprelation = Prefcomprelation.new(precomprelation_params)
#prefcomprelation.save
end
#end
end
class Message < ActiveRecord::Base
def permitted_params
params.permit message: [:message, :componentdetail]
end
def message_params
params.require(:message).permit(:message, :componentdetail)
end
attr_accessible :message, :componentdetail
belongs_to :componentdetail
end
class Componentdetail < ActiveRecord::Base
def permitted_params
params.permit componentdetail: [:compname, :compdisplay, :prefcomprelation]
end
def componentdetail_params
params.require(:componentdetail).permit(:compname, :compdisplay, :prefcomprelation)
end
attr_accessible :compname, :compdisplay, :prefcomprelation
has_many :prefcomprelation
#accepts_nested_attributes_for :prefcomprelation, :allow_destroy => true
has_many :message
#accepts_nested_attributes_for :message, :allow_destroy => true
def name
return self.compname
end
end
controllers
class PreferenceController < ApplicationController
def index
#preference = Preference.all
end
def show
end
end
class PrefcomprelationController < ApplicationController
def index
#prefcomprelation = Prefcomprelation.all
end
def create
#prefcomprelation = Prefcomprelation.new(precomprelation_params)
#prefcomprelation.save
end
def new
#prefcomprelation = Prefcomprelation.new(precomprelation_params)
#prefcomprelation.save
end
def show
end
end
class MessageController < ApplicationController
def index
#message = Message.all
end
def create
#message = Message.new(message_params)
#message.save
end
def show
end
end
class ComponentdetailController < ApplicationController
def index
#componentdetail = Componentdetail.all
end
def show
end
end
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
Admin Pages
ActiveAdmin.register Preference do
end
ActiveAdmin.register Prefcomprelation do
index do
selectable_column
id_column
column :comment
column :preference
column :created_at
actions
end
filter :comment
filter :preference
filter :componentdetail
form do |f|
f.inputs "Prefcomprelation" do
f.input :comment
#f.inputs do
f.input :preference
f.input :componentdetail
end
f.actions
# f.input :preference
end
end
ActiveAdmin.register Message do
index do
column :message
column :created_at
actions
end
form :html => { :enctype => "multipart/form-data" } do |f|
f.inputs 'Details' do
f.input :message
f.input :componentdetail
end
f.actions
end
end
ActiveAdmin.register Componentdetail do
end
issue comes when I try to create new Prefcomprelation and select preference and componentdetails value from the dropdown menu and try to save.
same happens to the message page.
form do |f|
f.inputs "Product Details" do
f.input :name, :as => :select,
:collection => Product.all.map{|u| ["#{u.product_name}", u.product_name]}
f.input :description
f.input :image
f.input :quantity
end
f.actions
end
Try to add drop down like above and save select value in active admin.
The idea is simple. I require a nested attribute tag for user registration. tag requires a user_id.
the view
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {role: :form}) do |f| %>
<%= f.fields_for :tags, resource.tags.build do |a| %>
<%= a.text_field :tagname %>
<% end %>
<% end %>
tag migration
class CreateTags < ActiveRecord::Migration
def change
create_table :tags do |t|
t.string :tagname
t.references :user, index: true
t.timestamps
end
add_index :tags, :tagname, unique: true
end
end
the tag model
class Tag < ActiveRecord::Base
belongs_to :user
validates_presence_of :user_id
validates_uniqueness_of :tagname
end
the user model
class User < ActiveRecord::Base
has_many :tags, autosave: true, dependent: :destroy
accepts_nested_attributes_for :tags
end
strong parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(
:tags_attributes => [:id, :user_id, :tagname]
) }
The save feature is untouched at the moment. The form passes the nested attribute :tagname just fine. But I've been unable to get the "would be" user_id from resource.
I've already looked for hours online for any answer to this. None has appeared, but the idea that the nested attribute should be saved after the initial user object is saved sounds like a workable solution. But then it's no longer handled as a nested attribute.
Help is appreciated! Thanks!
Solution
Alright I solved it. I bastardised my devise registrations controller a bit, but it works.
First I removed user_id verification from my tag model.
class Tag < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :tagname
end
I then proceeded with modifying the devise registration controller.
def create
build_resource(sign_up_params)
# added code begins here
#tag = nil
#tag.define_singleton_method(:valid?) {false}
if params["user"].has_key? "tags_attributes"
#tag = Tag.new(params["user"].delete("tags_attributes").values.first)
if #tag.valid?
resource_saved = resource.save # original line of code
else
resource.errors.add(*#tag.errors.first)
flash.delete :tagname_error
set_flash_message :alert, :tag_taken if is_flashing_format?
end
else
set_flash_message :alert, :need_tag if is_flashing_format?
end
# end added code
yield resource if block_given?
if resource_saved
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
# This is where current_user has been created and resource.id == current_user.id
# begin added code
#tag.user_id= resource.id
# end added code
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
#validatable = devise_mapping.validatable?
if #validatable
#minimum_password_length = resource_class.password_length.min
end
respond_with resource
end
end
I've defined :tag_taken and :need_tag in my devise locales file for translations of the error string.
And everything works!
For the record this is in addition to the existing code in the question.
My project:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :email,
:password,
:password_confirmation,
:first_name,
:last_name,
:birth_date,
:residence,
:user_role,
:show_email,
:avatar
as_attached_file :avatar,
:default_url => '/images/system/user_avatars/default/default_avatar.png',
:url => "/public/images/system/user_avatars/:id_:style.:extension",
:path => "/public/system/user_avatars/:id_:style.:extension"
def update_profile(user_id, params) #params has :category and :user params
#user = User.find(user_id)
#user.update_attributes(params[:user])
return params[:category]
end
end
So, from my controller i call this method and i get no error. Paperclip shows attachment saved. The database is updated, but the image file is not saved. I have an registration made from scratch, so that's why i have the "attr_accessor :password"
I checked:
Have :multipart => true in form
Have attr_accessible :avatar in user model
Can any one give me some lead, cos i cant figure, why paperclip dosnt save the file.
Set attr_accessible :avatar_file_name as well, and you also need a paperclip.rb initializer:
require "paperclip"
Paperclip.options[:command_path] = "/ImageMagick"
And, of course, have ImageMagick installed.