I have a Rails 4 application and I am using the attr_encrypted gem to store SSN in an encrypted form in the DB.
What I am trying to do is only display the last 4 digits when editing / showing the form. So essentially once the SSN has been saved, users can't see the full SSN in the form. So I've tried doing the following,
class PaymentData < ActiveRecord::Base
attr_encrypted :ssn, key: 'secret'
alias_method :org_ssn, :ssn
def ssn
org_ssn.reverse # just a way of testing if this works
end
end
What I am seeing that the on the show form, which uses <%= #payment_data.ssn %>, it works fine. But on my edit form which is using <%= f.input :ssn %>, the edit field is prepopulated with the full SSN.
Note: I am using the simple_form gem for my forms, but if I use the basic, <%= f.text_field :ssn %> syntax, I see the same full SSN.
What am I doing wrong here?
This is what I ended up doing,
def obfuscated_ssn
return ssn unless ssn && ssn.size > 3
ssn.last(4).rjust(ssn.length, '*')
end
def obfuscated_ssn=(ssn)
self.ssn = ssn unless ssn.include?('*')
end
This is a bit hacky, but it displays the SSN with a redacted string, but then doesn't save a string with the redacted bits, *, to keep bad data from getting the database.
I suggest something different.
Keep your obfuscated_ssn method from your answer, it makes sense to have it in the model. However overriding the setter like this... well I'd go a different way.
What I don't really like, is that you are doing validations in the setter. I checked the gem page but couldn't find anything related to validation, but normally your model would include validation rules like
validates_length_of :ssn, :minimum => 5, :maximum => 20, :allow_blank => true
I don't know if the ssn gem is clever enough to run the validations only on non-encrypted data though...
But anyway here's how I would tweak your form/controller :
Your form
you would need to figure out how to display one field and submit to a completely different one
I suggest changing the default value shown by the field (Ruby doc for text_field :)
f.text_field(:ssn, :value => f.object.obfuscated_ssn)
Now I'm not sure about the behavior you want. Is the user supposed to always write the ssn field? Or do you want to leave it unchanged in case the user doesn't enter anything ? If so I'd put some checks on your controller
Controller
def model_params
if params[:your_model][:ssn] == #model.obfuscated_ssn
params[:your_model].delete(:ssn) # Make sure your model doesn't get updated with obfuscated ssn
end
params.require(:your_model).permit(:ssn, etc.)
end
Related
I have a form which include number fields
<%= f.number_field :contribution_to_sales, class: 'form-control',:pattern=>["\d+"] %>
It allows 2.0 but I want it should not allow 2.0 .
How to do that?
You are missing anchors on your regular expression which causes the "2" in "2.0" to be matched (on some browsers). The regex to use is:
<%= ... pattern: "^\d+$" %>
You should probably be doing the validation on the model as well, as the HTML5 pattern attribute may not be obeyed by all browsers. Simply add:
class YourModel < ActiveRecord::Base
validates :contribution_to_sales, numericality: { only_integer: true }
...
end
Add the following to the Model this form relies on:
validate :format_numbers
def format_numbers
if self.number_field.include?(',') || self.number_field.include?('.')
errors.add(base: "Do not add commas or periods please")
end
end
That is one way to validate however I prefer the much more user friendly way which removes certain things a user may add that I know I don't want and then continue to process the form instead of reporting back with an error. This would be done by removing commas and since you don't want periods either I would imagine we can strip anything to the right of a period out as well. To do it this way you would do the following in your Model instead of the above:
validate :format_numbers
def format_numbers
self.number_field = self.number_field.gsub(/\.\d+/, '') #This will remove any period and all numbers to the right of that period.
self.number_field = self.number_field.gsub(',','') #This will remove all commas throughout.
end
This provides a smoother user experience since they can now type 2,000.00 in the form field and it will be saved as 2000
id like to make a multistep form with rails using the edit and update actions. so i would like it to be like step 1 of the form, and the user fills in his name, address, and phone number. then the user clicks save and continue and he then fills out his shipping address and then clicks save and continue and fills out his billing address. i saw ryan bates version, but its not what im looking for. i would like the order to be saved after the first step so if the user doesnt finish their form, i can call them and ask them what went wrong. can anyone refer me to a tutorial or give me an example of how to make an order form using the edit and update methods?
Typically this means you'll need to put conditions on your model validations. Some subset of your validations should apply to each form page:
class User
validates_presence_of :first_name
validates_presence_of :last_name
validates_presence_of :street, :if => :on_page_two?
validates_presence_of :city, :if => :on_page_two?
validates_presence_of :postal_code, :if => :on_page_two?
validates_presence_of :state, :if => :on_page_two?
validates_presence_of :country, :if => :on_page_two?
validates_acceptance_of :terms_and_conditions, :if => :on_page_three?
def on_page_two?
# whatever you need to determine the page number
end
def on_page_three?
# whatever you need to determine the page number
end
end
It's not pretty but I highly recommend a pattern like this. Anything more complicated and you'll need to rewrite it when your signup flow changes.
There are different approches to this problem.
My particular prefered solution is to implement something like a "State Machine" in the model. This way, I can persist the progress of a, per example, a multistep form, without having to hasle with more actions than new/create and edit/update.
I'm currently working on a heavy long State Machine application using the State Machine gem for rails.
Hope it helps you!
There's a great Railcast on creating multi-step wizard forms, which you can find here. It uses the Wicked gem.
Let's say I have a form_tag in a view gathering a view user input . With this input that I get through the params hash, I want to verify some of htis information such as email phone name.
Can I just verify in the controller or is this a bad thing? I don't plan to save the thing I get to a db or anything, it's just getting put into an email and sent off, but I want to verify these things before it's sent.
Thank you.
EDIT:
Found this http://www.viddler.com/explore/rails3/videos/6/
class Test
include ActiveRecord::Validations
validates_presence_of :name, :email
attr_accessor :name, :email
end
You can use the model for whatever you need that is related to the object and don't need to save it. Keeping stuff like this in the model is desirable in order to keep controller tidy. Say you have a user model:
#controller
#user.new params[:user]
#user.check_valid
#user.save # is optional
#user.rb
def check_valid
!email.blank? and !phone.blank?
end
I need a "I accept terms of service" checkbox on a page, it has to be checked in order for the order to proceed. It seems hence illogical to have a column in the database to match this (whether user has accepted or declined terms).
I am using the form helper like this in my view:
<%= check_box("client", "terms") %>
And in my model:
validates_acceptance_of :terms
At the moment it is not working at all.
This seems like a really common piece of code, yet I can't find it used anywhere without having the terms in the model. Else I could use javascript to validate it, but would prefer to keep it all the in model.
This should work fine, without a database column or attr_accessor:
http://guides.rubyonrails.org/active_record_validations.html#acceptance
I would be inclined to check your params hash is as it should be i.e. that the 'terms' attribute is being passed within the 'client' hash, perhaps try adding raise params.inspect on your controller create action to help you debug?
What about having an attr_accessor :terms in your Client model?
I had this working with these settings:
In the controller, I have added :terms_of_service as a permitted field:
def application_params
params.require(:application).permit(. . . , :terms_of_service)
end
In the model:
attr_accessor :terms_of_service
validates :terms_of_service, :acceptance => true
In the view:
<%= f.check_box("terms_of_service", :checked => false) %>
attr_accessor :terms will do the trick nicely.
Either go with #neutrino's solution, or to reset :terms to "not accepted" if you need to redisplay the form (because validation may fail), use this:
def terms
nil
end
def terms=(val)
# do nothing
end
First Item
I Want to validate a field to make sure it is unique (in the last 6 months) before saving it to the database.
I am thinking I should use validates_uniqueness_of :field, case_sensitive => false, Scope => ...
For my application it only has to be unique if, it was used <6 months ago.
Thinking to compare it to created_at, but don't really know how to go about it.
Second Item
I think I should somehow use .strip to remove any spaces before or after the text that the use may have put in accidentally (I know that these extra spaces are used by default in rails and if they are there can make a filed unique.)
If anyone has any hints on how this should be done correctly I really would appreciate it.
validates_uniqueness_of works by checking if a record already exists with the same value of the given field within the given scope. :scope lets you define the scope (obviously) of the uniqueness; for instance, if I was creating blog software and wanted to only allow a post title to be used once per blog, I could say validates_uniqueness_of :title, :scope => :blog_id -- without the scope, I'd only be allowing each title to be used once across the entire system. :scope won't let you do a complex check, like that which you desire.
What you're probably need to do is create your own validation function to check the uniqueness of the field in question within the given timeframe (code goes within the model):
validate :field_must_be_unique_within_six_months
def field_must_be_unique_within_six_months
return if field.blank?
num_duplicates = self.class.count(:conditions => ["field = ? AND created_at < ?", self.field, 6.months.ago])
if num_duplicates > 0
errors.add(:field, :taken)
end
end
The field_must_be_unique_within_six_months method will work similarly to validates_uniqueness_of, in that it will add an error message if there is already a record with the same given field, but with the added condition that it will also check the date. The validate :field_must_be_unique_within_six_months will add the method to the validation process when a record is saved.
To validate multiple fields at the same time without violating DRY, you could use validates_each to do something like the following:
validates_each :field1, :field2 do |record, attr, value|
if record.class.exists?(["#{attr.to_s} = ? AND created_at < ?", value, 6.months.ago])
errors.add(attr, :taken)
end
end
In the above block, record is the record being validated, attr is the attribute (so field1, field2, etc.) and value is the value of that attribute.
You can probably do something like this:
def validate
errors.add(:field, 'blah blah') if is_used_recently && !has_unique_field?
end
def has_unique_field?
Model.exists?(['field = ? and created_at > ?', self.field, 6.months.ago])
end
def is_used_recently
self.created_at < 6.months.ago || self.new? # i don't know if created_at would be set by this point
end
Alternatively you might want to create a new validation handler, or extend the existing one to pass in a :within option if that's something you're going to be doing often.
To get rid of leading and trailing white space the method you want is 'strip'. You can run this on all your fields by doing something like:
before_validation :clean_up_whitespace
def clean_up_whitespace
self.some_field.strip! # this does the strip in place
end
I hope this helps, let me know if I've made any mistakes!