Rails - Strong parameters with empty arrays - ruby-on-rails

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".

Related

How to display ROR 5 ActiveRecord pluck as String in html.erb

In html.erb I have:
<%= ContactDescribe.where(["contact_describe_id = ?", "12"]).limit(1).pluck(:borrower_or_lender_text) %>
The field is retrieved successfully. But returns an array element. I need to learn how to convert that element to a string.
In addition to Deepak's answer, you can also convert the Array into a "Sentence" String
<%= ContactDescribe.where(contact_describe_id: 12).limit(1).pluck(:borrower_or_lender_text).to_sentence %>
Recommendation:
As pointed out by TheChamp, it is best practice to already "prepare" the values needed in the views as instance variables from the controller. See my recommended refactor
# controller
def YOUR_ACTION_NAME
#contact_describe = ContactDescribe.where(contact_describe_id: 12).first
end
# view
<%= #contact_describe.borrower_or_lender_text %>
Note: you won't even need pluck anymore unless you have other reasons why you want limit(1)
The issue here is that where returns a collection - something similar to an array, just in ActiveRecord - no matter what limit you set on it. To retrieve the information you would use .first or [0] since you always only return one object.
But, since you are looking for a specific ContactDescribe object. Do this instead:
ContactDescribe.find_by(contact_describe_id: 12).borrower_or_lender
Additionally there two things you should improve in your code.
1: Logic should go into the controller or the model. A view is solely here to show objects.
2: What is up with the contact_describe_id field? Why not call it id. It seems redundant. Isn't user.id more convenient than user.user_id?
You can make use of join
<%= ContactDescribe.where(contact_describe_id: 12).limit(1).pluck(:borrower_or_lender_text).join(',') %>

rails text_field / text_ara empty string vs nil

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.

View array or one record in rails view

In my method via some calculations a get data, then i need to view it in view, but if write
#ar.each do |a|
when i have only one record i get error, also when i have one error each is bad idea. So how to do this this?
So i have such code in method:
non_original = []
#articles.each do |a|
non_original << get_non_tecdoc("LA44", 1, "KNECHT")
end
#non_original = non_original
get_non_tecdoc returns object, or nothing...
So in view i have:
-#non_original.each do |no|
=no.brand
=no.description
=no.price
=no.quantity
But what to do if #non_original has one record, then #non_original.each gives error. So how to do check in view? If #non_original has one record, than simple #non_original.brand etc, but if more than one, than use each loop?
This will work with #ar as a single value as well as an array:
Array(#ar).each do |a|
p a
end
This Array is a method on Kernel.
<%= debug #ar %>
This will give you a nice YAML format to look at in your view (assuming ERB).
EDIT: I believe this is what you want, since you're not interested in debugging.
In your controller, use the splat operator to convert a singleton element to an array (it doesn't modify arrays):
#ar = *#ar
Then #ar.each will work as expected in your view.
Alternatively, you could check in your view:
<% if #ar.is_a?(Array) %>
<% #ar.each ... %>
<% else %>
<%= #ar %>
<% end%>
Why don't you try using #ar.inspect and output it to the console to see the instance variables contents.
As long as #ar is an array you should not get a error. If you are returning one record change it to an array with one record.
If you are using active record query interface like the "where" clause; it will return an array with 0 or more active_record objects. If you use find it will return one instance of an active_record object.
So if your method that queries is using the active record where clause #ar should always return an array.
Please try this:
Tablename.find_by_table_id
Example:
if account_id is 10 then, take following example,
#getResults = Account.find_by_account_id(10)
It will gives single record.
we can get values using #getResults.id,#getResults.name ....like wise.

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.

How to submit multiple NEW items via Rails 3.2 mass-assignment

I have a pretty standard use-case. I have a parent object and a list of child objects. I want to have a tabular form where I can edit all the children at once, as rows in the table. I also want to be able to insert one or more new rows, and on submit have them be created as new records.
When I use a fields_for to render a series of sub-forms for nested records related by has-many, rails generates field names e.g. parent[children_attributes][0][fieldname], parent[children_attributes][1][fieldname] and so on.
This causes Rack to parse a params hash that looks like:
{ "parent" => {
"children" => {
"0" => { ... },
"1" => { ... } } }
When passed a new (un-persisted) object, the same fields_for will generate a field name that looks like:
parent[children_attributes][][fieldname]
Note the [] with no index in it.
This cannot be posted in the same form with the fields containing [0], [1], etc. because Rack gets confused and raises
TypeError: expected Array (got Rack::Utils::KeySpaceConstrainedParams)
"OK", thinks I. "I'll just make sure all the fields use the [] form instead of the [index] form. But I can't figure out how to convince fields_for to do this consistently. Even if I give it an explicit field name prefix and object:
fields_for 'parent[children_attributes][]', child do |f| ...
So long as child is persisted, it will automatically modify the fieldnames so that they become e.g. parent[children_attributes][0][fieldname], while leaving fieldnames for new records as parent[children_attributes][][fieldname]. Once again, Rack barfs.
I'm at a loss. How the heck do I use standard Rails helpers like fields_for to submit multiple new records, along with existing records, have them be parsed as an array in the params, and have all the records lacking IDs be created as new records in the DB? Am I out of luck and I just have to generate all the field names manually?
As others have mentioned, the [] should contain a key for new records because otherwise it is mixing a hash with an array type. You can set this with the child_index option on fields_for.
f.fields_for :items, Item.new, child_index: "NEW_ITEM" # ...
I usually do this using the object_id instead to ensure it is unique in case there are multiple new items.
item = Item.new
f.fields_for :items, item, child_index: item.object_id # ...
Here's an abstract helper method that does this. This assumes there is a partial with the name of item_fields which it will render.
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
You can use it like this. The arguments are: the name of the link, the parent's form builder, and the name of the association on the parent model.
<%= link_to_add_fields "Add Item", f, :items %>
And here is some CoffeeScript to listen to the click event of that link, insert the fields, and update the object id with the current time to give it a unique key.
jQuery ->
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
That code is taken from this RailsCasts Pro episode which requires a paid subscription. However, there is a full working example freely available on GitHub.
Update: I want to point out that inserting a child_index placeholder is not always necessary. If you do not want to use JavaScript to insert new records dynamically, you can build them up ahead of time:
def new
#project = Project.new
3.times { #project.items.build }
end
<%= f.fields_for :items do |builder| %>
Rails will automatically insert an index for the new records so it should just work.
So, I was not happy with the solution I saw most often, which was to generate a pseudo-index for new elements, either on the server or in client-side JS. This feels like a kludge, especially in light of the fact that Rails/Rack is perfectly capable of parsing lists of items so long as they all use empty brackets ([]) as the index. Here's an approximation of the code I wound up with:
# note that this is NOT f.fields_for.
fields_for 'parent[children_attributes][]', child, index: nil do |f|
f.label :name
f.text_field :name
# ...
end
Ending the field name prefix with [], coupled with the index: nil option, disables the index generation Rails so helpfully tries to provide for persisted objects. This snippet works for both new and saved objects. The resulting form parameters, since they consistently use [], are parsed into an array in the params:
params[:parent][:children_attributes] # => [{"name" => "..."}, {...}]
The Parent#children_attributes= method generated by accepts_nested_attributes_for :children deals with this array just fine, updating changed records, adding new ones (ones lacking an "id" key), and removing the ones with the "_destroy" key set.
I'm still bothered that Rails makes this so difficult, and that I had to revert to a hardcoded field name prefix string instead of using e.g. f.fields_for :children, index: nil. For the record, even doing the following:
f.fields_for :children, index: nil, child_index: nil do |f| ...
...fails to disable field index generation.
I'm considering writing a Rails patch to make this easier, but I don't know if enough people care or if it would even be accepted.
EDIT: User #Macario has clued me in to why Rails prefers explicit indices in field names: once you get into three layers of nested models, there needs to be a way to discriminate which second-level model a third-level attribute belongs to.
The common solution is to add a placeholder into [], and replace it with a unique number on inserting the snippet to the form. Timestamp works most of the time.
Maybe you should just cheat. Put the new records in a different faux attribute that is a decorator for the actual one.
parent[children_attributes][0][fieldname]
parent[new_children_attributes][][fieldname]
It's not pretty, but it should work. It might take some extra effort to support round-trips to the form for validation errors.
I've came across this user case in all my last proyects, and I expect this to continue, as julian7 pointed, it is necesary to provide a unique id inside the []. In my opinion this is better done via js. I've been dragging and improving a jquery plugin for dealing with this situations. It works with existing records and for adding new records but expects a certain markup and it degrades gracefully, heres the code and an example:
https://gist.github.com/3096634
Caveats for using the plugin:
The fields_for call should be wrapped in a <fieldset> with data-association attribute equal to the pluralized name of the model, and a class 'nested_models'.
an object should be built in the view just before calling fields_for.
the object fields perse should be wrapped in a <fieldset> with class "new" but only if the record is new (cant remember if I removed this requirement).
A checkbox for the '_destroy' attribute inside a label must exist, the plugin will use the label text to create a destroy link.
A link with class 'add_record' should exist within the fieldset.nested_models but outside the fieldset enclosing the model fields.
Appart from this nuisances its been working wonders for me.
After checking the gist this requirements must be clearer.
Please let me know if you improve on the code or if you use it :).
BTW, I was inspired by Ryan Bates first nested models screencast.
long post deleted
Ryan has an episode on this:
http://railscasts.com/episodes/196-nested-model-form-revised
It looks like you need to generate the unique index manually. Ryan uses the object_id for this.
I think you can make it work by including the id of the record as a hidden field
There is a gem called cocoon for doing this, I would go for a leaner mor DIY aproach but it was specifically built for this cases.

Resources