NoMethodError on method added to Date class - ruby-on-rails

I added two methods to the Date class and placed it in lib/core_ext, as follows:
class Date
def self.new_from_hash(hash)
Date.new flatten_date_array hash
end
private
def self.flatten_date_array(hash)
%w(1 2 3).map { |e| hash["date(#{e}i)"].to_i }
end
end
then created a test
require 'test_helper'
class DateTest < ActiveSupport::TestCase
test 'the truth' do
assert true
end
test 'can create regular Date' do
date = Date.new
assert date.acts_like_date?
end
test 'date from hash acts like date' do
hash = ['1i' => 2015, '2i'=> 'February', '3i' => 14]
date = Date.new_from_hash hash
assert date.acts_like_date?
end
end
Now I am getting an error that: Minitest::UnexpectedError: NoMethodError: undefined method 'flatten_date_array' for Date:Class
Did I define my method incorrectly or something? I've even tried moving flatten_date_array method inside new_from_hash and still got the error. I tried creating a test in MiniTest also and got the same error.

private doesn't work for class methods, and use self.
class Date
def self.new_from_hash(hash)
self.new self.flatten_date_array hash
end
def self.flatten_date_array(hash)
%w(1 2 3).map { |e| hash["date(#{e}i)"].to_i }
end
end

Related

Rspec Rails - Stub Active Record Relation to iterate using :find_each

I'm trying to test if a specific attribute some_field gets assigned with correct values. I'm currently not persisting the records on the DB due to speed issues so I need a way thru stubs only.
Here's a sample method:
class Teacher < ApplicationRecord
has_many :students
def my_method
...
teacher.students.find_each do |s|
s.some_field[:foo] = 'bar'
s.save
end
end
end
Here's the test spec which is failing since :find_each only works with ActiveRecord Relations:
it 'assigns a correct value in some_field attribute' do
allow(teacher).to receive(:students).and_return([student1])
allow(student1).to receive(:save)
teacher.my_method
expect(student1.some_field).to eq({ foo: 'bar' })
end
Error:
NoMethodError:
undefined method `find_each' for #<Array:0x00007fa08a94b308>
I'm wondering if there's a way for this without persisting in DB?
Any help will be appreciated.
Also mock the find_each and return regular each instead.
let(:teacher) { double }
let(:students_mock) { double }
let(:student1) { double }
it do
expect(teacher).to receive(:students).and_return(students_mock)
expect(students_mock).to receive(:find_each) { |&block| [student1].each(&block) }
expect(student1).to receive(:save)
teacher.students.find_each(&:save)
end

Rspec, Factory girl and after_initialize

I'm trying to test this after_initialize callback which is for the item model (which has_many line_items):
after_initialize :build_default_items, unless: :line_items?
callback:
def build_default_items
LineOfBusiness.all.each do |lob|
line_items.new(line_of_business_id: lob.id)
end
end
My test looks like:
describe 'callbacks' do
let(:user) { create :user }
it 'should build default items' do
lob1 = LineOfBusiness.create(id:1, name: "Name1", eff_date: Date.today,exp_date: Date.tomorrow, create_user: user, update_user: user)
lob2 = LineOfBusiness.create(id:2, name: "Name2", eff_date: Date.today,exp_date: Date.tomorrow, create_user: user, update_user: user)
lob_count = LineOfBusiness.all.count # this is correct as 2
item = build :item
expect(item.line_items.count).to eq(lob_count)
end
end
Error message as follows:
expected: 2
got: 0
(compared using ==)
So its failing in the callback method, its seeing the LineOfBusiness.all as Nil
def build_default_items
LineOfBusiness.all.each do |lob| # <-- this is Nil so fails
line_items.new(line_of_business_id: lob.id)
end
end
Any ideas why its Nil in the callback method?
line_items.count will fire query to database, and as you are not saving line_items in after_initialize callback, spec will fail. Instead try using line_items.size.
expect(item.line_items.size).to eq(lob_count)

Rails: Adding to errors[:base] does not make record invalid?

In my Purchase model, I have a method that calculates the tax:
def calculate_tax
if self.shipping_address.state == State.new_york
corresponding_tax = Tax.find_by(zip_code: self.shipping_address.zip_code, state_id: self.shipping_address.state_id)
if corresponding_tax
self.tax = corresponding_tax.rate * (self.subtotal + shipping)
else
#HERE !!!
self.errors[:base] << "The zip code you have entered is invalid."
puts "errors = #{self.errors.full_messages}" #<-- this prints out the error in my log, so I know it's being run
end
else
self.tax = 0.00
end
end
This method is being called within this method:
def update_all_fees!
calculate_subtotal
calculate_shipping
calculate_tax #<-- being called here
calculate_total
save!
end
However, save! is saving the record successfully. Shouldn't it be throwing an exception? How would I make it so that save! fails when calculate_tax is in the second else block?
You can add custom validation methods with the validate directive. Here's may take on the code you posted:
class Purchase < ActiveRecord::Base
validate :new_york_needs_tax_record
def update_all_fees!
calculate_subtotal
calculate_shipping
calculate_tax
calculate_total
save!
end
private
def calculate_tax
if ships_to_new_york? && corresponding_tax
self.tax = corresponding_tax.rate * (self.subtotal + shipping)
elsif !ships_to_new_york?
self.tax = 0.00
else
self.tax = nil
end
end
def ships_to_new_york?
self.shipping_address.state == State.new_york
end
def corresponding_tax
Tax.find_by(zip_code: self.shipping_address.zip_code, state_id: self.shipping_address.state_id)
end
def new_york_need_tax_record
if ships_to_new_york? && !corresponding_tax
self.errors[:base] << "The zip code you have entered is invalid."
end
end
end
Edited for historical reasons. The first response didn't cover all scenarios.
But if you need to raise the error if there are any just do:
validate :taxes_scenario
def taxes_scenario
[Add any clause here that makes your scenario invalid]
end
So you can validate the taxes scenario and made sure your error is added properly.
Simply adding an error to the error list won't make the record fail. You have to have what's called a "validation" set up. There are some wonderful guides about it here that should help you through the process.
For example in this one you will probably want to add to your Tax model the following validation:
validates :zip_code, presence: true, numericality: true
once you have the validations set up, save! should automatically spit out an error when a model fails validation
In Rails 7:
class User < ApplicationRecord
validate :must_be_a_friend
def must_be_a_friend
if friend == false
# Does NOT work anymore
errors[:base] << 'Must be friends'
# DOES work
errors.add(:base, 'Must be friends')
end
end
end

Rails model.valid? flushing custom errors and falsely returning true

I am trying to add a custom error to an instance of my User model, but when I call valid? it is wiping the custom errors and returning true.
[99] pry(main)> u.email = "test#test.com"
"test#test.com"
[100] pry(main)> u.status = 1
1
[101] pry(main)> u.valid?
true
[102] pry(main)> u.errors.add(:status, "must be YES or NO")
[
[0] "must be YES or NO"
]
[103] pry(main)> u.errors
#<ActiveModel::Errors:[...]#messages={:status=>["must be YES or NO"]}>
[104] pry(main)> u.valid?
true
[105] pry(main)> u.errors
#<ActiveModel::Errors:[...]#messages={}>
If I use the validate method from within the model, then it works, but this specific validation is being added from within a different method (which requires params to be passed):
User
def do_something_with(arg1, arg2)
errors.add(:field, "etc") if arg1 != arg2
end
Because of the above, user.valid? is returning true even when that error is added to the instance.
In ActiveModel, valid? is defined as following:
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
run_validations!
ensure
self.validation_context = current_context
end
So existing errors are cleared is expected. You have to put all your custom validations into some validate callbacks. Like this:
validate :check_status
def check_status
errors.add(:status, "must be YES or NO") unless ['YES', 'NO'].include?(status)
end
If you want to force your model to show the errors you could do something as dirty as this:
your_object = YourModel.new
your_object.add(:your_field, "your message")
your_object.define_singleton_method(:valid?) { false }
# later on...
your_object.valid?
# => false
your_object.errors
# => {:your_field =>["your message"]}
The define_singleton_method method can override the .valid? behaviour.
This is not a replacement for using the provided validations/framework. However, in some exceptional scenarios, you want to gracefully return an errd model. I would only use this when other alternatives aren't possible. One of the few scenarios I have had to use this approach is inside of a service object creating a model where some portion of the create fails (like resolving a dependent entity). It doesn't make sense for our domain model to be responsible for this type of validation, so we don't store it there (which is why the service object is doing the creation in the first place). However for simplicity of the API design it can be convenient to hang a domain error like 'associated entity foo not found' and return via the normal rails 422/unprocessible entity flow.
class ModelWithErrors
def self.new(*errors)
Module.new do
define_method(:valid?) { false }
define_method(:invalid?) { true }
define_method(:errors) do
errors.each_slice(2).with_object(ActiveModel::Errors.new(self)) do |(name, message), errs|
errs.add(name, message)
end
end
end
end
end
Use as some_instance.extend(ModelWithErrors.new(:name, "is gibberish", :height, "is nonsense")
create new concerns
app/models/concerns/static_error.rb
module StaticError
extend ActiveSupport::Concern
included do
validate :check_static_errors
end
def add_static_error(*args)
#static_errors = [] if #static_errors.nil?
#static_errors << args
true
end
def clear_static_error
#static_errors = nil
end
private
def check_static_errors
#static_errors&.each do |error|
errors.add(*error)
end
end
end
include the model
class Model < ApplicationRecord
include StaticError
end
model = Model.new
model.add_static_error(:base, "STATIC ERROR")
model.valid? #=> false
model.errors.messages #=> {:base=>["STATIC ERROR"]}
A clean way to achieve your needs is contexts, but if you want a quick fix, do:
#in your model
attr_accessor :with_foo_validation
validate :foo_validation, if: :with_foo_validation
def foo_validation
#code
end
#where you need it
your_object.with_foo_validation = true
your_object.valid?

Strange behaviour when returning an array from class_eval'ed method

With Ruby 1.9.2, I'm using class_eval to extend a class.
def slugged(fields)
# assign string to variable only for easier debugging
method = <<-EOS
def slug_fields
#{ fields.is_a?(Array) ? fields.inspect : ":#{ fields }" }
end
EOS
class_eval method
end
This works fine as long as fields is a symbol (e.g. after slugged :name, slug_fields returns :name).
However, calling slugged with an array makes slug_fields returns nil (e.g. after slugged [:kicker, :headline], slug_fields returns nil).
Strangely, when debugging slugged, the string containing the to-be-created method looks exactly the way you would expect them to:
" def slug_fields\n [:kicker, :headline]\n end\n"
" def slug_fields\n :name\n end\n"
edit: as requested, a more complete version of what breaks for me:
module Extensions
module Slugged
extend ActiveSupport::Concern
included do
before_validation { |record| record.slug ||= record.sluggerize }
end
module ClassMethods
def slugged(fields)
# assign string to variable only for easier debugging
method = <<-EOS
def slug_fields
#{ fields.is_a?(Array) ? fields.inspect : ":#{ fields }" }
end
EOS
class_eval method
end
end
module InstanceMethods
def sluggerize
fields = slug_fields
slug_string = case
when fields.is_a?(Array)
fields.map { |f| self.send(f) }.join('-')
else
self.send fields
end
slug_string.parameterize
end
end
end
end
class Article < ActiveRecord::Base
include Extensions::Slugged
slugged [:kicker, :headline]
end
class Station < ActiveRecord::Base
include Extensions::Slugged
slugged :name
end
a = Article.new :headline => "this is a great headline!", :kicker => "attention-drawing kicker"
a.save # works, slug is set
s = Station.new :name => "Great Music"
s.save # TypeError: nil is not a symbol (in sluggerize where "self.send fields" is called)
Your code works fine for me under 1.9.2:
class Foo
class << self
def slugged(fields)
method = <<-EOS
def slug_fields
#{ fields.is_a?(Array) ? fields.inspect : ":#{ fields }" }
end
EOS
class_eval method
end
end
end
Foo.slugged :a
p Foo.new.slug_fields
#=> :a
Foo.slugged [:a,:b]
p Foo.new.slug_fields
#=> [:a, :b]
p RUBY_DESCRIPTION
#=> "ruby 1.9.2p180 (2011-02-18) [i386-mingw32]"
Can you please provide a complete, runnable, standalone test case that breaks for you?

Resources