Rails 4: collection_select not inserting 'class' attribute? - forms

What am I missing here? I am working with Rails 4.0.0 and trying out the new Bootstrap 3.0.0rc1. I have a simple 'recipe box' app that has a Recipe model and a Category model that feeds a 'category' field on the Recipe. In the recipes#new view, I have the following fields defined:
<h1>Create New Recipe</h1>
<%= form_for #recipe, html: { class: 'form-horizontal' } do |f| %>
<fieldset>
<legend>Main Information</legend>
<div class="form-group">
<%= f.label :name, "Recipe name", class: "col-lg-2 control-label" %>
<div class="col-lg-6">
<%= f.text_field :name, class: "form-control" %>
</div>
</div>
<div class="form-group">
<%= f.label :category_id, class: "col-lg-2 control-label" %>
<div class="col-lg-6">
<%= f.collection_select :category, Category.all, :id, :name, class: "form-control", prompt: "Select a category" %>
</div>
</div>
...
The text_field helper renders a properly formatted tag, complete with class attribute. However, no matter how I construct the select or collection_select helpers, I can't seem to get Rails to give me a that contains a class attribute. The code above gives me this:
<select id="recipe_category" name="recipe[category]"><option value="">Select a category</option>
...
So the prompt comes through, but the class attrib does not, so it looks like part of html_options hash is recognized. But the Bootstrap styling isn't applied. Doesn't matter if I use braces {} around the class: "form-control" or not. Doesn't matter if I use parens around the collection_select params or not. Happens with select helper as well.
Can anyone advise? Are you seeing this too?

Try using :
<%= f.collection_select :category, Category.all, :id, :name, {prompt: "Select a category"}, {class: "form-control"} %>
According to the rails documentation, first comes options and then html options. Remember that html options need to be in braces: {prompt: "Select a category"} or {class: "form-control"}.

<%= f.collection_select :category, Category.all, :id, :name, {prompt: "Select a category"}, {class: "form-control"} %>
The checked answer doesn't work, but is checked because the correct answer buried in the comments (provided by Alter Lagos). I am trying to avoid confusion by moving the actual answer out of the comments.

Try this, works for me!
<%= collection_select(:category, :category_id, #category_for_advertising, :id, :description, {}, {:class=>"dropdown-menu"}) %>

Another version of what has been answered, incorporating W3CSS:
<%= family_form.collection_select :billing_status_id,
> BillingStatus.by_tenant(#cutid).order(:description), :id,
> :description, {}, {class: 'w3-select'} %>
<%= family_form.label
> :billing_status, class: 'w3-label' %>

Related

Can you the coccon gem without a direct nested association?

I have to convert my form to not using nested association. In other words, instead of
<%= link_to_add_association f, :contacts, class: 'btn btn-primary', partial: 'projects/contact_fields', data: {
association_insertion_node: '.contact_fields', association_insertion_method: :append
} do %>
<i class="fas fa-plus"></i>
<% end %>
<%= f.fields_for :contacts do |contact| %>
<%= render 'projects/contact_fields', f: contact %>
<% end %>
I would like to be able to just pass in a string to be used as the container (similar to how you can with field_for).
<%= link_to_add_association 'contacts[]', 'projects/contact_fields', class: 'btn btn-primary', partial: 'projects/contact_fields', data: {
association_insertion_node: '.contact_fields', association_insertion_method: :append
} do %>
<i class="fas fa-plus"></i>
<% end %>
<% #contacts.each_with_index do |contact, index| %>
<%= fields_for "contacts[#{index}]", contact do |c| %>
<%= render 'projects/contact_fields', f: c %>
<% end %>
<% end %>
Cocoon is currently unable to edit/manage a collection. Cocoon is just helping the form-behaviour for nested-children functionality in rails, so there is no simple solution to edit an array or collection. On the other hand, this is in general pretty simple to implement without cocoon.
Very high level, do something like:
#contacts.each do |contact|
= render `contacts/edit`, contact: contact
= render `contacts/new`
so render the edit form for each existing contact, and render an empty new form. You will have to edit your controller functionality a little, because you will always re-render the complete collection/index page (with all existing contacts in the collection?).
So you can just render multiple forms on one page. Using turbolinks this will render fast and feel actually completely the same. You could use xhr to update only specific parts of the page, but to get started that is not even needed.
So I ended up following drifting ruby and using code from the cocoon gem to implement something myself. I hope others can benefit from it. Thank you nathanvda for the cocoon gem which helped me with the code below, really wish I could have used it:
add this to your app/helpers/application_helper.rb
def link_to_add_row(*args, &block)
if block_given?
link_to_add_row(capture(&block), *args)
else
#byebug
name, association, new_object, partial, html_options = *args
html_options ||= {}
html_options[:class] = [html_options[:class], "custom_add_fields"].compact.join(' ')
id = 'NEW_RECORD'
fields = fields_for("#{association}[#{id}]", new_object, child_index: id) do |builder|
#byebug
render( partial, f: builder)
end
fields = CGI.escapeHTML(fields).html_safe
link_to(name, '#', class: html_options[:class], data: {id: id, fields: fields})
end
end
add to your app/assets/application.js
$(document).on('click', '.custom_remove_fields', function(event) {
$(this).prev('input[type=hidden]').val('1');
$(this).closest('tr').hide();
return event.preventDefault();
});
$(document).on('click', '.custom_add_fields', function(event) {
var regexp, time;
time = new Date().getTime();
regexp = new RegExp($(this).data('id'), 'g');
$('.contact_fields').append($(this).data('fields').replace(regexp, time));
return event.preventDefault();
});
In your template you can use the following to render a partial for the collection:
<%= link_to_add_row('contacts', contact.new, 'contact_fields', class: 'btn btn-primary') do %>
<i class="fas fa-plus"></i>
<% end %>
This is how I render the partial with the collections in my template:
<tbody class="contact_fields">
<% #contacts.each_with_index do |contact, index| %>
<%= fields_for "contacts[#{index}]", contact do |c| %>
<%= render 'contact_fields', f: c %>
<% end %>
<% end %>
</tbody>
This is what my contact_fields.html.erb partial looks like.
<tr class="nested-fields">
<td>
<%= f.text_field :fullname, class: 'form-control invoke-contacts-search contact-fullname' %>
</td>
<td>
<%= f.text_field :email, class: 'form-control invoke-contacts-search contact-email' %>
</td>
<td>
<%= f.text_field :phone, class: 'form-control contact-phone' %>
</td>
<td>
<%= f.text_field :department, class: 'form-control contact-department' %>
</td>
<td>
<%= f.text_field :manager, class: 'form-control contact-manager' %>
</td>
<td>
<%= f.hidden_field :id %>
<%= f.hidden_field :_destroy %>
<%= link_to '#', class: 'btn btn-danger custom_remove_fields' do %>
<i class="fas fa-trash-alt"></i>
<% end %>
</td>
</tr>

Phoenix framework: Where does resource's default form #action attribute for all resources come from?

The command
mix phx.gen.html Blog Post posts title body:text
generates among other files the template form.html.eex for the add/edit posts form, which looks like this:
<%= form_for #changeset, #action, fn f -> %>
<%= if #changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :title, class: "control-label" %>
<%= text_input f, :title, class: "form-control" %>
<%= error_tag f, :title %>
</div>
<div class="form-group">
<%= label f, :body, class: "control-label" %>
<%= textarea f, :body, class: "form-control" %>
<%= error_tag f, :body %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
The form_for function gets #action argument which is not present in the PostController, so where does it come from? I couldn't find.
In my installation I have a plug MyProject.Locale which redirects all unlocalized requests to localized ones like:
/posts --> /de/posts
And I have the :locale scope in my router
scope "/:locale", MyProject.Web, as: :locale do
pipe_through :browser
get "/", PageController, :index
resources "/posts", PostController
end
The default #action doesn't take the scope's locale chunk into account and sends the POST request to the /posts url, which is redirected by my MyProject.Locale plug to /de/posts as GET request (it uses the function Phoenix.Controller.redirect(to:...)). I want the form to send the POST request to the localized path.
So, can we override this #action argument for all resources somewhere in one place or do we have to provide it in each controller in render function call
render(conn, "new.html", changeset: changeset, action: action)
or the only option is to change the form templates for all resources?

Standard Rails form + Cocoon gem: undefined method `new_record?' for nil:NilClass error

I'm following this tutorial and the author is using Slim. Since I more familiar with standard Rails form, I try to change the slim markup to normal form like so:
new.html.erb
<%= render 'form' %>
_form.html.erb
<%= form_for(#user) do |f| %>
<%= f.text_field :name %>
<br><br>
<%= fields_for :user_photos do |photo| %>
<%= render "user_photo_fields", f: photo %>
<span class="links">
<%= link_to_add_association "add photo", f, :user_photos %>
</span>
<% end %>
<%= f.submit %>
<% end %>
_user_photo_fields.html.erb
<div class="nested-fields">
<div class="field">
<%= f.file_field :photo %>
</div>
<%= link_to_remove_association "remove", f %>
</div>
And, this is my models:
class User < ActiveRecord::Base
has_many :user_photos
validates_presence_of :name
accepts_nested_attributes_for :user_photos, allow_destroy: true
end
class UserPhoto < ActiveRecord::Base
belongs_to :user
mount_uploader :photo, PhotoUploader
end
And lastly, strong params inside the users_controller.rb. I didn't touch the rest methods inside the controller because I'm using rails g scaffold user name:string generator.
def user_params
params.require(:user).permit(:name, user_photos_attributes: [:id, :photo, :_destroy])
end
I get this error:
undefined method `new_record?' for nil:NilClass
What am I missing here?
I believe it's just a simple typo - your fields_for :user_photos should be f.fields_for :user_photos (so that it's properly connected to the parent form).
please try with this.
class User < ActiveRecord::Base
has_many :user_photos
validates_presence_of :name
accepts_nested_attributes_for :user_photos, allow_destroy: true
end
can you try to fix this by removing the f
<div class="nested-fields">
<div class="field">
<%= file_field :photo %>
</div>
<%= link_to_remove_association "remove" %>
</div>

How to DRY two slightly different forms?

I know about partial in rails, but it seems that I can only re-use exactly the same form with partial.
What if I have two forms that differ only in one field? For example, in my classroom app, I have one form for a peer-grader and one form for a teacher-grader.
The teacher's form is as follows. The peer-grader form is exactly the same, minus the grade field. How do I DRY this?
<%= form_for #doc,
url: student_homework_document_path(student_id: #doc.submitter_id,
id: #doc.id),
html: { multipart: true } do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :grade %>
<%= f.number_field :grade %>
<%= f.label :graded_file %>
<%= f.file_field :graded_file %>
<%= f.label :graded_file_source_code %>
<%= f.file_field :graded_file_source_code %>
<%= f.submit "Submit grading", class: "btn btn-large" %>
<% end %>
I would have thought you had one controller called teachers and one called peers or something. You could keep the form the same but add a variable to the peer-grader method such as #peer_grader = true. Then in your form add:
<% if #peer_grader = true %>
<%= f.label :grade %>
<%= f.number_field :grade %>
<% end %>
You would also need to set another variable called #url in both controllers set to the correct path and then update the form to:
<%= form_for #doc, url: #url, html: { multipart: true } do |f| %>

Rails, choosing option in drop-down filters values in second drop-down

I'm trying to create some pages in my rails app, using bits and pieces of what I've learned from some Railscasts. My environment is as follows:
Rails 3.2.13
ruby 1.9.3p448
Using the chosen gem
Database SQLite
When I want to create a new profile, I would visit localhost:3000/profiles/new.
There I have a text box for my profile called Name. Next I have a drop-down
where the use can choose what profile type - Admin, Editor or User. Once an
option is selected, another drop-down becomes visible - Traits. The user can
then choose 1 or many traits.
For each profile type (Admin, Editor, User) there are different traits. So the
Traits model has fields name and trait_type where trait_type would have a value
of either Admin, Editor or User.
I'm having trouble coding the view, assuming I should code there, but maybe I
should be in a controller, getting the Traits drop-down to display only say
Admin traits if I choose a profile type of Admin.
Can someone please give me a nudge in a positive direction? Please keep in mind
that I'm new to this. Hope I am providing enough, and not too much information.
Note: The console.log entries in my profiles.js.coffee file is temporary, just
so I can see what is going on.
Thanks,
HoGi
-------------------------------------------------
/app/assets/javascripts/application.js
-------------------------------------------------
//= require jquery
//= require jquery_ujs
//= require chosen-jquery
//= require_tree .
-------------------------------------------------
-------------------------------------------------
/app/assets/javascripts/profiles.js.coffee
-------------------------------------------------
jQuery ->
$('#profile_trait_ids').chosen()
jQuery ->
$('#profile_trait_ids').parent().hide()
prof_types = $('#profile_prof_type').html()
console.log(prof_types)
$('#profile_prof_type').change ->
prof_type = $('#profile_prof_type :selected').text()
console.log(prof_type)
$('#profile_trait_ids').parent().show()
-------------------------------------------------
-------------------------------------------------
/app/assets/stylesheets/application.css
-------------------------------------------------
*= require_self
*= require chosen
*= require_tree .
*/
-------------------------------------------------
-------------------------------------------------
/app/models/profile.rb
-------------------------------------------------
class Profile < ActiveRecord::Base
attr_accessible :active, :name, :prof_type, :trait_ids
has_many :traitships
has_many :traits, through: :traitships
PROFILE_TYPES = ["Admin", "Editor", "User"]
end
-------------------------------------------------
-------------------------------------------------
/app/models/trait.rb
-------------------------------------------------
class Trait < ActiveRecord::Base
attr_accessible :active, :name, :trait_type
has_many :traitships
has_many :profiles, through: :traitships
end
-------------------------------------------------
-------------------------------------------------
/app/models/traitship
-------------------------------------------------
class Traitship < ActiveRecord::Base
#attr_accessible :profile_id, :traid_id
belongs_to :profile
belongs_to :trait
end
-------------------------------------------------
-------------------------------------------------
/app/views/_form.html.erb
-------------------------------------------------
<%= form_for(#profile) do |f| %>
<% if #profile.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#profile.errors.count, "error") %> prohibited this profile from being saved:</h2>
<ul>
<% #profile.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :prof_type, 'Profile Type' %><br />
<%= f.select :prof_type, Profile::PROFILE_TYPES, prompt: 'Select a type' %>
</div>
<div class="field">
<%= f.label :trait_ids, "Traits" %><br />
<%= f.collection_select :trait_ids, Trait.order(:name), :id, :name, {}, { multiple: true } %>
</div>
<div class="field">
<%= f.label :active %><br />
<%= f.check_box :active %>
</div>
<div class="actions">
<%= f.submit 'Update' %>
</div>
<% end %>
-------------------------------------------------
UPDATED CONTROLLER:
(traits_controller)
# GET /traits/:trait_type/trait_options
# GET /traits/:trait_type/trait_options.json
def trait_options
#traits = Trait.order(:name).where("trait_type like ?", params[:trait_type])
respond_to do |format|
format.html # trait_options.html.erb
format.json { render json: #traits }
end
end
Part 1
You should add a new action to your controller (traits_controller) that takes as input one parameter profile_type and returns all traits for that profile_type as json/xml.
Part 2
Once you have this ready, You should call this action via ajax on on_change event of your profile_type dropdown. Use that data received to construct the new traits dropdown.
chosen, select2 etc, should make this very easy to accomplish once your part 1 is ready.
Update
I have never used chosen myself. But looking at its documentation, something like this might work for you
$('#profile_type').on('change', function() {
$("#traits").chosen().change( … );
$("#traits").trigger("chosen:updated");
});
This Railscast (Episode #258) demonstrates how a chosen select is populated using json data
If it is possible to drop chosen in favor of select2, I highly recommend you do it.