rails text_field / text_ara empty string vs nil
Asked Answered
B

2

6

I'm not even sure if I have a problem, but I just don't like that my text_fields and text_areas get saved in the db as empty string instead of nil.

I'm playing with the null object pattern and just realized if I create a profile but don't fill in the location field and then call <%= @user.profile.location.upcase %> then it doesn't blow up since location is a string even it it's empty.

Is this the rails convention to have it this way? If so, then why? It's weird a bit since let's say there is a number_of_pets attr on the profile and then I try to call something like

<% if user.profile.number_of_pets.odd? %>
  <%= @user.profile.first_name %> has odd number of pets
<% end %>

then it blow's up since I can't call nil.odd?.

form as usual, so it will saved as empty string if not filled

<%= form_for @profile, url: user_profile_path do |f| %>
  <%= f.label :city %>
  <%= f.text_field :location, class: 'form-control' %>
  ......
Bobbinet answered 13/5, 2016 at 12:4 Comment(3)
you can write validation for thisBridwell
Amol, thanks for the answer, but this wasn't my question. I'd like to know what's the convention and why.Bobbinet
This has always bugged me too. For more predictable data (blank is NULL), I always use the nilify_blanks gem. I know this doesn't answer your question, but it is more of a nod of agreement.Lottielotto
J
5

The easiest work around is to use a gem like "strip_attributes" found here: https://github.com/rmm5t/strip_attributes

A custom workaround could be done by adding a before_save callback in your model that takes any values that are blank and sets them back to nil.

In your model for example:

before_save :my_nil_maker_method_lol

[...]

def my_nil_maker_method_lol
  if self.whatever_attribute.blank?
    self.whatever_attribute=nil
  end
end

Update:

Keeping blank fields from being saved could be done several ways such as described above or even deleting blank params in your controller before they hit the database.

The Rails way, when maintaining database integrity, is to always keep this logic inside your model. It makes it much easier to maintain and leaves much less room for surprises like if you were to modify the incoming parameters somewhere else.

As far as how it should be done in the model is really just a matter of what you as the developer expect to get for input. You can add a callback as shown above which maintains your db as you see fit or you can add a validates_presence_of validation that will return an error to the user if that field is left blank.

If you're asking whether you should be keeping empty strings from being inserted into the database at all, it is really up to you as the developer since there may be instances where you might want that information but in this case it sounds as though you're looking to restrict empty strings.

Jed answered 13/5, 2016 at 12:13 Comment(3)
bkunzi01 thanks for the answer, but this wasn't my question. I asked what's the convention and why.Bobbinet
I will just keep it as it is for the time being. At the moment I don't need any special stuff, just thought I missed something.Bobbinet
be careful when using whatever_attribute.blank? as that will return true if the attribute value is false (which in turn would cause that attribute not to be saved)Aeronautics
S
1

I am speaking from what I observed, and may not necessarily the Rails-convention.

When we do rails g post title content:text for example, you may remember that it does not have default: '' for both title(string) and content(text). This is already a hint to me that there is no Rails-convention regarding this, or specifically speaking every attribute by default is allowed to be NULL or nil.

The advantage of using NULL is that you can identify which records have those attributes set up.

Let's say we have an API server. If a client creates a post to our API server, we know which attributes are intended to have a value. Let's say something like the following:

client-1's POST params:

post: {title: 'Foo bar'}
  • if NULL-allowed, will create a Post(title: 'Foo bar', content: nil)
  • if default: '', will create a Post(title: 'Foo bar', content: '')

client-2's POST params:

post: {title: 'Foo bar', content: ''}
  • if NULL-allowed, will create a Post(title: 'Foo bar', content: '')
  • if default: '', will create a Post(title: 'Foo bar', content: '')

From above, notice that if we have default: '', then we cannot know if the client is actually intending to have an empty content value, because for both client-1 and client-2, the resulting content value for the post will have '' (empty) anyway. But if we have NULL-allowed attributes, then we can still identify if the client intended to not have a content value by not passing in the attribute in the parameters. This is an important use-case.

Depending on your project and the attribute's purpose, you may either use NULL-allowed or set default empty string for that attribute.

Now, the one main problem with NULL-allowed as you have encountered is that you cannot guarantee that every value will be a String, therefore your <%= @user.profile.location.upcase %> will raise an error in case location is NULL or nil.

This can be a little annoying especially if you have a chain of methods like your example

<%= @user.profile.location.upcase.downcase %>

This will be a problem if @user.profile.location is nil, because you'll have to gracefully ensure that it won't raise an error. And this will also be a problem if @user.profile is nil (let's just say you are allowing @user.profile to be nil). And normally, you'll do something like the following to make this work:

<% if [email protected]? && [email protected]? %> 
  <%= @user.profile.location.upcase.downcase %>
<% end %>

That if condition can still go on very long as you have longer chained methods to gracefully ensure it won't raise any error.

Using .try() will potentially "clean" this up. I use this a lot of times especially in template files. Solution will be cleaner like below, albeit potentially confusing for those who do not know:

 <%= @user.profile.try(:location).try(:upcase).try(:downcase) %>

If either .location or .upcase is nil, it will return nil, and not raise any undefined method ... for NilClass anymore.

Skurnik answered 13/5, 2016 at 13:49 Comment(1)
Thanks Jay-Ar, I don't like using either if or try, that's why I'm playing around with null object pattern. But I will leave the default "" settings for the fields.Bobbinet

© 2022 - 2024 — McMap. All rights reserved.