I'm building off the tutorial app in ruby on rails for a project, and I'm trying to create an association between two models.
In my database, there are users, events, and an attendance table that associates with the email from a user and a code from an event.
I've tried to research how to do this myself, but every time I try to validate the attendance email to a user, it states that the user cannot be blank as if I were trying to create a new one.
Still quite new to Ruby on Rails, so any advice would be appreciated! The models are below.
User Model:
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
has_many :attendances, inverse_of: :user
accepts_nested_attributes_for :attendances
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates(:name, presence: true, length: { maximum: 50 })
validates(:email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false})
validates(:password, length: { minimum: 6 } )
validates(:password_confirmation, presence: true)
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
Attendance Model:
class Attendance < ActiveRecord::Base
attr_accessible :code, :email
belongs_to :user
validates_presence_of :user
end
So far I'm only trying to enforce the association between User and Attendance, once I get that working I'll do the same to Events. Also, this is Rails 3.2.19 and Ruby 1.9.3.
EDIT: Here is the code I'm using for the form, I believe that it works because until I put the validation into the model it was creating rows into the Attendance table.
<% provide(:title, 'Test Event') %>
<h1>Attendance Registration</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#attendance) do |f| %>
<%= render 'shared/attendance_error_messages' %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :code %>
<%= f.text_field :code %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
Also, here's the attendance controller, if that helps.
class AttendancesController < ApplicationController
def new
#attendance = Attendance.new
end
def create
#attendance = Attendance.new(params[:attendance])
if #attendance.save
flash[:success] = "Attendance logged."
redirect_to root_path
else
render 'new'
end
end
end
Add this line to your form to avoid the user presence error
<%= f.hidden_field :user_id, current_user.id %>
Related
I'm trying to bring a list of Users that a "request" has been accepted for a user to select from. I set up the collection and what is showing up on the view page is either the user id when I put :user_id or its shows <User:0x007fbdrf252a60> when i put in user. I tried putting in :name but it returns a "undefined method name" error.
Currently my form looks like:
<%= form_for :event_logs, url: event_logs_path do |f| %>
<%= f.select :user_id, options_from_collection_for_select(#user_event_log, :id, :name ) %>
<%= f.hidden_field :event_id, :value => #event.id %>
<%= f.submit 'Send', class: 'btn btn-success' %>
<% end %>
And in the controller for the show its:
#user_event_log = RequestLog.usr_req_by current_user
The user_req_by in the model is: scope :usr_req_by, ->(user) { where(user_id: user.id) }
If more information is needed I'm more than happy to add it. Thank you
User Model:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates_presence_of :name
has_many :request_logs, dependent: :destroy
acts_as_messageable
def first_name
self.name.split.first
end
def last_name
self.name.split.last
end
end
Request Log Model:
class RequestLog < ApplicationRecord
enum status: { accepted: 0, rejected: 1 }
belongs_to :user, optional: true
belongs_to :request, optional: true
validates :user_id, presence: true
validates :request_id, presence: true
scope :usr_req_by, ->(user) { where(user_id: user.id) }
scope :req_itp, ->(requests) { where(request_id: requests) }
end
The collection iterated over, is a collection of RequestLog. So when you use :name in options_from_collection_for_select, it tries to call .name on a RequestLog.
I suppose you want the users name there? If so, you can add a helper method to RequestLog
def user_name
return user.name
end
and use :user_name in the view.
Another option, is to pass an object, that responds to .call as a param, as so:
<%= form_for :event_logs, url: event_logs_path do |f| %>
<%= f.select :user_id, options_from_collection_for_select(#user_event_log, :id, ->(log){ log.user.name }) %>
<%= f.hidden_field :event_id, :value => #event.id %>
<%= f.submit 'Send', class: 'btn btn-success' %>
<% end %>
I haven't tried this out, but I did look at the source for options_from_collection_for_select, that uses value_for_collection under the hood; Defined as
def value_for_collection(item, value)
value.respond_to?(:call) ? value.call(item) : public_or_deprecated_send(item, value)
end
(See Rails Source Code and apidock.com)
I have a problem with a form and strong parameters. I see many have gone before me with such problems but nothing suggested to them seems to work.
My form:
<%= form_for(#student) do |f| %>
<%= f.label :school_id %>
<%= f.text_field :school_id, class: 'form-control' %>
...
<%= f.fields_for :enrollments do |enrol_form| %>
<%= enrol_form.label :date , "Enrollment date", class: 'form-control' %>
<%= enrol_form.date_field :date , class: 'form-control' %>
<%= enrol_form.hidden_field :reason, value: "Entered on system" %>
<% end %>
...
<% end %>
Models:
class Student < ActiveRecord::Base
include PhoneValid
has_many :enrollments, inverse_of: :student
validates :enrollments, :presence => { message: "Must have at least one enrollment event" }
accepts_nested_attributes_for :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :student
default_scope -> { order(date: :asc) }
validates :date, presence:true
validates :enroll_wd, inclusion: {in: %w(ENROLL WD), messages: "%{value} must be either WD or ENROLL"}
end
Controller:
class StudentsController < ApplicationController
def create
#student = Student.new(student_params)
#student.save
end
def student_params
params.require(:student).permit(:school_id, :cellphone, :attendance_officer_id, :attendance_officer_type, :language_preferred, {enrollment_attributes: [:date, :reason, :id]})
end
end
The output of my form according to debug(params) is:
--- !ruby/hash:ActionController::Parameters
utf8: "✓"
authenticity_token: >cuk8Y+d8fHhJ3mU7wtRYtyDJoiYaG8lfzHvdGVBUI+1qmH7xQr20BHsBAFRT4r1EfDb/MMbxMq8rbKw3Cf2Y1A==
student: !ruby/hash:ActionController::Parameters
school_id: '234234'
cellphone: '234234234'
language_preferred: en
enrollments_attributes: !ruby/hash:ActionController::Parameters
'0': !ruby/hash:ActionController::Parameters
date: '2015-07-10'
reason: Entered on system
attendance_officer_id: '8'
attendance_officer_type: User
commit: Save student
controller: students
action: create
The output of student_params is:
{"school_id"=>"234234", "cellphone"=>"234234234", "attendance_officer_id"=>"8", "attendance_officer_type"=>"User", "language_preferred"=>"
I've been trying every different format of student_params that I can find on in the forums and have concluded that the problem must be somewhere else - am I creating the form correctly? Is there something wrong in my models?
Thank you for any help you can give me.
Walter
Try changing your student_params to below
def student_params
params.require(:student).permit(:school_id, :cellphone, :attendance_officer_id, :attendance_officer_type, :language_preferred, enrollments_attributes: [:date, :reason, :id])
end
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 the following model
class OrgPerson < ActiveRecord::Base
has_and_belongs_to_many :TypRole
has_and_belongs_to_many :OrgContact
has_one :OrgCredential, dependent: :destroy
belongs_to :OrgCompany, foreign_key:"org_company_id"
belongs_to :TypPosition, foreign_key:"typ_position_id"
validates :first_name, presence: true
validates :last_name, presence: true
accepts_nested_attributes_for :OrgCredential
end
class OrgCredential < ActiveRecord::Base
belongs_to :OrgPerson, foreign_key:"org_person_id"
validates :user_name, presence: true
validates :password, length: { minimum: 6 }
before_create :create_remember_token
has_secure_password
end
and the following strong parameters:
def person_params
params.require(:org_person).permit(:first_name, :last_name,
org_credential_attributes: [:password, :password_confirmation])
end
and the following new.html.erb
<%= form_for(#person) do |f| %>
<%= render 'shared/error_messages' %>
<div class="col-md-12 ">
<%= f.text_field :first_name, placeholder: "First Name", :class => "form-control" %>
<%= f.text_field :last_name, placeholder: "Last Name", :class => "form-control" %>
<%= f.fields_for :org_credentials do |oc|%>
<%= oc.password_field :password, placeholder: "Password", :class => "form-control" %>
<%= oc.password_field :password_confirmation, placeholder: "Password Confirmation", :class => "form-control" %>
<% end %>
<%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
</div>
In shared/error_messages
<% if #person.errors.any? %>
<div id="error_explanation" class="col-md-12">
<div class="alert alert-danger" role="alert">
The form contains <%= pluralize(#person.errors.count, "error") %>.
</div>
<ul>
<% #person.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
The problem as shown in the picture is that it says something like "Org credential password is too short" when I only want it to say "Password is too short." I'm quite new to rails and I feel like I have to iterate through the error hash to spit out "Password is too short," but i'm not quite certain if this is the only way to do it or if there is an easier way. I hope someone who may have ran into the same problem can enlighten me on how they solved it.
Thank you!
Associations
Firstly, you need to be aware that Rails requires snake_case for the association name definitions - and CamelCase for the Class Names:
#app/models/org_person.rb
Class OrgPerson < ActiveRecord::Base
has_and_belongs_to_many :typ_roles
has_and_belongs_to_many :org_contacts
has_one :org_credential, dependent: :destroy
belongs_to :org_company
belongs_to :typ_position
validates :first_name, presence: true
validates :last_name, presence: true
accepts_nested_attributes_for :org_credential
end
--
Messages
In regards to your error messages, and as Rahul Singh recommended, you need to consider that you can define custom error messages for your validations
You can do that using the the following:
validates :first_name, :last_name,
presence: { message: "You Need To Enter A Name!" }
This will give you custom messages, however, there is a sticking point with Rails, in that it's difficult to get the attribute name to be customized.
If you wanted to show the messages only, you'll be best using something like this (live code):
<% #person.errors.each do |attr,msg| %>
<li><%= msg %></li>
<% end %>
you can add custom validation error message in your model.
class OrgCredential < ActiveRecord::Base
belongs_to :OrgPerson, foreign_key:"org_person_id"
validates :user_name, presence: true
validates :password, length: { minimum: 6 }, message: "Password is too short"
before_create :create_remember_token
has_secure_password
end
read through here Active Record Validations for more details.
Thanks everyone for you answers.
I didn't know that Rails had a sticking point to it, thanks Rich.
I actually used Michal's response to point me to the right direction.
Seems like the only way to actually get rid of the attribute name is to
actually customize every single error message in the en.yml file within configs/locales.
The following guide is a very useful link that helped me fix this problem:
http://adamhooper.com/eng/articles/5
If anyone is running into the same problems, please refer to adam hopper's article.
The rails guide: http://guides.rubyonrails.org/i18n.html#pluralization was of some help, but not as much as the first resource.
Cheers!
I'm really new to both ruby on rails and programming. I am trying to develop an application but i am stucked now. I was watching http://railscasts.com/episodes/196-nested-model-form-part-1 to make nested model forms but i am having an error. My problem details are as follows;
I have employers model, and employers model has_many interviews, and interview model has_many customquestions. I'm trying to create a form through which i will collect info to create interview. Although i made all necessary assosications, when i submit the form it raises error saying that "Customquestions interview can't be blank". I am kinda sure that it is because of that i miss some code in interview controller. Below you can see my interview controller and the form template that i am using to submit info.
Interview Controller
class InterviewsController < ApplicationController
before_filter :signed_in_employer
def create
#interview = current_employer.interviews.build(params[:interview])
if #interview.save
flash[:success] = "Interview created!"
redirect_to #interview
else
render 'new'
end
end
def destroy
end
def show
#interview = Interview.find(params[:id])
end
def new
#interview = Interview.new
3.times do
customquestion = #interview.customquestions.build
end
end
end
Form which i use to submit info:
<%= provide(:title, 'Create a new interview') %>
<h1>Create New Interview</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#interview) do |f| %>
<%= render 'shared/error_messages_interviews' %>
<%= f.label :title, "Tıtle for Interview" %>
<%= f.text_field :title %>
<%= f.label :welcome_message, "Welcome Message for Candidates" %>
<%= f.text_area :welcome_message, rows: 3 %>
<%= f.fields_for :customquestions do |builder| %>
<%= builder.label :content, "Question" %><br />
<%= builder.text_area :content, :rows => 3 %>
<% end %>
<%= f.submit "Create Interview", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
In interview model, i used accepts_nested_attributes_for :customquestions
Interview Model
class Interview < ActiveRecord::Base
attr_accessible :title, :welcome_message, :customquestions_attributes
belongs_to :employer
has_many :customquestions
accepts_nested_attributes_for :customquestions
validates :title, presence: true, length: { maximum: 150 }
validates :welcome_message, presence: true, length: { maximum: 600 }
validates :employer_id, presence: true
default_scope order: 'interviews.created_at DESC'
end
The validation error gets raised in the customquestions model because (I assume) it validates :interview_id. The problem is that interview_id won't get set until the parent object (Interview) is saved, but validations for customquestion are run before Interview is saved.
You can let cusomtquestions know about this dependency by adding the option :inverse_of=> :customquestions to belongs_to :interview in the customquestions model.