I’m trying to edit/update a model record using simple_form, but the form is not going to directly change a model field. Instead, I offer a couple of check_box_tag fields that tell update what fields need changed. As a result, update is not receiving a params[:device] hash that I can use to update the attributes. I am attempting to create this hash, but am getting ForbiddenAttributesError when I issue the #device.update_attributes(params[:device]).
I believe my strong parameters list is correct. If I allow one model field (name) to be processed in the edit view, I receive the expected params[:device] hash and everything works. If I disable that field, because I don’t want it to be changed, then I need to create that hash myself and I receive the error. When I look at the hash I created, it looks to me as equivalent to the one passed by the view. I don’t understand why it is failing.
Environment is Ruby 2.0.0, Rails 4.1 on Windows 8.1 with RubyMine 6.3.
The form is: <... needs correct formatting once working ...>
<%= simple_form_for #device do |f| %>
<legend><%= controller.action_name.capitalize %> Device:</legend>
<%= f.input :name, disabled: true %>
<%= check_box_tag(:is_admin, 0, #device.admin?) %>
<%= label_tag(:is_admin, "Make admin?") %>
<%= check_box_tag(:chg_pwd) %>
<%= label_tag(:chg_pwd, "Change password?") %>
<%= f.button :submit %>
<% end %>
The params[:device] that I receive when I sent f.input :name, disabled: false and allow the view to generate params[:device] is:
ActionController::Parameters (3 element(s))
"{"name"=>"D105", "password"=>"D105Dvgr", "password_confirmation"=>"D105Dvgr"}"
And, everything works.
The params[:device] that I create is:
ActionController::Parameters (3 element(s))
"{"name"=>"D106", "password"=>"D106VdAd", "password_confirmation"=>"D106VdAd"}"
And, I receive Forbidden Attribute Error, even though I see no difference between the two.
The update is: <... Code needs refactored, once it is working...>
class DevicesController < ApplicationController
before_filter :authenticate_device!
... other methods removed here ...
def edit
#device = Device.find(params[:id])
# my_page = render_to_string controller: 'devices', action: 'edit', layout: "application"
end
def update
authorize! :update, #device, :message => 'Not authorized as an administrator.'
#device = Device.find(params[:id])
pwd_msg = ""
if params[:chg_pwd]
pwd_gen = #device.device + SecureRandom.urlsafe_base64(15).tr('lIO0=_\-', 'sxyzEUM').first(4)
params[:device] = {name: #device.name} if params[:device].nil?
params[:device][:password] = pwd_gen
params[:device][:password_confirmation] = pwd_gen
pwd_msg = ", new password is #{pwd_gen}"
end
if #device.update_attributes(params[:device])
params[:is_admin] ? #device.add_role(:admin) : #device.remove_role(:admin)
flash[:notice] = ["Device updated" + pwd_msg]
redirect_to devices_path
else
#device.errors.messages.each do |key, value|
flash[:alert] = ["Unable to update device"]
#device.errors.messages.each do |key, value|
flash[:alert] << key.to_s.capitalize + " " + value[0]
end
end
redirect_to devices_path
end
end
private
def device_params
params.require(:device).permit(:device, :name, :email, :password, :password_confirmation, :encrypted_password, :salt, :role_ids, :is_admin, :chg_pwd) # TODO minimize when update is working
end
end
The model is:
class Device < ActiveRecord::Base
rolify
devise :database_authenticatable, :rememberable, :trackable, :validatable
validates :device,
presence: true,
length: {minimum: 4 },
uniqueness: {case_sensitive: false }
validates :name,
presence: true
def remember_me
true unless self.admin?
end
def admin
self.add_role :admin
end
def not_admin
self.remove_role :admin
end
def admin?
self.has_role? :admin
end
def device?
self.has_role? :device
end
def vip?
self.has_role? :vip
end
def login=(login)
#login = login
end
def login
#login || self.device || self.email
end
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login) # Note one equal sign. Strange but true.
where(conditions).where(["lower(device) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
end
NEW INFORMATION: I neglected to provide information I have in the ApplicationController. This fix from Anton Trapp handles strong parameters for gems that aren't yet fully Rails 4 compatible:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
I have found that using the proposed solution of:
#device.update_attributes(device_params)
does not work if a model field is updated. The result is "param not found: device". It does work if no model field is update. So, the whole issue begs the question of what is truly wrong.
In DevicesController#update action, change
#device.update_attributes(params[:device])
To
#device.update_attributes(device_params)
As you are using Rails 4.1, you need to whitelist the attributes which you would like to be inserted/updated in database. As you passed the attributes directly to update_attributes method without permitting them you received ActiveModel::ForbiddenAttributesError
UPDATE
To resolve param not found: device:
def device_params
if params[:device]
params.require(:device).permit(:device, :name, :email, :password, :password_confirmation, :encrypted_password, :salt, :role_ids, :is_admin, :chg_pwd) # TODO minimize when update is working
end
end
The fix was to add the fields as attr_accessor to the model, but not the database, so that it could be used correctly within the form.
attr_accessor :is_admin, :chg_pwd
And then modify the view to:
<%= simple_form_for #device do |f| %>
<legend><%= controller.action_name.capitalize %> Device:</legend>
<%= f.input :name, disabled: true %>
<%= f.input :is_admin, as: :boolean, checked_value: true, unchecked_value: false %>
<%= f.input :chg_pwd, as: :boolean, checked_value: true, unchecked_value: false %>
<%= f.button :submit %>
<% end %>
Then, due to the Application Controller code from Anton Trapp:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
I was able to update the fields in Device Controller as follows:
#device.update_attributes(params[:device])
Related
I am trying to render a new view on an already existing user show page. When trying to submit this view, I get param is missing or the value is empty: user. To be clear this is a skill partial being rendered on the user show page. For some reason it is using the strong params in my User Controller.
The code:
show.html.erb for user
<h4>Create a Skill</h4>
<%= render partial: "skills/form" %>
userscontroller.rb
def show
#user = User.find(params[:id])
#skill = Skill.new
#skills = #user.skills.all
end
private
def user_params
params.require(:user).permit(:username, :password, :avatar_url, :email, :about, :cover_letter, :city, :state)
end
end
SkillsController.rb
class SkillsController < ActionController::Base
def new
user = User.find(params[:user_id])
#skill = user.skills.new
end
def create
user = User.find(params[:user_id])
#skill = user.skills.new(skill_params)
if #skill.save
flash[:message] = "#{#skill.name} skill has been created!"
redirect_to user_path(user)
else
redirect_to new_user_skill_path
end
end
private
def skill_params
params.require(:skill).permit(:name, :level)
end
end
Also, I have Namespaced skills within user. No authentication in place yet.
EDIT: #nickm, here are the contents of skills/_form
<%= simple_form_for(Skill.new, :url => { :action => "create" }) do |f| %>
<%= f.input :name, label: 'Skill Name ' %>
<%= f.input :level, label: "Skill Level ", collection: ["Beginner","Proficient", "Intermediate", "Advanced", "Expert"], include_blank: false, include_hidden: false %>
<%= f.submit %>
<% end %>
The problem is that you aren't passing a user_id through the form. You would have to either add a form input:
<%= f.hidden_field :user_id, some_value %>
Then find the user:
user = User.find(params[:skill][:user_id])
and then make skill_params
def skill_params
params.require(:skill).permit(:name, :level, user_id)
end
Or optionally, set the value of user_id in your controller action. Not sure how you're going to pass that value since you haven't built any authentication yet. If you were using something like devise you could do
current_user.skills.new(skills_params)
...in your create action.
Model User:
class User < ApplicationRecord
has_one :address, foreign_key: :user_id
accepts_nested_attributes_for :address
end
Model Address
class Address < ApplicationRecord
belongs_to :user, optional: true
end
Controller User, everything happen here
class UsersController < ApplicationController
def home # method which I use to display form
#user = User.find_by :id => session[:id]
end
def update # method for updating data
#user = User.find(session[:id])
if #user.update(user_params)
flash[:notice] = "Update successfully"
redirect_to home_path
else
flash[:error] = "Can not update"
redirect_to home_path
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, images_attributes: [:image_link, :image_description], address_attributes: [:city, :street, :home_number, :post_code, :country])
end
end
Updating form:
<%= form_for #user, :html => { :id => "update-form", :class => "update-form"} do |f| %>
<%= f.text_field :name %>
<%= f.text_field :email %>
<%= f.fields_for :address do |a| %>
<%= a.text_field :city %>
<%= a.text_field :street %>
<%= a.number_field :home_number %>
<%= a.text_field :post_code %>
<%= a.text_field :country %>
<% end %>
<%= f.submit %>
<% end %>
When I submitting my form, it shows me everything is fine, I mean "Update successfully", but in database its looks like new record is added to address table, but user table is updated properly. Can someone give me explanation why? I am looking answers in google but nothing helps me.
When I submitting my form, it shows me everything is fine, I mean
"Update successfully", but in database its looks like new record is
added to address table, but user table is updated properly. Can
someone give me explanation why?
This is due to the nature of strong params. It expects :id to be permitted for the nested_attributes to update properly, else a new record is created instead. Permit the :id and you are good to go.
def user_params
params.require(:user).permit(:name, :email, :password, images_attributes: [:id, :image_link, :image_description], address_attributes: [:id, :city, :street, :home_number, :post_code, :country])
end
try below code in your controller:
class UsersController < ApplicationController
def home # method which I use to display form
#user = User.find_by :id => session[:id]
end
def update # method for updating data
#user = User.find(session[:id])
if #user.update(user_params)
flash[:notice] = "Update successfully"
redirect_to home_path
else
flash[:error] = "Can not update"
redirect_to home_path
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, images_attributes: [:image_link, :image_description], address_attributes: [:id, :city, :street, :home_number, :post_code, :country])
end
end
So I'm creating a simple app for myself and a small group of people. I would like to restrict access to people that I hand-generate codes for by typing them in myself.
User sign up, log in, logout works, but I don't just want anyone to be able to be register.
TL;DR
User can sign up but how do I go about setting up a hand-generated code
Should be some way to invalidate that code after sign up and see who
the code is associated with
How can I do this in rails?
I know the user_model would have to add some sort of field to it, the view/form for it as well, and it would have to be validated (by the controller?). Just stuck.
My thought process is as follows (what i've thought so far)
All the invite codes should be kept as an array in a file in the
rails app?
I will have to add a migration that adds invite_code to the model/db/view form
The controller should validate the presence of the invite code in the view form?
user/new.html.erb
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Retype Password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create My Account" %>
<% end %>
users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome!"
redirect_to #user
# Handle a successful save.
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
user.rb model
class User < ActiveRecord::Base
# links this to the question.rb model
has_many :questions, dependent: :destroy
attr_accessor :remember_token
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# returns a random token
def User.new_token
SecureRandom.urlsafe_base64
end
# remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# returns true if the given token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end
Well, In that case, you can have a model say Token created as below:
def self.up
create_table :tokens do |t
t.string :code, null: false
t.boolean :used, default: false
t.timestamps
end
end
And a migration in your User table for token_id
You can generate new code like, Token.generate_new_code
def self.generate_new_code
token = Token.new(code: Digest::SHA1.hexdigest Time.now.to_s)
token.code if token.save
end
And you can understand the rest i.e. accept code from registration form, validate for unused code and set it true and token_id if the registration is successful.
I use sorcery for user authentication in a rails 4.1 application. Everything works fine. But when I try to update specific attributes of the user model (which is authenticated by sorcery), I get an error that the password is blank and is too short.
Here's a snippet from the console
> user = User.last
=> # I get the user
> user.update(about_me: "I'm a user")
=> false
> user.update(about_me: "I'm a user", password: "secret")
=> true
Here's my model code
app/models/user.rb
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, presence: true, length: { minimum: 6 }
.....
end
My controller code
app/controllers/users_controller.rb
class UsersController < ApplicationController
.....
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
flash[:notice] = "Profile successfully updated"
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:username, :name, :email, :password, :about_me)
end
end
And my update form
app/views/users/edit.html.erb
<%= form_for #user, method: :put do |f| %>
<% if #user.errors.any? %>
<div class="alert">
<p><%= pluralize(#user.errors.count, 'error') %></p>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.text_field :username, placeholder: 'Username' %>
<%= f.text_field :name, placeholder: 'Name' %>
<%= f.email_field :email, placeholder: 'Email' %>
<%= f.text_area :about_me, placeholder: 'About me' %>
<%= f.password_field :password, placeholder: 'Password' %>
<%= f.submit 'Save Changes', class: 'button' %>
<% end %>
If I remove the password field from the form, I get errors about the password being blank and about it's length.
Is this something to do with sorcery or is it something I'm missing with rails itself?
Is there a better way to update let's say only the email field without affecting anything else?
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, presence: true, length: { minimum: 6 }, if: :new_user?
private
def new_user?
new_record?
end
end
The validation will be checked only if it's a new_record, for which we have added our own private validation method new_user?. This function will return true during your normal signups/registrations. Hence, at those signups only the password validation will be needed.
During the edit, off course the user will be an existing user / new_record? will return false. Hence the validation for password will be skipped.
2nd way:
class User < ActiveRecord::Base
attr_accessor :skip_password
validates :password, presence: true, length: { minimum: 6 }, unless: :skip_password
end
#users_controller.rb
def update
#user = User.find(params[:id])
#user.skip_password = true
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
Here we have added our own custom attr_accessor skip_password. If the skip_password value is set to true, then during edit/update the password validation will be skipped.
I hope both of those ways will help you :)
If someone looks for this topic in future, it is possible to use changes map of ActiveRecord model:
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, presence: true, length: { minimum: 6 }, if: -> {new_record? || changes[:crypted_password]}
.....
end
where :crypted_password is the value of sorcery_config.crypted_password_attribute_name.
Also currently such condition of validates pointed in Simple Password Authentication sorcery wiki article.
I have
Model
class User < ActiveRecord::Base
attr_accessible :email
validates :email,
presence: true
serialize :data, ActiveRecord::Coders::Hstore
%w[zipcode first_name].each do |key|
attr_accessible key
define_method(key) do
data && data[key]
end
define_method("#{key}=") do |value|
self.data = (data || {}).merge(key => value)
end
end
end
Controller
class UsersController < ApplicationController
def create
#user = User.find_or_initialize_by_email(params[:user][:email])
if #user.update_attributes(params[:user])
redirect_to :back, notice: "Thanks for sign up!"
else
render "pages/home"
end
end
end
View with client side validation
<%= simple_form_for User.new, validate: true do |f| %>
<%= f.input :email %>
<%= f.input :first_name %>
<%= f.input :zipcode %>
<%= f.button :submit, 'Sign up' %>
<% end %>
First question: I would like to update existing user record but not if param is "" or " ", how to achieve this?
Second question: Should I use create action to do that? Maybe update will be more clear. But this form also create an user object.
Third question: Is any chance to add uniqueness validation to email attribute? Right now my client-side validation do not allow do that.
First: To disallow updating a field with empty strings, set allow_blank: false you could also do allow_nil: false. Add these to your validations in your model.
Second: N/A
Third: Simply add uniqueness: true to your email validation in your model.
Read more here