rails text_field / text_ara empty string vs nil - ruby-on-rails

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' %>
......

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.

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 !#user.profile.nil? && !#user.profile.location.nil? %>
<%= #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.

Related

Can i save a rails form values multiple times in database?

I dont know is it possible but i have a form and when i submit the form, i want the records to be saved multiple times in database. As of now, i am not worried about efficiency , but to get this thing going.
#message = Message.new
<%= simple_form_for #message do |f| %>
<%= f.input :description, as: :text, input_html: {rows: 7}, label: false, placeholder: "Type your message ..." %>
<%= f.submit "Submit", class: "btn btn-info" %>
<%end%>
I want the above form to be saved multiple times, like (i know its dumb)
for i in 1..4
#message.save
end
Intention:
My intention here is to send a single message to all group members at once. So i have the group_id and description now and when users submits the form, i want to iterate over all the users who are following the group_id and save the records with respective user_id's who all are following group.
I dont think i need validation for this.
You don't have group_id in the form so I'm not sure if it's somewhere else but you claim to have it so I'll just assume you do.
#group = Group.find(group_id)
#group.users.each do |user|
message_params = params[:message].merge(user_id: user.id)
Message.create(message_params)
end
UPDATE
So I did some digging, and found out that this is not as trivial as I originally thought.
When you call #message.save, rails will go behind the scenes to check if that record is a new record (i.e. call #message.new_record?) If it is not a new record, it will update the existing one instead of creating a new one. If you can override that, you could save the record multiple times.
The issue I found when trying to set the new_record attribute directly was it is only called on initialization ( when #message.new is called) and if the record does not get initialized then new_record defaults to false.
As others have pointed out in comments, there really shouldn't need to be a reason to save an identical record (which is why rails doesn't easily allow it). If you want to assign the same message to multiple users, set up associations to relate the two models. If you need to use the same message every time you do something, create it once then simply fetch it from the database whenever you need to use it.

Rails - Strong parameters with empty arrays

I'm sending an array of association ids, say foo_ids to my controller. To permit an array of values, I use:
params.permit(foo_ids: [])
Now, the problem is that if I send an empty array of foo_ids, the parameter is ignored. Instead of clearing all foos as an empty array should do, the association is left alone, because foo_ids isn't permitted.
This may be because an empty array is converted to nil in rails, and that nil value is ignored as strong parameters is looking for an array of scalar values, not a single scalar value.
Can anyone suggest a good way to solve this? Thanks!
Additional info
In an update controller action, I need to be able to handle two cases. I need to be able to set foo_ids to an empty array. I also need to be able to ignore foo_ids if I merely want to update another field. Setting foo_ids to an empty array if nil does not work for this second case.
This is quite late, but I just had this problem myself. I solved it by including both the scalar version and array version in the permit statement, like so:
params.require(:photo).permit(:tags, tags: [])
FYI - it has to have both in the same permit statement - if you chain them it'll get thrown out for some reason.
EDIT: I just noticed that an empty array submitted via this method will be turned into nil - I've now got a bunch of fields that should be empty arrays that are nil. So the solution I posted doesn't actually work for me.
EDIT the second: Thought I had already added this, but this problem is related to Rails performing deep_munge on params hashes. This comment explains how to fix it: https://stackoverflow.com/a/25428800/130592
The temporary solution I've come down to is:
params[:foo_ids] ||= [] if params.has_key?(:foo_ids)
params.permit(foo_ids: [])
Here, foo_ids is set to an empty array only if is passed. If it is not passed in the request, it is ignored.
I'm still hoping to find a better solution to this, as this sort of thing will be quite common in the project I'm working on - please do suggest better ideas if you have any.
This solution won't work for all cases:
params.require(:photo).permit(:tags, tags: [])
For example, if you are using MongoDB and you have a tag_ids array, which stores the ids in a has_many collection, your tag_ids attribute MUST be an array if you specify "type: Array" for the attribute in your model. Consequently, it won't work to send tag_ids with a nil value even if you do this:
params.require(:photo).permit(:tag_ids, tag_ids: [])
Mongoid, the official Ruby adapter for MongoDB, will complain the value of tag_ids must be an array.
The solution is you can indeed send an empty array via your form! And it doesn't need to be a json request. You can simply use remote: true on your form and send it via type: :js. How to do it? Simple. Just add a hidden input in your form and set its value to an empty string:
<%= form_for #user, remote: true, html: { class: 'form' } do |f| %>
<%= select_tag("#{f.object_name}[tag_ids][]", options_for_select(Tag.all.collect {|t| [t.name, c.id]}, selected: f.object.tag_ids), { class: 'form-control', multiple: 'multiple' }) %>
<%= hidden_field_tag "#{f.object_name}[tag_ids][]", '' %>
<%= f.submit class: 'btn ink-reaction btn-raised btn-primary' %>
<% end %>
This here is the key:
<%= hidden_field_tag "#{f.object_name}[tag_ids][]", '' %>
Your attribute will be stored as an empty array in your database. Note I only tested this with Mongoid, but I assume it carries the same functionality in ActiveRecord.
I had the same problem recently, but none of the answers here worked for me. This is my solution. If you have javascript handling HTTP requests, this may work for you, too.
In your client side:
if (photo.tags.length === 0){
photo.tags = ["null"]
}
And on your PhotosController
def photo_params
p = params.require(:photo).permit(tags: [])
p["tags"].reject! { |tag| tag == "null" }
p
end
I ran into the same issue and found a similar solution as Donato, albeit while constructing a multipart FormData in JS. The trick was to put an empty string in the array.
const formData = new FormData()
formData.append('dish[tag_ids][]', '')
On the controller side, params arrives with "dish"=>{"tag_ids"=>[""]}, which dish.update interprets as "remove all tags".

Serialized attributes not evaluating properly with form helpers

I'm rendering a form with serialized attributes. The attribute serialization works fine under normal usage, but now i'm trying to serialize a hash. The view code looks like the following
<%= #item.seasonality_of_sales_pct.each do |key, value| %>
<%= eval("f.label :'seasonality_of_sales_pct[:#{key}]'") %>
<%= eval("f.text_field :'seasonality_of_sales_pct[:#{key}]'") %>
<% end %>
The error I'm getting is undefined method 'seasonality_of_sales_pct[:January]' for #<Item:0x007f01083edd38>. However, the line that is throwing the error is the second eval. The first eval evaluates just fine. I'm baffled as to why this might be happening.
In the controller, I am setting up an attribute like the following
#item.seasonality_of_sales_pct = {January: nil, February: nil, March: nil, September: nil}
Another question that could maybe be answered in the comments is: How bad does this code smell? I'm not sure how the rails community feels about metaprogramming like this. It hurts me a bit to do it, but seems to work most of the time
When you use form_for and then use f.text_field :some_attribute_name then the object you are building the form for (in your case #item) mush have an attribute named some_attribute_name.
You get this error because #item has no attribute or method named seasonality_of_sales_pct[:January]
I also would point out that there is no reason to use eval in your form, it is a serious security risk, as code can be injected.
I wanted to be a bit more thorough than Khaled's answer, which was sort of right. The reason that the first eval statement didn't cause the error was because f.label doesn't care what you give it. <%= f.label :fake_stuff %> will just create a label called Fake Stuff. I'm still not quite sure why the attribute didn't work. If I had f.text_field :seasonality_of_sales_pct, I got a text box full of my hash. Also, I got the labels to display the correct values.
I absolutely did not need to use evals here (I can hope it was only a moment of weakness). Just do
<%= f.text_field :'seasonality_of_sales_pct[:"#{key}"]' %>

How it's possible to send an array from one view to the next

I have a Controller with the function getAccounts where I look for certain accounts. My idea is to first show the number of results and then send the result array to the next function called showAccounts which generates the view. First of all I declared the result array as an instance variable. Then I tried to send with a form tag. It does not work ... Has anyone an idea?
def getAccounts
filter = '(uid='+params[:id]+')'
attrs = ['*']
#accounts=Array.new
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn=admin, dc=cippool-mb, dc=rwth-aachen, dc=de','DLPins!')
conn.perror("bind")
begin
conn.search($base, $scope, filter, attrs) { |entry|
setAttributes(entry)
}
rescue LDAP::ResultError
conn.perror("search")
exit
end
conn.perror("search")
conn.unbind
end
def showAccounts
end
The view where I send the data.
Es wurden <%= #accounts.size %> Accounts gefunden.
<%= form_tag :action => "showAccounts" do %>
<%= hidden_field_tag "accounts", #accounts %>
<%= submit_tag "Anzeigen" %>
<% end %>
I can also paste the view where I need this array, but I dont't think it's relevant for this question. I use Rails 3.2.7 and Ruby 1.9.2p0
If you want to pass some large amount of data between separate requests I would suggest using session, it's designed for such things.
If you debug(#accounts) you'll see what it passes -- something like <#0x7187237 Array> which is not what you want!
If you really want to pass in the accounts array, you'll need to serialize it to a text format to put in a hidden field. That's going to probably be a HUGE chunk of data though if #accounts is large.
That said, you could dump it to YAML or JSON, or use one of the serialization functions in Ruby or put it into a custom text format of your own (not recommended). Keep in mind then that you need to deserialize on the next page before you use it.
I'm assuming part of the wanting to pass it to the next step is to avoid an expensive LDAP request. You might want to look at putting in a lightweight cache -- redis for example -- to temporarily store the requests.

sharing variables between controller an view in a rails app

in my controller I am collecting data (into a hash) like this (note that I do not have a BillingAddress model in my app, #billing_address is standard ruby hash
#billing_address = params[:billing_address]
my view is laid out like this
<%= text_field_tag 'billing_address[phone]' %>
I want to show the previous value that user entered (in case of errors), like this:
<%= text_field_tag 'billing_address[phone]', #billing_address['phone'] %>
however, this gives me an exception saying I am trying to access nil, ideas?
I guess params[:billing_address] is nil.
Try to assign empty hash if it is.
#billing_address = params[:billing_address] || {}
This is not the Rails way of showing previous values in case of error. Check this screencast to get idea on how to handle errors better way.

Resources