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 }
Related
I am trying to add a multiple date field (unlimited cardinality) in my custom module configuration form.
$form['holidayForm']['holidays'] = [
'#type' => 'date',
'#multiple' => TRUE,
'#title' => $this->t('Holidays'),
'#default_value' => $config->get('holidayForm.holidays'),
];
As you can see, I have added the #multiple property but I still get a single field
Is there a different property that I needed to add.
I'm running Rails 4.2.3. I have a model, Volunteer, with a serialized attribute, participated. After object creation, I can't update the participated attribute:
> #volunteer = Volunteer.new( participated: {'citizenship' => '', 'daca' => ''} )
=> #<Volunteer id: 1, participated: {'citizenship' => '', 'daca' => ''}>
> #volunteer.participated['citizenship'] = 'test'
=> {'citizenship' => 'test', 'daca' => ''}
> #volunteer
=> #<Volunteer id: 1, participated: {'citizenship' => 'test', 'daca' => ''}>
> #volunteer.changed?
=> true
> #volunteer.save
=> true
> Volunteer.last
=> #<Volunteer id: 1, participated: {'citizenship' => '', 'daca' => ''}>
As you can see, the database isn't being updated. I was originally running rails 4.2.0. At that time, changed? wasn't even returning true--in this scenario, it was returning false. After some searching, this pull request on GitHub led me to suspect that the issue might be a Rails bug. Updating to rails 4.2.3 fixed the problems with changed?, but the database still isn't updating.
Any suggestions would be greatly appreciated! I've sunk several hours into this so far.
For what its worth:
class Volunteer < ActiveRecord::Base
serialize :participated
end
class CreateVolunteers < ActiveRecord::Migration
def change
create_table :volunteers do |t|
t.text :participated
t.timestamps null: false
end
end
end
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 }
Let me explain first my model structure:
i have a status model:
class Status
include Mongoid::Document
include Mongoid::Search
include Mongoid::Timestamps
field :status_code, type: Integer
field :status_description, type: String
validates :status_code, :status_description, :transactiontype, :presence => true
belongs_to :transactiontype, :class_name => 'Transactiontype'
has_many :transactions, :class_name => 'Transaction', autosave: false
search_in :status_code, :status_description, :transactiontype => :transaction
def self.getStatus(transactiontype)
statuses = Status.where(:transactiontype_id => transactiontype).all
stats = []
puts "DATE DASHBOARD: #{Time.now.beginning_of_day} to #{Time.now.end_of_day}"
statuses.each do |status|
transactions = status.transactions.dateRange(Date.today.beginning_of_day, Date.today.end_of_day)
if transactions.length > 0
status.transactions = transactions
stats.push(status)
end
end
puts "SIZE : #{stats.size}"
stats
end
etc..
end
then i have another model called transactions:
class Transaction
include Mongoid::Document
include Mongoid::Search
include Mongoid::Timestamps
field :ref_no, type: String
field :trans_date, type: DateTime
belongs_to :status, :class_name => 'Status'
belongs_to :transactiontype, :class_name => 'Transactiontype'
validates :ref_no, :trans_date, :status, :presence => true
def self.dateRange(startdate,enddate)
puts "DATE : #{startdate} to #{enddate}"
if !startdate.blank?
where(:created_at => {"$gt" => startdate.beginning_of_day, "$lt" => enddate.end_of_day})
# where(:trans_date.gte => startdate.beginning_of_day, :trans_date.lte => enddate.end_of_day)
end
end
etc..
end
the weird part is that:
when im trying to execute:
Status.getStatus(params[:transactiontype_id])
i received the correct output but the transactions associated with the Status is being updated and each records before the filtered date is being updated with null status_id.
i already tried to add autosave: false but nothing works
can someone help me with this?
the solution is to convert the active record to json first
def self.getStatus(transactiontype)
statuses = Status.where(:transactiontype_id => transactiontype).all
stats = []
puts "DATE DASHBOARD: #{Time.now.beginning_of_day} to #{Time.now.end_of_day}"
statuses.each do |status|
ar_status = status.as_json
ar_status['transactions'] = status.transactions.dateRange(Date.today.beginning_of_day, Date.today.end_of_day)
if ar_status['transactions'].length > 0
stats.push(ar_status)
end
end
puts "SIZE : #{stats.size}"
stats
end
for some reason.. its auto saving the records.
Here is a model that I'm using, I've simplified it a bit down to the simplest form that still fails my example:
class User < ActiveRecord::Base
after_create :setup_lists
def setup_lists
List.create(:user_id => self.id, :name => "current")
List.create(:user_id => self.id, :name => "master")
end
end
And I'd like to spec the example as follows:
require 'spec_helper'
describe User do
before(:each) do
#user = Factory(:user)
end
describe "#setup_lists" do
before(:each) do
List.stub(:create).with(:name => "current")
List.stub(:create).with(:name => "master")
it "creates a new master list" do
List.should_receive(:create).with(:name => "master")
end
it "creates a new current list" do
List.should_receive(:create).with(:name => "current")
end
end
end
Which I expected would work just fine, but I am left with the following error:
Failures:
1) User#setup_lists creates a new master list
Failure/Error: List.should_receive(:create).with(:name => "current")
(<List(id: integer, name: string, created_at: datetime, updated_at: datetime, user_id: integer) (class)>).create({:name=>"current"})
expected: 1 time
received: 0 times
# ./spec/models/user_spec.rb:44
2) User#setup_lists creates a new current list
Failure/Error: List.should_receive(:create).with(:name => "master")
(<List(id: integer, name: string, created_at: datetime, updated_at: datetime, user_id: integer) (class)>).create({:name=>"master"})
expected: 1 time
received: 0 times
# ./spec/models/user_spec.rb:48
Can anybody help me understand why this is happening?
Three issues:
1) The User object is created before setting the message expectation, so should_receive never gets to see the message;
2) You're stubbing out methods for which you're also setting expectations. You want to stub out methods for which you don't set expectations, but which are needed for the test to pass
3) You need to pass in all the parameters
To fix, create the User object after setting the expectaion, and stub out each method in turn (because your model calls List.create twice):
describe User do
describe "#setup_lists" do
it "creates a new master list" do
List.stub(:create).with(:user_id=>1,:name => "current")
List.should_receive(:create).with(:user_id=>1,:name => "master")
#user = User.create
end
it "creates a new current list" do
List.stub(:create).with(:user_id=>1,:name => "master")
List.should_receive(:create).with(:user_id=>1,:name => "current")
#user = User.create
end
end
end
While it's really an issue of style, it makes more sense to use a real User object here rather than a factory, since you're testing the model itself.
zetetic's answer is awesome, but if you want something a bit quicker (and still works), I'd recommend using the shoulda-callback-matchers gem. It's a complete set of matchers that make testing callbacks easier. I'm all about easy & reducing boilerplate. You can see some examples in my RSpec model testing skeleton if you care to look.
Either way gets the job done!