I'm having trouble understanding how to satisfy strong params when using button_to to do an update action. I'm trying to set an attribute called active to the value of true for an existing instance of a class called Plan.
(Note that I'm using HAML for my views here.)
This works:
= form_for(plan, remote: true) do |f|
= f.hidden_field :active, value: true
= f.submit 'set active'
But this doesn't:
= button_to "set active", plan_path(plan, active: true), method: :put, remote: true
Error
Completed 400 Bad Request in 7ms (ActiveRecord: 1.1ms)
ActionController::ParameterMissing - param is missing or the value is
empty: plan:
actionpack (4.2.1) lib/action_controller/metal/strong_parameters.rb:249:in 'require'
() Users/Rob/Sites/drexel_msis_planner/app/controllers/plans_controller.rb:77:in 'plan_params'
() Users/Rob/Sites/drexel_msis_planner/app/controllers/plans_controller.rb:45:in 'block in update'
actionpack (4.2.1) lib/action_controller/metal/mime_responds.rb:210:in 'respond_to'
() Users/Rob/Sites/drexel_msis_planner/app/controllers/plans_controller.rb:44:in 'update'
Routes
user_plans GET /users/:user_id/plans(.:format) plans#index
POST /users/:user_id/plans(.:format) plans#create
new_user_plan GET /users/:user_id/plans/new(.:format) plans#new
edit_plan GET /plans/:id/edit(.:format) plans#edit
plan PATCH /plans/:id(.:format) plans#update
PUT /plans/:id(.:format) plans#update
DELETE /plans/:id(.:format) plans#destroy
Controller
# PATCH/PUT /plans/1
def update
respond_to do |format|
if #plan.update(plan_params)
format.js { flash.now[:notice] = "Plan was successfully updated." }
end
end
end
private
def plan_params
params.require(:plan).permit(:user_id, :name, :active)
end
It seems like such a silly issue but I can't figure it out and the API documentation doesn't seem to give any clues as to why it wouldn't be working.
These are but a few of the variations that I've tried (each is followed by its accompanying error message):
= button_to "set active", plan_path(plan: plan, active: true), method: :put, remote: true
ActionController::UrlGenerationError - No route matches
{:action=>"update", :active=>true, :controller=>"plans", :plan=>#,
:user_id=>"104"} missing required keys: [:id]:
= button_to "set active", plan_path(id: plan.id, active: true), method: :put, remote: true
Completed 400 Bad Request in 17ms (ActiveRecord: 2.1ms)
ActionController::ParameterMissing - param is missing or the value is
empty: plan: actionpack (4.2.1)
lib/action_controller/metal/strong_parameters.rb:249:in `require'
= button_to "set active", plan, active: true, method: :put, remote: true
ActionController::ParameterMissing - param is missing or the value is
empty: plan: actionpack (4.2.1)
lib/action_controller/metal/strong_parameters.rb:249:in 'require'
()
Users/Rob/Sites/drexel_msis_planner/app/controllers/plans_controller.rb:77:in
'plan_params' ()
Users/Rob/Sites/drexel_msis_planner/app/controllers/plans_controller.rb:45:in
'block in update'
I was able to finally resolve this based on the information in this thread.
Instead of placing the parameters in their own hash as another argument to button_to, I included them inside of the call to the plan_path method. The first argument needs to be the model's ID, and the second argument needs to be the model's name as a key with a hash of the desired attributes as its value. (Example below):
= button_to "set active", plan_path(plan.id, plan: { active: true }), method: :put, remote: true
If you look at the submitted params the difference is that your form results in params being
{ "id" => 123, "plan" => {"active" => true}, "controller" => "...", "action" => "..."}
Whereas the second results in
{ "id" => 123, "active" => true, "controller" => "...", "action" => "..."}
And in that case params[:plan] is nil, which leads to the error you see.
There are multiple ways to fix this. You could change the submitted parameters to match what the controller currently expects, for example
button_to set_active, plan, method: :put, remote: true, params: {"plan[active]" => 1}
(You could also have the parameters be part of the form URL as you were attempting but having as form fields feels slightly more correct to me).
Alternatively, if this update action isn't used by any other forms, then change it to match the submitted data. I wouldn't normally do this - it would be very easy but your app will be easier to think about if things behave in predictable ways.
According to http://apidock.com/rails/ActionView/Helpers/UrlHelper/button_to, params should be a separate hash
= button_to "set active", plan, method: :put, remote: true, params: { :active => true }
Related
I'm using Rails 5. I want one of the attributes in my model to fail validation if it consists of only letters or if it contains the pattern "\d:\d" anywhere in its value. I tried this
validates_format_of :my_attr, numericality: { greater_than: 0, :only_integer => true }, :allow_blank => true, :without => /(\d:\d|^\p{L}+$)/
But when I create a new object
2.4.0 :018 > ab = MyObjectTime.new({:my_attr => "ab"})
It is not indicating an error when I query "ab.errors" for the field in question. What's the correct way to write the regular expression above?
First and Foremost new method does not trigger any kind of validation on the object.
class Person < ApplicationRecord
validates :name, presence: true
end
>> p = Person.new
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {}
new method does not trigger any validation on an object as it is not hitting the database to save the record.
>> p.save
# => false
save method will try to create the record to the database and triggers the respective validation on the object.
>> p.valid?
# => false
When you hit the .valid? the method, it validates the object against the mentioned validation.
>> p.errors.messages
# => {name:["can't be blank"]}
>> p = Person.create
# => #<Person id: nil, name: nil>
Creating and saving a new record will send an SQL INSERT operation to the database.
Internal functionality of Person.create is Person.new and Person.save
When you are creating an object, it tries to create the valid record to the database and triggers the respective validation.
>> p.errors.messages
# => {name:["can't be blank"]}
>> p.save
# => false
>> p.save!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
>> Person.create!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
Secondly, validates_numericality_of and validates_format_of are the different set of validation helpers which you have mixed.
validates_numericality_of :my_attr, :only_integer => true, :allow_blank => true,
:greater_than => 0
validates_format_of :my_attr, :without => /(\d:\d|^\p{L}+$)/
This validation won't accept any such object :
MyObjectTime.new({:my_attr => "ab"})
MyObjectTime.new({:my_attr => "1:1"})
For more information you can take help from these validation helpers => http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html
Try something like this
validates :email, presence: true, format: { with: /\A([^\}\{\]\[#\s\,]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
Currently doing Rails Unit Test, using minitest-rails.
I'm using bootstrap editable js to directly update data in view.
I having trouble asserting value correctly, got Failure result.
Only for the function that I used bootstrap editable, since it uses other way to send parameters than normal Rails update action.
Please help a look at my codes.
In my controller:
def edit_job_type
update_common_table('job_types', params[:pk], params[:name], params[:value])
end
In my included module:
def update_common_table(table, id, key, value)
begin
case table
when 'job_types'
#record = JobType.find(id)
end
case key
when 'en_name'
#record.en_name = params[:value]
edit_field = 'English Name'
end
#record.last_updated_by = session[:username]
#record.save
render json: {
status: 'success',
message: "#{edit_field} was successfully updated.",
updated_at: #record.updated_at.to_time.strftime("%a, %e %b %Y %H:%M"),
updated_by: session[:username]
}
rescue => error
render json: {status: 'error', message: error.message}
end
end
In my minitest-rails, controller:
setup do
#job_type = job_types(:waiter)
end
test "should update job_type" do
patch :edit_job_type, id: #job_type.id, job_type: { pk: #job_type.id, name: 'en_name', value: "janitor" }
assert_response :success, message: 'English Name was successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name # this one FAILS, not updated value
end
In my fixtures > job_types:
waiter:
en_name: waiter
When I run rake test:
I got failure result, because the update was failed.
Expected: "New Job Type Updated"
Actual: "waiter"
Still getting the default value "waiter", instead of "janitor"
Please help to figure out how can I fixed my test.
SOLVED
Finally I've made a work around after thorough searching.
The solution was to use a XHR method since bootstrap editable uses POST method.
Before:
test "should update job_type" do
patch :edit_job_type, id: #job_type.id, job_type: { pk: #job_type.id, name: 'en_name', value: "janitor" }
assert_response :success, message: ' Successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name # this one FAILS, not updated value
end
After:
test "should update job_type" do
xhr :post, :edit_job_type, format: :js, pk: #job_type.id, name: 'en_name', value: "janitor"
assert_response :success, ' Successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name
end
Thanks to this tutorial, Building Rails Test
I am having an annoying issue with using Globalize + RSpec + factory_girl.
I have a model with an attribute translated, and when creating factories using factory_girl the issue occurs. The code explains it perfectly:
Translation migration:
def self.up
CandidateBranch.create_translation_table!({
name: {type: :string, null: false, limit: 150 }
}, {
migrate_data: true
})
end
Model:
class CandidateBranch < ActiveRecord::Base
translates :name
####### Validations ---------------------------------------------------------
validates :name, presence: true, length: { in: 2..150 }
####### more code
end
Factory:
FactoryGirl.define do
factory :candidate_branch do
sequence(:id) { |id| id }
sequence(:name) { “trying out" }
end
end
Test:
require 'rails_helper'
RSpec.describe CandidateBranch, type: :model do
context "Validate" do
it "has a valid factory" do
record = FactoryGirl.attributes_for(:candidate_branch)
puts "parameters => #{record.inspect}"
record = CandidateBranch.create record
puts "parameters => #{record.inspect}"
expect(record).to be_valid
end
end
end
Logs:
▶ RAILS_ENV=test bundle exec rspec spec/models/candidate_branch_spec.rb
"parameters => {:id=>1, :name=>\"trying out\"}"
F
Failures:
1) CandidateBranch Validate has a valid factory
Failure/Error: record CandidateBranch.create record
ActiveRecord::StatementInvalid:
Mysql2::Error: Field 'name' doesn't have a default value: INSERT INTO `candidate_branches` (`id`, `created_at`, `updated_at`) VALUES (1, '2015-02-18 12:27:57.486623', '2015-02-18 12:27:57.486623’)
Mysql Transaction:
(0.3ms) BEGIN
CandidateBranch::Translation Load (0.5ms) SELECT `candidate_branch_translations`.* FROM `candidate_branch_translations` WHERE `candidate_branch_translations`.`candidate_branch_id` = 1
(0.4ms) SAVEPOINT active_record_1
SQL (0.6ms) INSERT INTO `candidate_branches` (`id`, `created_at`, `updated_at`) VALUES (1, '2015-02-18 12:27:57.486623', '2015-02-18 12:27:57.486623')
Mysql2::Error: Field 'name' doesn't have a default value: INSERT INTO `candidate_branches` (`id`, `created_at`, `updated_at`) VALUES (1, '2015-02-18 12:27:57.486623', '2015-02-18 12:27:57.486623')
(0.2ms) ROLLBACK TO SAVEPOINT active_record_1
(0.3ms) ROLLBACK
As seen in the record, although among the parameters attribute ‘name' is defined when creating the record in the database query, the translation table obviously finds nothing and then try to create the registry without the field translated, which fails.
However, if we comment translation statement in the model…
class CandidateBranch < ActiveRecord::Base
#translates :name
####### Validations ---------------------------------------------------------
validates :name, presence: true, length: { in: 2..150 }
####### more code
end
▶ RAILS_ENV=test bundle exec rspec spec/models/candidate_branch_spec.rb
"parameters => {:id=>1, :name=>\"trying out\"}"
"parameters => #<CandidateBranch id: 1, name: \"trying out\", created_at: \"2015-02-18 12:29:09\", updated_at: \"2015-02-18 12:29:09\”>"
MySQL transaction:
(0.3ms) BEGIN
(0.4ms) SAVEPOINT active_record_1
SQL (0.5ms) INSERT INTO `candidate_branches` (`id`, `name`, `created_at`, `updated_at`) VALUES (1, 'trying out', '2015-02-18 12:29:09.195756', '2015-02-18 12:29:09.195756')
(0.3ms) RELEASE SAVEPOINT active_record_1
(0.4ms) ROLLBACK
Is it a bug? Am I doing something wrong? Did this happened to anyone else?
I answer myself. Globalize does not fill translated fields in the table base model, what it does is move these fields to their table translations. This means that the validation of the attribute must be applied on the fields translated as the original field is empty.
As you found out yourself, globalize moves the translated values to the ..._translations table.
Your error message is caused by a NOT NULL database constraint on the base table, that was obviously set by a former migration with option null: false.
To make your example work, you need to add to your migration:
change_column_null :candidate_branches, :name, true
I have an rails application where i used slugged urls. How can i test those urls with Rspec.
rake routes is generating following results
new_user_session GET /login(.:format) sessions#new
user_session POST /login(.:format) sessions#create
bays GET /:slug/bays(.:format) bays#index
POST /:slug/bays(.:format) bays#create
new_bay GET /:slug/bays/new(.:format) bays#new
edit_bay GET /:slug/bays/:id/edit(.:format) bays#edit
bay GET /:slug/bays/:id(.:format) bays#show
PATCH /:slug/bays/:id(.:format) bays#update
PUT /:slug/bays/:id(.:format) bays#update
DELETE /:slug/bays/:id(.:format) bays#destroy
Now when i run rpec for bays controller, i caught with following error.
Failure/Error: get :index, {}, valid_session
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"bays"}
Using
rspec-rails (3.0.1)
rails (4.0.0)
As you have defined it... bays index requires a :slug parameter. According to the error message, you have passed no slug to this route. You must either pass :slug (eg get :index, :slug => "1234", valid_session) or redefine the route to not need it.
for testing routes in rspec... That's a basic part of rspec: rspec routing specs
in your case it'd be something like:
{ :get => "/1234/bays" }.
should route_to(
:controller => "bays",
:slug => "1234"
)
For rspec 3.x you can go with:
scenario 'get correct slug', :type => :routing do
expect(get("/models/slug")).to route_to(:controller => "controller_name", :action => 'action_name', :id => "your_slug_name")
end
Note the type: :routing definition before the block.
More info here.
I'm stuck (again!) at Chapter 9 (this time in section 9.2.2) of the Rails tutorial. I am getting
bundle exec rspec spec/
................................FFF........................
Failures:
1) Authentication authorization as wrong user submitting a GET request to the Users#edit action
Failure/Error: before {sign_in user, no_capybara: true}
NoMethodError:
undefined method `new_remember_token' for #<User:0x007f8181815448>
# ./spec/support/utilities.rb:13:in `sign_in'
# ./spec/requests/authentication_pages_spec.rb:71:in `block (4 levels) in <top (required)>'
The other 2 errors are of the same type.
Here is spec causing the errors:
describe "as wrong user" do
let(:user) {FactoryGirl.create(:user)}
let(:wrong_user) {FactoryGirl.create(:user, email: "wrong#example.com")}
before {sign_in user, no_capybara: true}
describe "submitting a GET request to the Users#edit action" do
before {get edit_user_path(wrong_user)}
specify { expect(response.body).not_to match(full_title('Edit user'))}
specify { expect(response).to redirect_to(root_url)}
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user)}
specify { expect(response).to redirect_to(root_url)}
end
end
And here is the method (utilities.rb) the error message is complaining about:
def sign_in (user, options={})
if options[:no_capybara]
# Sign in when not using Capybara
remember_token = User.new_remember_token
cookies[:remember_token]
user.update_attribute(:remember_token, User.digest(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
The code for the model (User.rb) is here:
class User < ActiveRecord::Base
before_save { self.email = email.downcase}
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
validates :password, length: {minimum: 6}
has_secure_password
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.digest(User.new_remember_token)
end
end
I had previously trouble with the sign_in method but it miraculously disappeared. What am I doing wrong?
I finally found the culprit for the erratic test results that I have been observing in this case and, quite likely, on previous occasions (Failure/Error: sign_in user undefined method `sign_in', Rails named route not recognized). The problem seems to be that rails does not clear by default the cache between tests. Which is, actually, downright scary. It seems you cannot really trust the test results. I realised this by commenting out the method that rails was complaining about and re-running the test. The error persisted which meant one thing - rspec was simply working with some cached versions of the files and thus disregarding the changes which I am making. So even if the tests pass you can't be sure that they really do. This is really bizarre. After realising the problem with a bit of googling I found how to force rails to clean the cache - check jaustin's answer here: is Rails.cache purged between tests?