I'm a beginner building an app for my language students that checks whether the user copied a term correctly into a form. The Word model has a :term attribute, which is what the user will be copying. The Word Exposition model is associated with the Word model; upon enrollment each WordExposition-Word association's :completed attribute is set to false. I'm trying to implement a form in the WordExposition show view, such that if the user types the correct term, the WordExposition completed attribute will change to true from the default false.
In order to check whether the Word.term matches the term that the student copied I have a word_from_student_matches_word method in the WordExposition model, which I want to run before updating. As of now, I'm getting undefined local variable or methodword_from_student_matches_word'` upon submission of the form. How could I go about checking for matching spelling and updating the boolean attribute from the view?
WordExposition model:
class WordExposition < ActiveRecord::Base
belongs_to :enrollment
belongs_to :word
delegate :term, to: :word
delegate :reference, to: :word
delegate :image, to: :word
delegate :sound, to: :word
attr_accessor :term_given_by_student
validate :word_from_student_matches_word, on: :update
def word_from_student_matches_word
return true if word.term == term_given_by_student
errors.add(:term_given_by_student, "Terms don't match")
end
def next_word_exposition
WordExposition.where(["id > ? AND lesson_id = ?", id, lesson_id]).first
end
end
Word Expositions controller:
class WordExpositionsController < ApplicationController
before_action :authenticate_user!
before_action :require_enrollment_in_lesson
def show
#word = current_enrollment.word_expositions.find_by!(word_id: params[:id])
end
def update
current_word_exposition
if word_from_student_matches_word
current_word_exposition.completed = true
current_word_exposition.save
end
end
private
helper_method :current_lesson
def current_lesson
#current_lesson ||= Lesson.find(params[:lesson_id])
end
helper_method :current_enrollment
def current_enrollment
#current_enrollment ||= Enrollment.find_by!(lesson_id: params[:lesson_id], user_id: current_user.id)
end
def require_enrollment_in_lesson
if !(current_user.enrolled_in?(current_lesson))
redirect_to lesson_path(current_lesson), alert: 'You need to enroll in order to view the activities!'
end
end
def word_exposition_params
params.require(:word_exposition).permit(:completed)
end
def current_word_exposition
#current_word_exposition ||= WordExposition.find(params[:id])
end
end
Word Expositions show view:
<h1>Word Exposition</h1>
<!-- display the term to be copied -->
<div class="col-xs-10 col-xs-offset-1">
<h2><%= #word.term %></h2><br>
<!-- Form to check matching spelling and update WordExposition :completed to true if correct -->
<%= simple_form_for #word, url: lesson_word_exposition_path(current_lesson, #word), method: :patch do |f| %>
<%= f.input :term_given_by_student, label: "Enter the term exactly as above:" %><br>
<%= f.button :submit, class: 'btn btn-primary' %>
<% end %>
</div>
Rake routes:
lesson_enrollments POST /lessons/:lesson_id/enrollments(.:format) enrollments#create
lesson_word_exposition GET /lessons/:lesson_id/word_expositions/:id(.:format) word_expositions#show
PATCH /lessons/:lesson_id/word_expositions/:id(.:format) word_expositions#update
PUT /lessons/:lesson_id/word_expositions/:id(.:format) word_expositions#update
lessons GET /lessons(.:format) lessons#index
lesson GET /lessons/:id(.:format) lessons#show
word GET /words/:id(.:format) words#show
teacher_lesson_words POST /teacher/lessons/:lesson_id/words(.:format) teacher/words#create
new_teacher_lesson_word GET /teacher/lessons/:lesson_id/words/new(.:format) teacher/words#new
edit_teacher_lesson_word GET /teacher/lessons/:lesson_id/words/:id/edit(.:format) teacher/words#edit
teacher_lesson_word PATCH /teacher/lessons/:lesson_id/words/:id(.:format) teacher/words#update
PUT /teacher/lessons/:lesson_id/words/:id(.:format) teacher/words#update
DELETE /teacher/lessons/:lesson_id/words/:id(.:format) teacher/words#destroy
teacher_lessons POST /teacher/lessons(.:format) teacher/lessons#create
new_teacher_lesson GET /teacher/lessons/new(.:format) teacher/lessons#new
edit_teacher_lesson GET /teacher/lessons/:id/edit(.:format) teacher/lessons#edit
teacher_lesson GET /teacher/lessons/:id(.:format) teacher/lessons#show
PATCH /teacher/lessons/:id(.:format) teacher/lessons#update
PUT /teacher/lessons/:id(.:format) teacher/lessons#update
DELETE /teacher/lessons/:id(.:format) teacher/lessons#destroy
you are wrong in your controller
def update
current_word_exposition
current_word_exposition.completed = true #this line move to callback in your model changing the flag before save
if current_word_exposition.save
#nothing o something
else
#do something
end
end
Your model is validating the term on update, and return false if something went wrong. Just need to save the model instance and control the result
Related
I need to capture a field added by a user in a form_for, inside the product show page.
My product.rb model as follows:
belongs_to :user
has_many :complaints
My complaint.rb model as follows:
belongs_to :product
belongs_to :user
My user.rb model as follows:
has_many :products
My product controller is a basic controller with all the new, create, edit, update actions and all the routes are good.
User looks at the product show page like this, and it's all good
http://localhost:3000/products/1
My goal is to create a complaint from the product show page, when user views the specific product. So I have created a complaints_controller.rb to capture all the details of the product, and create a complaint. I have an issue with capturing the complaint_number which is a field inside the complaints table.
Here is my form inside the product show page
<%= form_for([#product, #product.complaints.new]) do |f| %>
<%= f.number_field :complaint_number, placeholder: "Enter complaint number you were given" %>
<%= f.submit 'Complaint' %>
<% end %>
Here is my complaints_controller.rb
Goal is to capture the complaint_number fields and run the make_complaint method to create a complaint and populate rest of the fields in the newly created row of the complains table.
class ComplaintsController < ApplicationController
before_action :authenticate_user!
def create
# Will Get product_id from the action in the form in product show page.
product = Product.find(params[:product_id])
# This complaint_number does not seem to work
complaint_number = product.complaints.find_by(complaint_number: params[:complaint_number])
# Now I want to run a make_complaint method and pass the product and the complaint number. This fails, I can't capture the complaint_number in the form from user input.
make_complaint(product, complaint_number)
redirect_to request.referrer
end
private
def make_complaint(product, complaint_number)
complaint = product.complaints.new
complaint.title = product.title
complaint.owner_name = product.user.name
complaint.owner_id = product.user.id
# Note: complaint_number and current_complaint are a fields in the Orders table
# Note:
complaint.current_complaint = complaint_number
if complaint.save
flash[:notice] = "Your complaint has been sent!"
else
flash[:alert] = complaint.errors.full_messages
end
end
end
For routes I have added resources :complaint, only: [:create] inside the resources of products to get products/:id/complaints
My routes.rb is like this
Rails.application.routes.draw do
get 'products/new'
get 'products/create'
get 'products/edit'
get 'products/update'
get 'products/show'
root 'pages#home'
get '/users/:id', to: 'users#show'
post '/users/edit', to: 'users#update'
resources :products do
member do
delete :remove_image
post :upload_image
end
resources :complaint, only: [:create]
end
devise_for :users, path: '', path_names: { sign_in: 'login', sign_up: 'register', sign_out: 'logout', edit: 'profile' }
Your form has complaint_quantity:
<%= form_for([#product, #product.complaints.new]) do |f| %>
<%= f.number_field :complaint_quantity, placeholder: "Enter complaint number you were given" %>
<%= f.submit 'Complaint' %>
<% end %>
Your controller has complaint_number:
complaint_number = product.complaints.find_by(complaint_number: params[:complaint_number])
If you check your params from the server log, I bet you'll see the value you are looking for is coming across as complaint_quantity and not complaint_number.
UPDATE
With the form misspelling corrected, the error persists, so let's check into more areas:
complaint_number = product.complaints.find_by(complaint_number: params[:complaint_number])
So, break that down:
1. What does params actually include?
Is :complaint_number being submitted from the form?
If not, the form still has an error somewhere.
2. Does product.complaints actually include a complaint that could be matched by complaint_number?
I don't know your data structure well enough to tell, but it looks to me like you might actually want to do:
Complaint.find_by(complaint_number: params[:complaint_number])
instead of:
products.complaints.find_by(complaint_number: params[:complaint_number])
UPDATE #2
You know the problem is with your params.
I'm confident you aren't accessing your params correctly since you are using a nested form:
form_for([#product, #product.complaints.new])
Should mean your params are structured like { product: { complaint: { complaint_number: 1234 }}}
So params[: complaint_number] is nil because it should really be something like params[:product][:complaint][:complaint_number]
Please look at your server log in your terminal right after you submit the form to see the structure of your params. Or insert a debugger in the controller action and see what params returns.
ALSO, Instead of accessing params directly, you should whitelist params as a private method in your controller.
Something along these lines:
private
def product_complaint_params
params.require(:product).permit(:id, complaint_params: [ :complaint_number ])
end
See this: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html
When I write a message and when pressing the send option,
I want to store student_id, coach_id and message to the database. student_id and coach_id are being saved, but the message field is not being saved. It shows null in the database. How do I fix this?
Any help is appreciated.
Controller file:
class CourseQueriesController <ApplicationController
def index
#course_query = CourseQuery.new
end
def create
# #course_query = CourseQuery.new(course_query_params)
#course_query = CourseQuery.where(student_id: current_student.id, coach_id: "2", message: params[:message]).first_or_create
if #course_query.save
redirect_to course_queries_path, notice: 'Query was successfully send.'
else
render :new
end
end
private
def set_course_query
#course_query = CourseQuery.find(params[:id])
end
# def course_query_params
# params[:course_query].permit(:message)
# end
end
model/course_query.rb:
class CourseQuery < ActiveRecord::Base
belongs_to :student
belongs_to :coach
end
view/course_query/index.html.erb:
<%= simple_form_for (#course_query) do |f| %>
<%= f.button :submit , "Send or press enter"%>
<%= f.input :message %>
<% end %>
database /course_queries:
It seems you didn't permit :course_query.
Try to permit your params the following way:
def course_query_params
params.require(:course_query).permit(:message)
end
But according to the 2nd way you pass params (params[:message]) I think you have a bit different params structure. So try another one:
def course_query_params
params.permit(:message)
end
When you look into the params generated in the log, you will see that the message inside the course_query hash, so params[:message] should be params[:course_query][:message]
#course_query = CourseQuery.where(student_id: current_student.id, coach_id: "2", message: params[:course_query][:message]).first_or_create
I have an object that I am trying to allow users to edit in my rails 4 app. The user has_one supp_form and I want them to be able to edit the information in the supp_form. The page is loading fine and the relationships are setup properly.
The error
No route matches [PATCH] "/businesses/3/supp_form/edit"
when I rake routes I see the following route:
edit_business_supp_form_path GET /businesses/:business_id/supp_form/edit(.:format) supp_forms#edit
GET /businesses/:business_id/supp_form(.:format) supp_forms#show
PATCH /businesses/:business_id/supp_form(.:format) supp_forms#update
PUT /businesses/:business_id/supp_form(.:format) supp_forms#update
supp_forms_controller.rb
class SuppFormsController < ApplicationController
before_filter :authenticate_user!
def new
#suppform = SuppForm.new(supp_form_params)
end
def create
#suppform = SuppForm.create(supp_form_params)
end
def edit
#user = User.current_user
#suppform = #user.supp_form
end
def update
#user = current_user
#suppform = SuppForm.update(supp_form_params)
end
private
def supp_form_params
params.require(:supp_form).permit(:id, :business_id, :title, :first_name,
:last_name, :applicant_role, :work_phone_number)
end
end
View
<%= form_for #user.supp_form, :url => edit_business_supp_form_path(#user.supp_form), :html => { :class => "sky-form", :id => "sky-form4" } do |supp_form| %>
<%= supp_form.text_field :work_phone_number, :placeholder => "Your new phone number" %>
<% end %>
The problem is that it tries to access the route using a PATCH request, that is used for updating. In your routes the /businesses/:business_id/supp_form/edit route is only specified for GET requests, thus the error.
This happens because the path you are using in the form points to the edit action (which is only responsible for showing the edit form) and should instead point to the update action. So the route you should be actually using in the is the supp_form_path that, in connection with the PATCH method, pushes the information to the update action, where the object is updated.
I am creating a job board, and I don't want to allow the users the option to apply for the same job twice. How can I limit this?
app/views/jobs/job.html.erb
<% if applied_to_this_job? %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
app/helpers/jobs_helper.rb
def applied_to_this_job?
JobApplication.exists? user_id: current_user.id
end
Obviously this doesn't work because it checks if this user has applied to any job. How Can I check to see if the current user has applied to the job being viewed.
Also, how can I limit this at the controller level so that the user can't go to job_application/new and get to the form.
You would use a before_filter in the controller action.
class JobsController < ActionController::Base
before_filter :has_applied?, only: [new, create]
....
private
def has_applied?
if JobApplication.where(user_id: :current_user.id, job_id: params[:job_id]).any?
redirect_to :index, alert: "You have already applied"
end
end
end
This would allow the user to visit /jobs/new and post the application to /jobs/create unless they have applied. If they have applied, they will be redirected to the index in the sample code.
Also as another answer has noted, it would be wise to pass in the job id as well. Updated sample code above to reflect.
You need to check and see if the JobApplication object is for this #job try:
JobApplication.where( user_id: current_user.id, job_id: #job.id ).exists?
Although what you've accepted will work, I think it's somewhat of a surface-level fix.
You'll be much better using validators to determine if the user can actually create another job application. This will protect against any problems with the business logic in your "front-end" views
Here's how I'd handle it:
--
Uniq
#app/models/user.rb
class User < ActiveRecord::Base
has_one :job_application
end
#app/models/job_application.rb
class JobApplication < ActiveRecord::Base
belongs_to :user
validates :user_id, uniquness: true
end
You may also wish to give your database a uniq index for your user_id column:
> $ rails g migration AddUniqueIndex
#config/db/add_unique_index.rb
class AddUniqueIndex < ActiveRecord::Migration
def change
add_index :job_applications, [:job_id, :user_id], unique: true
end
end
This will give you a highly efficient DB-level uniqueness index - meaning that if you try and add any more applications than is permitted, it will either fail silently, or come back with an error.
Controller
The structure of the controller would allow you to be less stringent about the accessibility of the job_application functionality:
#app/views/jobs/job.html.erb
<% if current_user.has_applied?(params[:job_id]) %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
#app/models/user.rb
class User < ActiveRecord::Base
has_many :job_applications
def has_applied?(job_id)
job_applications.find job_id
end
end
I have a form that lets users add a new blocked tv show to their list of blocked shows. The form is not taking the param values (:user_id, :title, :image) that I tried to set in the controller. I'm a beginner, so I'm guessing the syntax is the problem.
Also I am getting a Couldn't find Tvshow without Id error when trying to use the #tvshow instance variable to set the param values of :title and :image. Each Blocked show should have the same title and image as the tvshow that the user selects in the collection_select. Is there an easier way to do this?
View
<%= form_for #blockedshow do |b| %>
<%= b.label :tvshow_id, "Add a Blocked TV Show " %><br/>
<%= collection_select(:blockedshow, :tvshow_id, Tvshow.all, :id, :title, prompt: true) %>
<%= submit_tag 'Add' %>
<% end %>
Controller
class BlockedshowsController < ApplicationController
def new
#blockedshow = Blockedshow.new
end
def create
#tvshow = Tvshow.find params[:blockedshow][:id]
#blockedshow = Blockedshow.new(safe_blockedshow)
params[:user_id] = current_user.id
params[:title] = #tvshow.title
params[:image] = #tvshow.image
if #blockedshow.save
flash[:notice] = "New Blocked TV Show added successfully"
redirect_to tv_show_index_path
else
render 'new'
end
end
private
def safe_blockedshow
params.require(:blockedshow).permit(:title, :user_id, :tvshow_id, :image)
end
end
Blockedshow model
class Blockedshow < ActiveRecord::Base
has_many :phrases
has_many :tvshows
belongs_to :user
end
Tvshow model
class Tvshow < ActiveRecord::Base
has_many :phrases
belongs_to :blockedshow
def self.search_for (query)
where('title LIKE :query', query: "%#{query}%")
end
end
Routes
resources :blockedshows
post 'blockedshows', to:'blockedshows#create#[:id]'
you are getting the issue because params[:blockedshow][:id] is not passed, if your trying to access the Tvshow id selected by from the drop-list you can do the following
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
Just fixed by changing the controller to this:
def create
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
#blockedshow = Blockedshow.new(
:user_id =>current_user.id,
:title=> #tvshow.title,
:image=> #tvshow.image,
:tvshow_id=>#tvshow.id
)