In a form, I want to include blank only in case of Client.count>1. Is there a clean way of doing this?
Now I'm using this select:
= f.select :client_id, Client.all.map{|c| [c.full_name, c.id]}, {include_blank: true}
You can use a tiny decorator:
class ClientDecorator
def self.form_select_choices
Client.pluck(:full_name, :id)
end
def self.form_select_include_blank?
{ include_blank: Client.count.positive? }
end
end
So, in your view you call those class methods:
<%= form.select :client_id, ClientDecorator.form_select_choices, ClientDecorator.form_select_include_blank? %>
Now you can test that and leave the database interaction far from the views.
Related
I have a collection_select in a rails form that looks like this:
<%= form.collection_select :post_id, Post.all, :id, :title, {}, { class: "mt-1 block" } %>
What I can't seem to figure out from the docs or googling, is how to pass multiple attributes from the Post to the dropdown so the user sees more than just the :title. Something like this:
<%= form.collection_select :post_id, Post.all, :id, :title + :category, {}, { class: "mt-1 block" } %>
I can create a custom method to pass to text_method like :title_with_category in the Post model like:
<%= form.collection_select :post_id, Post.all, :id, :title_with_category, {}, { class: "mt-1 block" } %>
Post.rb:
def title_with_category
self.title + " " + self.category
end
But is this the best way to do this? If so, what is the appropriate place to define this? The model? Or should this be in a helper? If it's a helper, should it be in the application helper?
Firstly, it's safer to do this in case one of the items is ever nil:
Post.rb
def title_with_category
"#{title} #{category}"
end
Next your selection. In the controller, return the options as an attribute:
def new
#post_options = Post.all.collect{|post| [post.id, post.title_and_category]}
# OR
#post_options = Post.all.collect{|post| [post.id, "#{post.title} #{post.category}"]}
# you can skip the model method with the second option
end
And on the form:
<%= form.select :post_id, #post_options, {}, { class: "mt-1 block" } %>
See form select.
You can pass a callable to collection_select for both the value_method and text_method arguments:
<%= form.collection_select :post_id,
Post.all,
:id, # value_method
->(p){ "#{p.title} #{p.category}" }, # text_method
{},
{ class: "mt-1 block" }
%>
A callable is any object that responds to the call method such as lamdba and proc objects.
It is called with the post for each iteration of the loop.
What is the appropriate place to define this? The model? Or should this be in a helper? If it's a helper, should it be in the application helper?
There is no clear cut answer if you choose to extract this out into a separate method. The model would be the simplest solution but you can also argue that presentational logic should be separated from buisness logic and that models already have tons of responsiblities.
I think we can all agree on that ApplicationHelper is the least suitible option unless you to just are aiming to toss your code into a junk drawer.
This code could go into Post, PostHelper, PostPresenter (if you're into the decorator pattern) or a custom form builder (which seems slightly overkill).
I have an a column called access and it's set to array: true. I'm having trouble submitting a form with this however. Here is my controller:
Controller
def create
#topic = Topic.find_by(slug: params[:topic_id])
#navigation_item = NavigationItem.create(navigation_item_params)
if #navigation_item.persisted?
redirect_to topic_path(params[:topic_id])
else
render :new
end
end
private
def navigation_item_params
params.require(:navigation_item).permit(
:title,
:url,
:thumbnail_id,
:access,
:category_id,
:tag
)
end
Pretty standard stuff in the controller
Form
<%= form.select :access, options_for_select(custodian_profiles), { include_blank: true, multiple: true }, class: "form-control #{error_class}" %>
I also have a presence true validation on the field.
So, when I try to submit the form the access field comes back as an empty array it looks like this #=> access: [] which obviously fails the validation check and doesn't work. How do I accept multiple select values with rails?
Looks like you may need another pair of brackets, try this:
<%= f.select :access, options_for_select(custodian_profiles), {}, {:multiple => true, :class => "form-control #{error_class}", include_blank: true } %>
Also make sure that your custodian_profiles variable is returning an array. Hope that helps.
In addition to showing the challenge's name. I also want to show its deadline next to the name.
It would look like this for example:
Visit London 09/09/16
Make $1,000,000 10/15/18
Knit a Scarf 01/11/19
Instead of just this:
<%= f.collection_select :challenge_id, current_user.challenges.order(:deadline),:id,:name, include_blank: true %>
Define a method name_with_deadline in challenge.rb
def name_with_deadline
"#{name} #{deadline}"
end
and then make use of this method as label in the collection.
<%= f.collection_select :challenge_id, current_user.challenges.order(:deadline),:id, :name_with_deadline, include_blank: true %>
The name_with_deadline method will called for every object in the collection to retrieve the label text.
Hope this helps!
You can add a virtual attribute to you model like below:
def name_deadline
"#{name} #{deadline}"
end
collection_select:
<%= f.collection_select :challenge_id, current_user.challenges.order(:deadline),:id,:name_deadline, include_blank: true %>
I declare a model with an enum type like:
class Event < ActiveRecord::Base
enum event_type: { "special_event" => 0,
"pto" => 1,
"hospitality" => 2,
"classroom" => 3
}
Then in my update view, I have a form with:
<%= simple_form_for #event do |f| %>
<%= f.input :event_type, collection: Event.event_types.keys %>
...
<% end %>
This works great, and I get a select populated with my enumerated types.
When I perform the #event.update(event_params) in my controller, I can check the db and see that the event_type field has been updated to the correct integer value.
However, when I revisit the edit page, the select shows a nil value. If I check its value by adding a debug line to my form:
<%= f.input :event_type, collection: Event.event_types.keys %>
<%= debug #event %>
I see that the value for event_type is correct:
--- !ruby/object:Event
attributes:
...
event_type: '2'
but the input selector is still blank rather than showing "hospitality" as it should.
Any ideas would be greatly appreciated. :)
this line worked just fine. <%= f.input :event_type, collection: Event.event_types %>
Do you have to manually set the selected value ?
what's your version of simple_form ?
Vincent's solution gives me the error: '0' is not a valid 'fieldname'
I had to add keys as suggested in other stackoverflow post:
<%= f.input :event_type, collection: Event.event_types.keys %>
Use the enum_help gem. Lets you do this:
<%= f.input :event_type %>
I got stuck on this one too. I needed to titlieze my enums so they wouldn't look so wonky with the snake_case that I was using. I used to_a to take the ruby hash and turn it into an array and then I used collect to return a new array in the format that I needed.
collection: Event.event_type.to_a.collect{|c| [c[0].titleize, c[0]]}
Hopefully this will help someone else out.
Thanks for following this up. I believe the issue I reported was caused by an incorrect declaration for event_type. In my migration, I had accidentally defined event_type as String rather than an Integer:
class CreateEvents < ActiveRecord::Migration
def change
create_table :events do |t|
...
t.string :event_type
...
end
end
end
I believe that enum's should be declared as integers to work correctly. Unfortunately, I did not test that changing the type to integer makes it work because I actually ended up using a different approach. Rather than using an enum I instead defined my collection of event types in my model:
class Event < ActiveRecord::Base
def self.types
['Special_Events', "On-Going", 'PTO', "Classroom"]
end
...
end
And, then in my form, used simple form with this syntax:
<%= f.input :event_type, collection: Event.types, input_html: { autocomplete: 'off' } %>
And all worked well.
I'm using draper as decorator, so I'd like to add words translation into model's decorator. Here is my code:
app/decorators/meter_decorator.rb
class MeterDecorator < Draper::Decorator
delegate_all
STATUS_MAPPING = {
uninitialized: '未安装',
good: '良好',
broken: '故障',
disabled: '禁止'
}
def status
STATUS_MAPPING[object.status.to_sym]
end
end
views/meters/_form.html.erb
<%= f.input :status, collection: MeterDecorator::STATUS_MAPPING, label_method: :last, value_method: :first, include_blank: false %>
views/meters/index.html.erb
<td><%= meter.decorate.status %></td>
For example, assuming you have an enum like this one in your model (Model Role in the example):
enum :work_type, in_person: "in_person", remote: "remote", hybrid: "hybrid"
This will work in your view
= f.input :work_type, as: :select, collection: Role.work_types.collect { |key, value| [key.to_s.titleize, value] }
This will out put the following HTML:
<select class="form__input" name="role[work_type]" id="role_work_type">
<option selected="selected" value="in_person">In Person</option>
<option value="remote">Remote</option>
<option value="hybrid">Hybrid</option>
</select>
With some more research, I came up with the following solution:
<%= f.input :event_type, collection: Event.event_types.keys,
:selected => Event.event_types.keys[#event[:event_type].to_i],
input_html: { autocomplete: 'off' } %>
So, I had to do 2 things:
Use :selected to set the value of the selector. This required the cumbersome syntax Event.event_types.keys[#event[:event_type].to_i] to set the select value. I'd love to hear if there is a simpler syntax I could have used. :)
Set autocomplete: 'off' to prevent firefox from wanting to set the selector to its previous setting on a page reload.
Alternate, simpler solutions would be welcomed!
Background: Users and communities share a
has_many :through
relationship. Each community has a "community_type" string that identifies it (ie "Gender", "City", etc.).
Objective: In my edit form, I'd like to allow the user to edit his :community_ids based on community type. Something like:
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_ids, Community.filtered_by("Gender"), :id, :name) %>
<%= f.collection_select(:community_ids, Community.filtered_by("City"), :id, :name) %>
<% end %>
The issue is that the form only accepts the last form field value for :community_ids - in this case being the "City" - rather than merging all of them as one big :community_ids array.
Solution:
For those interested, I ended up refactoring my model code from the answers below to this:
%W[ community1 community2 community3 community4 ].each do |name|
define_method "#{name}" do
self.communities.filtered_by("#{name}").map(&:name)
end
define_method "#{name}_ids" do
self.communities.filtered_by("#{name}").map(&:id)
end
define_method "#{name}_ids=" do |val|
self.community_ids += val
end
end
Is there a reason you're using select boxes for a has_many relationship? It seems checkboxes would be more appropriate. If you want to go with select boxes, I don't think you can use FormHelper#select, because as far as I know, it's expecting a single value, and your community_ids is an array. This is why it's only picking one of the values you give it.
For a select box (or any field), you can combine the values across fields by adding [] to the parameter name which tells Rails that the parameter is an array of values. You can do this by using regular select_tag to create the fields, and setting the parameter name as follows:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
You could also go with Ryan's approach of sending separate parameters, though one downside is your User model will have to be very aware of the types of communities that exist, and you'll have to write separate logic in the User model for each type of community. This will make your resources less modular. But if you do go that way, I'd suggest using pseudo-attributes instead of a before_save so that your community_ids get updated automatically from the params:
class User < ActiveRecord::Base
...
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
...
end
And then your select_tag calls would look something like this:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_gender_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_city_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
Updated to complete tsherif's (better than my original) answer.
view.rb
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_gender_ids, Community.filtered_by("Gender"), :id, :name, {}, id: 'community-gender-options') %>
<%= f.collection_select(:community_city_ids, Community.filtered_by("City"), :id, :name, {}, id: 'community-city-options') %>
<% end %>
model.rb
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
def community_gender_ids
self.communities.select(:id).where(:community_type => 'gender').map(&:id)
end
def community_city_ids
self.communities.select(:id).where(:community_type => 'city').map(&:id)
end
Alternatively, you could write some CoffeeScript/Javascript to bind to the select tags and add the IDs to a hidden value which is then submitted to the server with the form.