I'm in the process of learning Rails by following Agile Web Development with Rails 4. I am at chapter 9, just finnished unit testing. In order to better apprehend what I'm reading, I am building step by step my own application, along with the one I have to build following the book. The book goes through building an e-shop, my application is a patients record keeping application.
I am in the process of writing tests. I generated two scaffolds Doctors and Patients. For Patients everything went fine as it should be, since I'm actually doing everything in a parallel way with the book.
For Doctors though, I had a problem where [params] in the app/controller/doctors_controller.rb where the payload was passed as String instead of a Hash. More specifically, the error I get is:
DoctorsControllerTest
ERROR (0:00:00.094) test_should_create_doctor
undefined method `permit' for "625417172":String
# app/controllers/doctors_controller.rb:72:in `doctor_params'
app/controllers/doctors_controller.rb:27:in `create'
[...]
I double checked the code and everything is similar to Patients controller, but I have no problems there! After doing some online research I managed to bypass this error using attributes:
[...]
test "should create doctor" do
assert_difference('Doctor.count') do
post :create, doctor: #doctor.attributes # instead of #doctor
end
assert_redirected_to doctor_path(assigns(:doctor))
end
[...]
But then when I run the rake test I the following error:
DoctorsControllerTest
FAIL (0:00:00.182) test_should_create_doctor
"Doctor.count" didn't change by 1.
Expected: 3
Actual: 2
I'd be glad if someone could give me some hints on how to get PASS this test by adressing possibly the 1st reported error - which in IMHO it's the real source of my problems - or at least the second one so I can continue my project.
EDIT: Sharing requested code:
My model for Doctor is:
class Doctor < ActiveRecord::Base
validates :name, presence: {message: "Συμπληρώστε το όνομα του ιατρού"}
validates :surname, presence: {message: "Συμπληρώστε το επώνυμο του ιατρού"}
validates :birthday, presence: {message: "Συμπληρώστε την ημερομηνία γεννήσεως ιατρού"}
validates :gender, presence: {message: "Συμπληρώστε το γένος του ιατρού"}
validates :identity_number, presence: {message: "Συμπληρώστε τον αριθρμό αστυνομικής ταυτότητας του ιατρού"}
validates :identity_number_pd, presence: {message: "Συμπληρώστε την πόλη του αστυνομικού τμήματος που εξέδωσε την ταυτότητα"}
validates :father_name, presence: {message: "Συμπληρώστε το πατρώνμου ιατρού"}
validates :doctor_specialty, presence: {message: "Συμπληρώστε την ειδικότητα του ιατρού"}
validates :doc_medical_association_city, presence: {message: "Συμπληρώστε την πόλη του ιατρικού συλλόγου που είναι εγγεγραμένος ο ιατρός"}
validates :home_phone, presence: {message: "Συμπληρώστε τον αριθμό τηλεφώνου οικίας του ιατρού"}
validates :mobile_phone, presence: {message: "Συμπληρώστε το κινητό τηλέφωνο του ιατρού"}
validates :city, presence: {message: "Συμπληρώστε την πόλη κατοικίας του ιατρού"}
validates :country, presence: {message: "Συμπληρώστε χώρα της κατοικίας του ιατρού"}
validates :postal_code, presence: {message: "Συμπληρώστε τον ταχυδρομικό κώδικα της οικίας του ιατρού"}
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\Z}i, message: "Η μορφή της φωτογραφίας πρέπει να είναι PNG, JPG ή GIF"
}
validates :email, :allow_blank => true,:uniqueness => { :case_sensitive => false }, :email_format => {:message => 'Η διεύθυνση email που έχετε εισαγάγει είναι λανθασμένη'}
# Εξωτερικές συναρτήσεις (methods) για validation
validate :birthday_is_date # γεννέθλια
validate :doc_medical_association_no_check
# Έλεγχος ορθότητας ημερομηνίας γεννήσεως. Στην βάση δεδομένων η ημερομηνίες θα σωθούν με το Αμερικάνικο σύστημα
# πιο συγκεκριμένα: Χρονιά/μήνα/ημέρα, π.χ. 1972/11/24
def birthday_is_date
errors.add(:birthday, "Λάθος στην ημερομηνία γεννήσεως!") unless Chronic.parse(birthday)
end
# Έλεγχος για μοναδικό άριθμο μητρώου ιατρού. Εδώ το validation μπορεί να δημιουργήσει πρόβλημα. Πρέπει να εισαχθεί και τρίτο 'condition' - 14/03/14
def doc_medical_association_no_check
if doc_medical_association_no
errors.add(:doc_medical_association_no, "Ο αριθμός μητρώου υπάρχει είδη στην βάση δεδομένων!") if Doctor.exists?(["doc_medical_association_no = ? and not surname = ?", self.doc_medical_association_no, self.surname])
end
end
end
Test controller for Doctor is:
require 'test_helper'
class DoctorsControllerTest < ActionController::TestCase
setup do
#doctor = doctors(:alex)
#update = {
name: "Απόστολος",
surname: "Παπαδόπουλος",
gender: "Άνδρας",
birthday: Date.parse('1981-08-01'),
identity_number: "ΑT12314",
identity_number_pd: "Ξάνθης",
image_url: "ap_pap.jpg",
father_name: "Στέλιος",
doctor_specialty: "Πνευμονολόγος",
doc_medical_association_city: "ΑΘήνα",
doc_medical_association_no: "12345",
home_phone: "+30 1234567",
mobile_phone: "+30 1234567",
home_address: "Τεστ 32",
city: "Αθήνα",
country: "Ελλάδα",
postal_code: "12345",
email: "papa#somemail.com"
}
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:doctors)
end
test "should get new" do
get :new
assert_response :success
end
test "should create doctor" do
assert_difference('Doctor.count') do
post :create, doctor: #doctor # or #doctor.attributes but with the 'count by 1' error
end
assert_redirected_to doctor_path(assigns(:doctor))
end
test "should show doctor" do
get :show, id: #doctor
assert_response :success
end
test "should get edit" do
get :edit, id: #doctor
assert_response :success
end
test "should update doctor" do
patch :update, id: #doctor, doctor: #update
assert_redirected_to doctor_path(assigns(:doctor))
end
test "should destroy doctor" do
assert_difference('Doctor.count', -1) do
delete :destroy, id: #doctor
end
assert_redirected_to doctors_path
end
end
Of course doctor(:alex) is my fixture.
And finally controller for Doctor is:
class DoctorsController < ApplicationController
before_action :set_doctor, only: [:show, :edit, :update, :destroy]
# GET /doctors
# GET /doctors.json
def index
#doctors = Doctor.all
end
# GET /doctors/1
# GET /doctors/1.json
def show
end
# GET /doctors/new
def new
#doctor = Doctor.new
end
# GET /doctors/1/edit
def edit
end
# POST /doctors
# POST /doctors.json
def create
#doctor = Doctor.new(doctor_params)
respond_to do |format|
if #doctor.save
format.html { redirect_to #doctor, notice: 'Doctor was successfully created.' }
format.json { render action: 'show', status: :created, location: #doctor }
else
format.html { render action: 'new' }
format.json { render json: #doctor.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /doctors/1
# PATCH/PUT /doctors/1.json
def update
respond_to do |format|
if #doctor.update(doctor_params)
format.html { redirect_to #doctor, notice: 'Doctor was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #doctor.errors, status: :unprocessable_entity }
end
end
end
# DELETE /doctors/1
# DELETE /doctors/1.json
def destroy
#doctor.destroy
respond_to do |format|
format.html { redirect_to doctors_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_doctor
#doctor = Doctor.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def doctor_params
params.require(:doctor).permit(:name, :surname, :gender, :birthday, :identity_number, :identity_number_pd, :image_url, :father_name, :doctor_specialty, :doc_medical_association_city, :doc_medical_association_no, :home_phone, :mobile_phone, :home_address, :city, :country, :postal_code, :email, :notes)
end
end
The count by 1 problem apparently is caused by unhealthy validations. I was using an email validator which caused the problem. So if anyone has the same problem, try disabling one by one app/models/your_model_validations.rb and see how it goes.
Related
I have a user model
class User < ApplicationRecord
include ApplicationConstants
enum role: { admin: 0, waiter: 1, chef:2 }
has_secure_password
validates :name, :role, presence: true, on: :create
validates :email, uniqueness: true, format: EMAIL_REGEX
validates :password, length: { minimum: 8 }, allow_blank: true
end
The controller definition is:
before_action :set_user, only: [:show, :update, :destroy]
def update
if #user.update(user_params)
render json: #user
else
render json: #user.errors, status: :unprocessable_entity
end
end
def set_user
#user = User.find(params[:id])
end
def user_params
params.permit(:name, :role, :email, :password, :password_confirmation)
end
The problem is the following test case fails
test "should update user" do
put user_url(#user), params: { name: #user.name }
assert_response 200
end
The error is: {"password":["can't be blank"]}
I tried other answers like the one listed here, but it didn't work
As Matt pointed out, it was because of the digest attribute being nil. I added it to the fixtures and now it works.
I'm using Mongodb in my Rails app. I have 2 models, which are Account and User. Account has many users and users belongs to account.
Account model
has_many :users, :inverse_of => :account, :dependent => :destroy
validates :organization_name, presence: true, uniqueness: true
User model
belongs_to :account, :inverse_of => :users
validates :account, :presence => false
validates :email, presence: true
has_secure_password
validates :password, length: { minimum: 6 }, allow_nil: true
def User.new_token
SecureRandom.urlsafe_base64
end
def self.create_with_password(attr={})
generated_password = attr[:email] + User.new_token
self.create!(attr.merge(password: generated_password, password_confirmation: generated_password))
end
User controller
def new
#user = User.find_by(params[:id])
#user = #current_user.account.users.new
end
def create
#user = User.find_by(params[:id])
#user = #current_user.account.users.create_with_password(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
format.js
else
format.html { render 'new' }
format.json { render json: #user.errors, status: :unprocessable_entity }
format.js { render json: #user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:id, :email, :password, :password_confirmation, :owner, :admin)
end
I can successfully sign up an account with a user. But when I tried to add a new user, the user record can't be saved. I assigned a password for users when creating a new user.
The error messages:
message: Validation of User failed. summary: The following errors were found: Account can't be blank resolution: Try persisting the document with valid data or remove the validations.
If I removed the self.create_with_password and manually type in the password in the form, it works. So i guess the error must be in the self create password, it seems like doesn't save the record. By the way I'm using Rails 5.0. Any idea to solve this?
Hey #ternggio Welcome to community.
Account can't be blank.
This error appear due to belongs_to statement in user.rb.
In rails > 5 belongs_to is by default required, You can set as optional with marking optional: true.
belongs_to :account, :inverse_of => :users, optional: true
and remove below line.
validates :account, :presence => false
Hope, this will help you.
I'm trying to create a user via Postman as shown in the screenshot, but getting errors:
This is a rails app created with an --api option.
user.rb
class User < ApplicationRecord
validates :email, uniqueness: true
validates_format_of :email, with: /#/
validates :password_digest, presence: true
has_secure_password
end
users_controller.rb
class Api::V1::UsersController < ApplicationController
# GET /users/1
def show
render json: User.find(params[:id])
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
render json: #user, status: :created
else
render json: #user.errors, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
end
routes.rb
Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :users, only: [:show, :create]
end
end
end
Your user_params method expects the attributes nested in a user hash. Change the response to:
{
"user": {
"email": "...",
"password": "..."
}
}
Btw the validates :password_digest, presence: true line is not needed because has_secure_password has validations build in and ensures internally that a password is present.
Im trying to resolve this many days ago. I really don't know how to fix this issue. Im just a beginner with rails and im creating an api for personal use. here's my code:
users_controller.rb:
class UsersController < ApplicationController
def index
users = orchestrate_query(User.all)
render serialize(users)
end
def show
render serialize(user)
end
def create
user = User.new(user_params)
if user.save
UserMailer.confirmation_email(user).deliver_now
render serialize(user).merge(status: :created, location: user)
else
unprocessable_entity!(user)
end
end
def update
user = User.find(params[:id])
if user.update(user_params)
render serialize(user).merge(status: :ok)
else
unprocessable_entity!(user)
end
end
def destroy
user.destroy
render status: :no_content
end
private
def user
#user ||= params[:id] ? User.find_by!(id: params[:id]) : User.new(user_params)
end
alias_method :resource, :user
def user_params
params.require(:data).permit(:email, :password, :given_name, :family_name, :role, :confirmation_redirect_url)
end
end
users_confirmation_controller.rb:
class UserConfirmationsController < ActionController::API
before_action :confirmation_token_not_found
def show
user.confirm
if user.confirmation_redirect_url
redirect_to(user.confirmation_redirect_url)
else
render plain: 'You are now confirmed!'
end
end
private
def confirmation_token_not_found
render(status: 404, plain: 'Token not found') unless user
end
def confirmation_token
#confirmation_token ||= params[:confirmation_token]
end
def user
#user ||= User.where(confirmation_token: confirmation_token).first
end
end
user.rb -> model
class User < ApplicationRecord
has_secure_password
before_validation :generate_confirmation_token, on: :create
before_validation :downcase_email
enum role: [:user, :admin]
validates :email, presence: true,
uniqueness: true,
length: { maximum: 255 },
format: { with: /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
validates :password, presence: true, length: { minimum: 8 }, if: :new_record?
validates :given_name, length: { maximum: 100 }
validates :family_name, length: { maximum: 100 }
validates :confirmation_token, presence: true, uniqueness: { case_sensitive: true }
def confirm
update_columns({
confirmation_token: nil,
confirmed_at: Time.now
})
end
private
def generate_confirmation_token
self.confirmation_token = SecureRandom.hex
end
def downcase_email
email.downcase! if email
end
end
user_presenter.rb
class UserPresenter < BasePresenter
FIELDS = [:id, :email, :given_name, :family_name, :role, :last_logged_in_at,
:confirmed_at, :confirmation_sent_at, :reset_password_sent_at,
:created_at, :updated_at]
sort_by *FIELDS
filter_by *FIELDS
build_with *[FIELDS.push([:confirmation_token, :reset_password_token,
:confirmation_redirect_url,
:reset_password_redirect_url])].flatten
end
routes.rb
Rails.application.routes.draw do
scope :api do
resources :resorts, except: :put
resources :contact_info, except: :put
resources :users, except: :put
resources :user_confirmations, only: :show, param: :confirmation_token
get '/search/:text', to: 'search#index'
end
root to: 'resorts#index'
end
this is my request to my REST Client;
Method: POST, URL: http://localhost:3000/api/users;
Headers: application/json; and body is:
{"data":{"email":"john#gmail.com",
"password": "password",
"confirmation_redirect_url":"http://google.com"}}
Here is the error:
"error": "Internal Server Error",
app/controllers/users_controller.rb:12:in `create'
please help me. im stuck with it for the past 3 days now.
app/controllers/users_controller.rb:12:in `create'
You need to add an attribute to your users table password_digest:string when using has_secure_password. I am not positive on why your errors aren't showing correctly but this should fix the problem.
http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html
has_secure_password
short definition
Adds methods to set and authenticate against a BCrypt password. This mechanism requires you to have a password_digest attribute.
Your user params require you to call the User model to access and save the attributes YOU defined in the User table
def user_params
params.require(:user).permit(:email, :password, :given_name, :family_name, :role, :confirmation_redirect_url)
end
I'm having an issue with my ROR 4 application. Here's a quick background:
The application has several classes, Users, Training_Events and Mission_Notes.
A Training event can be associated with multiple users from a multi-select drop-down which builds an array of user_ids which are then saved to the Training_Event, whilst Mission_Notes can only be associated with one User and one Training_Event. Models below:
MissionNote.rb
class MissionNote < ActiveRecord::Base
belongs_to :training_event
belongs_to :user
end
User.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
belongs_to :group
has_many :ranks
has_many :mission_notes
has_and_belongs_to_many :training_events
validates :username, presence: true
validates :email, presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :group_id, presence: true
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
end
TrainingEvent.rb
class TrainingEvent < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :mission_notes
validates :title, presence: true
validates :date, presence: true
validates :mission, presence: true
validates_format_of :video, :with => /\A(https\:\/\/)?((www\.)?youtube\.com|youtu\.?be)\/.+$\Z/, :allow_blank => true, :message => "must be a valid YouTube URL"
validates_format_of :date, :with => /\A((19|20)\d\d+)-(0[1-9]|1[012]+)-(0[1-9]|[12][0-9]|3[01])\Z/
end
What I then want it to do is on the user's profile display a list of the events that the particular user has been associated with and the mission_notes for each event. The issue I have is when I save the training event the user_id field is not saved in the database however if I do TrainingEvent.all.each{|x| x.user_ids} then I get an array of the user_ids which were saved.
Can someone help point out what I am doing wrong here and maybe help clarify while the single user_id finds nothing but user_ids returns at least an array of items.
------------------- Edit ------------------------------------
Training_Events_Controller.rb
class TrainingEventsController < ApplicationController
before_action :set_training_event, only: [:show, :edit, :update, :destroy]
before_action :admin_user, only: [:new, :edit, :update]
# GET /training_events
# GET /training_events.json
def index
#training_events = TrainingEvent.all
end
# GET /training_events/1
# GET /training_events/1.json
def show
#user_ids = #training_event.user_ids
#user = User.find(#user_ids)
#mission_notes = MissionNote.find_by(user_id: #user)
byebug
end
# GET /training_events/new
def new
#training_event = TrainingEvent.new
#user_options = User.all.map{|u| [ u.username, u.id ] }
end
# GET /training_events/1/edit
def edit
#user_options = User.all.map{|u| [ u.username, u.id ] }
end
# POST /training_events
# POST /training_events.json
def create
#training_event = TrainingEvent.new(training_event_params)
respond_to do |format|
if #training_event.save
format.html { redirect_to #training_event, notice: 'Training event was successfully created.' }
format.json { render :show, status: :created, location: #training_event }
else
format.html { render :new }
format.json { render json: #training_event.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /training_events/1
# PATCH/PUT /training_events/1.json
def update
respond_to do |format|
if #training_event.update(training_event_params)
format.html { redirect_to #training_event, notice: 'Training event was successfully updated.' }
format.json { render :show, status: :ok, location: #training_event }
else
format.html { render :edit }
format.json { render json: #training_event.errors, status: :unprocessable_entity }
end
end
end
# DELETE /training_events/1
# DELETE /training_events/1.json
def destroy
#training_event.destroy
respond_to do |format|
format.html { redirect_to training_events_url, notice: 'Training event was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_training_event
#training_event = TrainingEvent.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def training_event_params
params.require(:training_event).permit(:title, :date, :training_objective, :mission, :video, :user_ids => [])
end
end
Also as an additional can someone suggest the best way to then put this on the view in the way I described above, I realise this part is a little vague but it has sent me round the bend as I seem to get a repeating list of events and notes. Will attach view code when I have second
Since you are using HABTM, you will not have an automatic attribute of "User_ID"... You will however have "user_ids" because you have told it that there is a join table describing a many to many relationship. I suspect that when you are saving the training event you are trying to update a "user_id" attribute.. You should instead be adding the current user_id to the user_ids array attribute that represents the relation. :)
Hope this helps!
The model TrainingEvent has the attribute user_ids which i assume is an array instead of user_id.
What I suggest is receive the array of user_ids not saved but accessed using attr_accessor :user_ids then on creating Training Events, iterate through the user_ids and save each TrainingEvent with the respective user_id.
Make sure user_id is an attribute and not user_ids.