Overriding Rails Error (Validation) Messages from Gems/Plugins - ruby-on-rails

Is there a generally accepted way of overriding error (validation) messages from a gem/plugin in rails?
For example, I'm using the ActiveMerchant gem, and if someone puts in an American Express credit card number, but selects 'MasterCard', I get a not-very-descriptive "Type is not the correct card type" error.
I can easily get around this by doing something like this:
def validate_card
unless credit_card.valid?
credit_card.errors.full_messages.each do |message|
if message =~ /is not the correct card type/i
errors.add_to_base "This credit card number is invalid.
Please ensure that you selected the correct type."
else
errors.add_to_base message
end
end
end
end
but this technique quickly becomes unmaintainable and is clearly (at least in my opinion) far from 'best-practices'.
Similarly, I could unpack the ActiveMerchant gem and hack it up to put in my own custom error messages, but that also seems unmaintainable as it would require the same hacks to added into future, unpacked, versions of ActiveMerchant.

In all honesty, you're best bet is to either rewrite parts of the gem/plugin to suit your needs. Unfortunately if you decide to update the gem/plugin at any time you will lose your changes.
However, Ruby is a dynamic language, so classes can be reopened, and you can override methods of any module/class from any file. Through the magic of open source, I've tracked down the module/class/method you'd need to meddle with to achieve your goal.
Put the following in a file and ensure it gets loaded after ActiveMerchant (how to load it is dependent on whether you're using the plugin or the gem)
module ActiveMerchant
module Billing
class CreditCard
private
def validate_card_number #:nodoc:
errors.add_to_base "This credit card number is invalid. \n" +
"Please ensure that you selected the correct type." unless
CreditCard.valid_number?(number)
end
end
end
end
N.B.: This method relies on ActiveMerchant internals, which is generally a bad idea. But I see it as the lesser of two evils to maintaing your own version of ActiveMerchant. Should you update the gem/plugin and something the above code relies on has changed, it could break in odd ways.

Related

Monkey patching a core class with business logic with Rails

I have a monkeypatched of ActiveRecord find with some business logic, for example:
# lib/core_extensions/active_record/finder_methods/finder.rb
module ActiveRecord
module FinderMethods
def find(*args)
return super if block_given?
#... business logic code => my_error_control = true
raise "My Error" if my_error_control
retorn = find_with_ids(*args)
end
end
end
retorn
I have not seen many examples like this, and this causes me a doubt:
Where should finder.rb be?
In this example, this file is in lib/core_extensions/... but if it contains business logic, I think finder.rb should lives in the folder app/core_extensions/ isn't it?
Edited, after Sergio Answer
things like this, are a bad practice?
# lib/core_extensions/nil_class/image_attributes.rb
# suport for product images attributes
class NilClass
def main_image(size,evita_video)
"/images/paperclip_missing/original/missing.png"
end
end
Where should finder.rb be?
Ultimately, it doesn't matter. It only matters that this code gets loaded. This mix of patching base libraries and adding business logic there looks like something that MUST be documented thoroughly (in the project's wiki or something like that). And if it is documented, then it doesn't matter. The code is where the documentation says it is.
That being out of the way, here's a design suggestion:
when user seeks a Family Family.find(params[family_id],session[:company_id]), this find will compare the company of the family result family.company witht the parameter
Why not do something like this:
family = current_company.families.find(params[:family_id])
where current_company can be defined as #current_company ||= Company.find(session[:company_id])
Here, if this company doesn't have this family, you'll get an exception.
Same effect*, only without any patching. Much more futureproof. You can even add a couple of rubocop rules to ensure that you never write a naked Family.find.
* it's not like you add that patch and rest of your code magically acquires super-powers. No. You still have to change all the finders, to pass that company id.
It's the first time I see such case :). I'd put it in app/core_extensions and check if live reloading works correctly with it. If not, I'd move it to lib/. (It's just a heuristic)
Edit:
Instead of extending NilClass I'd rather use regular NullObjects. It's really less surprising and easier to understand.
https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object

Rails flash messages: is there a good reason they work the way they do?

The Rails 'flash' message system is very useful, but I've always found the details of its implementation make its use awkward.
Specifically, the flash messages are cleared at the end of a response. This means that you have to work out whether they're going to be used in a direct page render or a redirect, then inform the flash system by using 'now' if it's not a redirect.
This seems overly complex and error-prone.
Occasionally I find myself building things that need to exhibit flash-like behaviour, and the process I use is slightly different:
class FlashlikeStore
attr_accessor :session
def initialize(session)
session[:flashlike] ||= []
self.session = session
end
def add(name, data)
self.store << { name: name, data: data }
end
def read
session.delete(:flashlike).to_json
end
def any?
store && store.any?
end
protected
def store
session[:flashlike]
end
end
With a little syntactic sugar in the application helper I can easily add my name-value pairs, and the act of reading it deletes the data. (In this case I'm actually reading this in via AJAJ, but that isn't important here.)
The upshot is that messages are only ever read once, with no need up-front to determine or guess when they're going to appear. They're read when they're needed, then they go away.
One could argue, I suppose, that a call to an object shouldn't both read data and change state, so the 'read' method could be split into a 'read' and a 'wipe' if you wanted to be purist that way. (Although you probably wouldn't be using Rails if you were.)
So the question is this: am I missing something? Is there a compelling reason for the way Rails flash messaging works, with its need for the 'now' method?

Using a/an on an I18n message

I have the following error message on my en.yml file:
invalid_transition: "Cannot %{action} a %{state} timesheet"
Problem is that sometimes the state can be approved, other times it can be rejected.
With that, the error can end up being mispelled like "Cannot submit a approved timesheet", instead of "an approved timesheet".
Does Rails provide this flexibility in I18n?
The most simple answer to your question, I think, would be to pass in the entire state with the correct indefinite article together.
There's a question that looks at how to prepend "a" or "an" depending on a given word. A short answer is that there's a gem indefinite_article that does it.
Your translation then becomes:
invalid_transition: "Cannot %{action} %{state_with_article} timesheet"
Then call the I18n.t and pass in "a rejected" or "an approved" as a variable to interpolate.
However, if you want to get your hands a bit dirtier you may be able to use the i18n-inflector gem and it's companion i18n-inflector-rails gem. For many languages the choice is more complicated than in English because of different genders and tenses affecting the choice of indefinite article. (Disclaimer: I've not used either of those gems but they look like they would help you solve the problem).

Rails: Credit Card Validator gem - need good example of how to use it

I am looking to use the credit card validator gem to validate credit card numbers and types. I'm still relatively new to rails and even though I've taken a look at the github documentation I am still trying to work out how to best use this gem. Would I need to put some code into the model? If so, how exactly? Do I need to use a validate_with?
I've done a few google searches but can't seem to find any examples of how best to use this gem and where to put the code.
Seems that my reputation is getting shot on here :(
Here is what I did. In my creditcard model I added the following line:
validates_with ValidateCreditCard
I then created the file lib/validate_credit_card.rb as follows:
class ValidateCreditCard < ActiveModel::Validator
def validate(record)
if !CreditCardValidator::Validator.valid?(record.card_number.to_s)
record.errors[:base] << "The credit card number is not valid."
end
end
end
I also had to put the following line into my config/application.rb
config.autoload_paths += %W( #{config.root}/lib )
This seems to be working, however, if this the best way of doing this?
Also, even though I can see the error message when I enter an invalid credit card number, the credit card field doesn't get surrounded in red, so I need to look into the whole errors[:base] and how that works.
Would appreciate some feedback in regards to this being the right way of doing this type of validation.
Many thanks.
Here is how I got this to work.
I added the following lines to my creditcard model:
validate :validate_credit_card
private
def validate_credit_card
if !CreditCardValidator::Validator.valid?(:card_number.to_s)
errors.add(:card_number, "is not valid.")
end
end

Rails assert that form is valid

What's the best practices way to test that a model is valid in rails?
For example, if I have a User model that validates the uniqueness of an email_address property, how do I check that posting the form returned an error (or better yet, specifically returned an error for that field).
I feel like this should be something obvious, but as I'm quickly finding out, I still don't quite have the vocabulary required to effectively google ruby questions.
The easiest way would probably be:
class UserEmailAddressDuplicateTest < ActiveSupport::TestCase
def setup
#email = "test#example.org"
#user1, #user2 = User.create(:email => #email), User.new(:email => #email)
end
def test_user_should_not_be_valid_given_duplicate_email_addresses
assert !#user2.valid?
end
def test_user_should_produce_error_for_duplicate_email_address
# Test for the default error message.
assert_equal "has already been taken", #user2.errors.on(:email)
end
end
Of course it's possible that you don't want to create a separate test case for this behaviour, in which case you could duplicate the logic in the setup method and include it in both tests (or put it in a private method).
Alternatively you could store the first (reference) user in a fixture such as fixtures/users.yml, and simply instantiate a new user with a duplicate address in each test.
Refactor as you see fit!
http://thoughtbot.com/projects/shoulda/
Shoulda includes macros for testing things like validators along with many other things. Worth checking out for TDD.
errors.on is what you want
http://api.rubyonrails.org/classes/ActiveRecord/Errors.html#M002496
#obj.errors.on(:email) will return nil if field is valid, and the error messages either in a String or Array of Strings if there are one or more errors.
Testing the model via unit tests is, of course, step one. However, that doesn't necessarily guarantee that the user will get the feedback they need.
Section 4 of the Rails Guide on Testing has a lot of good information on functional testing (i.e. testing controllers and views). You have a couple of basic options here: check that the flash has a message in it about the error, or use assert_select to find the actual HTML elements that should have been generated in case of an error. The latter is really the only way to test that the user will actually get the message.

Resources