collection_check_boxes in Rails getting results from form - ruby-on-rails

I find the documentation for
collection check boxes to be hard to understand. The explanation for the parameters is:
collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) public
Returns check box tags for the collection of existing return values of method for object's class. The value returned from calling method on the instance object will be selected. If calling method returns nil, no selection is made.
What I'm not sure about is what the "object" (the first argument) is supposed to be. I tried two different possibilities, and got different failures.
<%= collection_check_boxes(:wiki, :collaborating_user_ids, User.all, :id, :name) do |b| %>
<li>
<%= b.check_box %>
<%= b.label %>
</li>
<% end %>
The idea is that my app supports editing of wikis. I want the wiki owner to be able to add and remove users from the list of collaborators using checkboxes (which of course is only practical when there is a small number of users, which is the case). collaborating_user_ids is an instance method of Wiki that returns the current list of collaborators. I want to display the names of all users, with the current collaborators checked.
This works for the display, but when I inspect the params returned by this form, there is nothing showing which checkboxes were checked. On the other hand, if I replace the symbol :wiki by the instance variable #wiki, the checkboxes appear in the params associated with the wiki, but the checkboxes are not set correctly, initially.
I don't actually know what the first parameter of collection_check_boxes is supposed to be, but every example I've seen shows a symbol.

Assuming you have your associations set correctly.
The object should be the model that you are editing.
So yes, you are almost there. Make sure to include collaborating_user_ids inside your strong params in the wiki controller. This will allow the wiki to accept collaborators
# inside your wiki controller
class WikisController < ApplicationController
def wiki_params
params.require(:wiki).permit(collaborating_user_ids: [], ...)
end
end
Your form could look something like this
<%= form_for #wiki do |f| %>
<%= f.collection_check_boxes :collaborating_user_ids, User.all, :id, :name %>
<%= f.submit %>
<% end %>

Related

Adding a confirmation checkbox to a form in Rails not tied to some attribute?

Noob question! :)
I have a form, that has basically no point other than call some_action. The reason I use a form for this, is because we have a specific styling for this in our large website.
<%= styled_form_for(#user, :url => some_action_user_path #user)) do |f| %>
<%= f.save_button %>
<% end %>
I thought, since it's a form, I should be able to put a checkbox in there. It should have no other goal than confirming the user wants to do this action indeed. E.g. "Yes, I want to do some_action to the user model".
How would I make a checkbox that does not really change any attribute or affect anything - Other than that it should be checked for the form to submit?
This is probably dead simple, but according to the documentation and various error messages I should provide arguments such an attribute (which I don't want...)
form_for is meant to work on attributes of a model, which is what all the documentation you are reading is telling you. So if your model had a boolean column you could easily attach a check box to it.
If you ever want a form (or specific tag) that does not follow this, you can use the _tag version of these methods. For example, form_tag or, in your particular case, check_box_tag.
Example:
<%= styled_form_for(#user, :url => some_action_user_path #user)) do |f| %>
<%= check_box_tag "do_some_method" %>
<%= f.save_button %>
<% end %>
NOTE: You will only get a param entry for :do_some_method if it is checked off. If you want to get a param regardless, you have to add a hidden_field_tag before it.
<%= hidden_field_tag "do_some_method", "no_dont_do_it" %>
<%= check_box_tag "do_some_method", "yes_do_it" %>
Now if the checkbox is selected you'll get params[:do_some_method] set to "yes_do_it"; if it's not checked off, instead of getting no entry, you'll get params[:do_some_method] set to "no_dont_do_it".

Rails: Form helper for enum attribute

I set category's viewable attribute as an enum
class Category < ActiveRecord::Base
enum viewable: [:only_self, :friends, :anyone]
end
How should I make them accesible in _form when users edit this attribute? Something like?
<%= form_for(#category) do |f| %>
<%= f.select(:viewable) %>
<% end %>
UPDATE------
<%= f.select(:viewable, options_for_select([["description1", "only_self"], ["description2", "friends"], ["description3", "anyone"]])) %>
The description for each is quite repetative, because I need to put them whenever I need to display, not just in the forms. Where should I put them?
In form_for, the f.select does not display the current value of the this field. It always is the first description1.
When using the plural form, rails provides the full key/value array, so you can call Category.viewables for the array, and with the help of options_for_select you'll get a nice functioning dropdown list
<%= form_for(#category) do |f| %>
<%= f.select(:viewable, options_for_select(Category.viewables)) %>
<% end %>
Updated answers
The description for each is quite repetative, because I need to put
them whenever I need to display, not just in the forms. Where should I
put them?
You can use the I18n library, assuming your locale is chinese (zh i think) then you could create /config/locales/zh.yml and add something like this
categories:
viewable:
only_self: 'some chinese text'
friend: 'more chinese text'
anyone: 'well you know'
Then better create some helper that returns the localized options
def options_for_viewables
{
t('categories.viewable.only_self') => 0,
t('categories.viewable.friends') => 1,
t('categories.viewable.anyone') => 2
}
end
The view will become like this
<%= f.select(:viewable, options_for_viewables) %>

Rails Strong Parameters: How to accept both model and non-model attributes?

I have a form to create a user model with all its usual attributes, but I am also passing a lot of non-model attributes that based on which I will create further stuff in my controller action.
My question is how I can tell Strong Parameters to accept both the user data, AND this other data that is not related to the user db wise?
To illustrate, my form could like this (submit button deleted for brievity):
<%= form_for #user do |f| %>
<%= f.text_field 'attribute1' %>
<%= f.text_field 'attribute2' %>
<%= f.text_field 'attribute3' %>
<%= text_field_tag 'attribute_not_on_user_model1' %>
<%= text_field_tag 'attribute_not_on_user_model2' %>
<% end %>
How can I use strong parameters to do this? I tried this:
params.require(:user).permit(:attribute1, :attribute2 , :attribute3, :attribute_not_on_user_model1,
attribute_not_on_user_model2)
And also this:
params.require(:user).permit(:attribute1, :attribute2 ,
:attribute3).require(:attribute_not_on_user_model1,
attribute_not_on_user_model2)
Both did not work. I am aware that I could do attr_accessor in the user, but I have a growing list of attributes in this form that are not related to the user model per se (but are nonetheless essential to the creation of the user model and its subsequent related models). We could debate that this is not the best way to do this (A form object comes to mind), but for the moment I want to see if Strong Parameters can help me here.
user model attributes are stored inside the :user hash, whereas the non-user attributes are accessible directly at the params level.
If you inspect your params Hash, you will notice that it is constructed in the following way
{ user: { attribute1: "value", attribute2: value, ... }, attribute_not_on_user_model1: "value", attribute_not_on_user_model2: "value" }
Consequently, the call
params.require(:user)
will automatically ignore any other param that is not part of the user node. If you want to include also the other params, you either compose the hash, or update the view to inject the params in the form.
Injecting the parameter on the form will cause the params to be part of the same :user node. This approach normally works very well with virtual attributes (despite the concepts are not linked each other).
<%= form_for #user do |f| %>
<%= f.text_field 'attribute1' %>
<%= f.text_field 'attribute2' %>
<%= f.text_field 'attribute3' %>
<%= text_field_tag 'user[attribute_not_on_user_model1]' %>
<%= text_field_tag 'user[attribute_not_on_user_model2]' %>
<% end %>
The other solution would be something like
def some_params
hash = {}
hash.merge! params.require(:user).slice(:attribute1, :attribute2, :attribute3)
hash.merge! params.slice(:attribute_not_on_user_model1,
attribute_not_on_user_model2)
hash
end
The solution, however, really depends on how you will user those params later. If all these params are sent as a single Hash, then you may want to compose the single hash, but in that case you may also want virtual attributes.
The point is that, without a real use case, the question itself is quite non-sense. StrongParameters is designed to filter a group of parameters passed to a bulk-create or bulk-update action. Generally, this means you have a model.
If you design a custom method, or if you have non-model methods, StrongParameters whitelisting may not have any sense, given you have control over the method you are writing and invoking.
There are many ways to do this and one way is with accecpts_nested_attributes_for: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Rails form for mailing

I'm trying to build a form which preloads content from two models (two variables being passed, being shown in the textfields) and then, not saves the data but sends the altered content (from the textfields) as two(?) variables to a mailer class.
I've managed to preload the data from one of the two models but am not sure how the form_for tag has to look like to get both models loaded as well as targeting the mailer class method instead of updating the model entity when pressing "send".
Do I need the accepts_nested_attributes_for attribute inside the model if I'm not saving anything?
I hope someone could give me an small example of the crucial parts. A thousand thanks!
You can use fields_for to include other models in same form. You can use it inside the same form_for what is present.
Checkout the example here from the api docs,
<%= form_for #person do |person_form| %>
First name: <%= person_form.text_field :first_name %>
Last name : <%= person_form.text_field :last_name %>
<%= fields_for #person.permission do |permission_fields| %>
Admin? : <%= permission_fields.check_box :admin %>
<% end %>
<%= f.submit %>
<% end %>
when you submit the data from this form, you can just use that data to pass to the mailer class from controller. UserMailer.get_user_info(params[:name], params[:address]).send
Creates a scope around a specific model object like #form_for, but doesn't create the form tags themselves. This makes #fields_for suitable for specifying additional model objects in the same form.
Refer Docs here:.
fields_for(record_name, record_object = nil, options = {}, &block)

Form has_many in text_area

I have the following situation:
A Order has many Pages. I want to let the User to paste a bunch (20+) URLs (it's a Page attribute) that they might have in a doc file into a text area.
Right now I am not using a Form associated with an Order object, because I fail to see how I can do a nested form of the URLs if those are inside a text area.
I have seen a similar question has been asked before here: Rails: Using a Textarea for :has_many relationship , but I fail to see how would I code the view and model in order to do so.
So, if I have this:
Order has_many Pages
And a form like this:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls?? %> # This would let the user paste X URLs, which would be
# used to create X Pages associated with the Order.
<% end %>
You could retain the view code that you have:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls %>
#other field and submit button
<% end %>
In your model, you'll need to do the following:
attr_accessor :page_urls
after_validation do
if page_urls
parse_page_urls.each do |url|
pages.create(url: url)
end
end
end
def parse_page_urls
#use regexp to extract urls from page_urls string and return an array of url strings
end
The accessor is defined so that you can use :page_urls in your form_builder. You could set easily validations in your model for :page_urls that way too.
Once order has been validated, it will create page objects according to the number of urls extracted from the page_urls attribute.
You could refer to this for some help with using regexp to extract the urls from the string.
Hope that helps!
This is a job best handled with nested form. It will let you submit attributes of a has_many relationship model from the parent model, like you wish to do. For example, from its docs:
Imagine you have a Project model that has_many :tasks. To be able to use this gem, you'll need to add accepts_nested_attributes_for :tasks to your Project model. If you wish to allow the nested objects to be destroyed, then add the :allow_destroy => true option to that declaration. See the accepts_nested_attributes_for documentation for details on all available options.
This will create a tasks_attributes= method, so you may need to add it to the attr_accessible array (attr_accessible :tasks_attributes).
Then use the nested_form_for helper method to enable the nesting.
<%= nested_form_for #project do |f| %>
You will then be able to use link_to_add and link_to_remove helper methods on the form builder in combination with fields_for to dynamically add/remove nested records.
<%= f.fields_for :tasks do |task_form| %>
<%= task_form.text_field :name %>
<%= task_form.link_to_remove "Remove this task" %>
<% end %>
<%= f.link_to_add "Add a task", :tasks %>
In response to your comment:
In order to do something like that, you would need to do processing in the controller to separate the URL's, then make a new Page object associated with #order object. Unfortunately, there isn't a way to do this without post-processing, unless you do it with JS on the client side with hidden inputs.

Resources