Custom form builder not invoked in the fields_for method - ruby-on-rails

I have a nested form with 3 related models. I want all the fields to be built by my custom form builder PanelFormBuilder. However only the form_for seems to be processed by my form builder and the fields_for doesn't - it gets processed by the default one. I can't for the life of me work out why.
This works:
<%= panel_form_for #firm do |f| %>
This doesn't:
<%= panel_fields_for #firm.company do |c| %>
Here are the custom methods:
module ApplicationHelper
def panel_form_for(object, options = {}, &block)
options[:builder] = PanelFormBuilder
form_for(object, options, &block)
end
def panel_fields_for(object, options = {}, &block)
options[:builder] = PanelFormBuilder
fields_for(object, options, &block)
end
end
Here is the custom form builder
class PanelFormBuilder < ActionView::Helpers::FormBuilder
delegate :content_tag, :tag, to: :#template
%w[text_field text_area password_field].each do |method_name|
define_method(method_name) do |name, *args|
options = args.extract_options!
options.merge!(:class => "required")
super(name, *(args + [options]))
end
end
end
Can anyone tell me what I am doing wrong?
Thanks

Found my own answer. Turns out I should have been doing this:
<%= panel_form_for #firm do |f| %>
Followed by:
<%= f.fields_for :company do |c| %>
I don't need the panel_fields_for at all.

Look at the definition of fields_for:
fields_for(record_name, record_object = nil, options = {}, &block)
When you call fields_for with the block as an argument, Ruby will not be able to figure out that the options parameter you specify is actually the options, but instead think it is the record_object parameter.
You can just pass nil as the second parameter, and your custom builder will be instantiated:
fields_for(object, nil, options, &block)
This can be good to know in situations where you don't have access to the builder object of the form, like when you create some of the fields from a separate yield block.

Related

Rails: How to pass params of multiple checkbox to the model

i built this form that generate me some chebox with value like "U6", "U8" eccc
<%= form.label "Seleziona Categorie" %>
<% TeamCategory::NAMES.each do |category| %>
<%= check_box_tag 'categories_selected[]', category -%>
<% end %>
Now i have to pass the value of selected check_box to a method in my model.
Now is:
def create_tournament_team_categories
TeamCategory::NAMES.each do |name|
team_category = TeamCategory.where(name: name).first_or_create
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end
I would like to replace the TeamCategory::NAMES.each do with "selected check_box each do" and TeamCategory.where(name: name) with the value selected.
Thank you in advance
I am a newbie with Rails. What I see is that you took the part of the form to create the team, right?
For your code straight forward it could be:
<%= form.label "Seleziona Categorie" %>
<% TeamCategory::NAMES.each do |name| %> #you are looping through team category NAMES constant
<%= check_box_tag 'category_names_selected[]', name %>
<% end %>
Your form as is allows more than one category to be selected.
For the method:
def create_tournament_team_categories(category_names_selected)
category_names_selected.each do |name|
team_category = name
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end
you will probably use this method in your teams_controller.rb. In the controller, you should be able to retrieve from params a freshly created array of selected names with something along the lines with this.
#category_names_selected = params[:category_names_selected]
I do not know how complicated your app is so it might also be nested under ["team"][:category_names_selected] or ["team"]["category_names_selected"] in your params hash.
To see the exact structure of the params hash and adjust the equation above you can add for example require 'pry' at the top of your controller file and then but the binding.pry just after the part where your method is executed. When you restart the server and the app hits this part of the controller you should be able to see the exact structure of your params hash in the terminal.
You can then pass the array to the method that you can call in the controller. Do not forget to add :category_names_selected to the strong params in the controller. I hope this helps.
Controller on line 30
def create
#tournament = Tournament.new(tournament_params)
#tournament.sport_club = current_user.sport_club
#category_names_selected = params[:category_names_selected]
if #tournament.save
redirect_to tournaments_path, notice: 'Torneo creato con successo'
end
end
Method create_tournament_team_categories in the model
after_create :create_tournament_team_categories
def create_tournament_team_categories(category_names_selected)
#category_names_selected.each do |name|
team_category = name
self.tournament_team_categories << TournamentTeamCategory.create(team_category: team_category)
end
end

Controller, Model, View. Confusion with passing params

I have a simple self method defined in my model.
def self.search(name, type)
#handle name
#handle type
end
My confusion is with regards to the view & controller. Passing the correct values (or using the correct syntax).
In the view, a simple form.
<%= form_tag(index_path, method: :get) do %>
<%= label_tag :type, 'Type' %>
<% type_array = ["Foo", "Foo_One", "Foo_Two", "Foo_Three"] %>
<%= select_tag :type, options_for_select(type_array, selected: params[:type]), include_blank: true %>
<%= label_tag :name, 'Name' %>
<% name_array = ["Foo", "Foo_One", "Foo_Two", "Foo_Three"] %>
<%= select_tag :name, options_for_select(name_array, selected: params[:name]), include_blank: true %>
<%= submit_tag "Filter" %>
<% end %>
Confusion One
Under options_for_select, should it be select_tag :search or select_tag :type? Should it be selected: params[:search] instead??
In the controller
def index
#foo = Foo.all
#variation 1 that i tried
#foo = #foo.search(params[:search]) if search(params[:search]).present?
#variation 2 that i tried
#foo = #foo.search(params[:name,:type]) if search(params[:name,:type]).present?
#variation 3 that i tried
#foo = #foo.search(params[:name][:type]) if search(params[:name][:type]).present?
end
Confusion Two
Variation 3 kind of makes the most sense to me. But i dont see the self.search getting called. Also it throws an error (no implicit conversion of Symbol into Integer).
I'm not sure if the error is with my forms too?
Clearly i'm not very proficient with knowing where to pass the params and "collect" them. I've tried reading the ruby documentation but had a hard time understanding it.
I think the bigger picture would be, whats the proper syntax (or way to collect arguments) in a form for a method?
Confusion 1: It doesn't matter what the select_tags are called in your case as you don't seem to have it tied to an actual model attribute, but 'name' and 'type' are quite confusing simply as there are HTML attributes name and type on input fields. Still, it will still work the way you have done it and the 'selected' options look just fine.
Confusion 2: You need to access them individually from the params hash:
#foo.search(params[:name], params[:type]) if params[:name].present? || params[:type].present?
However, you have defined it as a self method as so:
class FooClass
def self.search(name, type)
# blah
end
This means you can't access it on an instance of the class, i.e #foo.search, you would call it from the class itself:
FooClass.search(params[:name], params[:type]) if params[:name].present? || params[:type].present?
If on the other hand you didn't have the 'self', as so:
class FooClass
def search(name, type)
# blah
end
The you could do:
#foo = FooClass.new
#my_var = #foo.search(params[:name], params[:type])
First off change self.search to just search because your index method in your controller is using an instance of your Foo model. when you do self.method that is a class level method and essentially works without having to create an instance of your class, which in this case is your Foo class.

Rails passing a block to a helper method

Viget Labs posted an article and gist detailing a rails helper method for adding a particular class (like .selected or .active) to a navigation link if it's url matches the current path.
You can use it in your layout like so:
= nav_link "News", articles_path, {class: "btn btn-small"}
<!-- which creates the following html -->
News
Nice. I'm using bootstrap, and want to have an icon in my button, so I need to generate the following html:
<i class="icon-home"> </i> News
I forked the gist and figured out a simple way of doing it. My fork lets the developer pass :inner_html and :inner_class to the helper like so:
= nav_link "News", articles_path, {class: "btn btn-small"}, {inner_html: 'i', inner_class: 'icon-home'}
It works fine, but I don't like my underlying implementation:
def link
if #options[:inner_html]
link_to(#path, html_options) do
content_tag(#options[:inner_html], '', :class => #options[:inner_class]) + " #{#title}"
end
else
link_to(#title, #path, html_options)
end
end
As you can see, I'm passing the new options to content_tag inside the block of a link_to method. I was hoping I would be able to refactor it in a few ways.
First of all, I'd prefer to be able to do this in my view:
= nav_link "News", articles_path, {class: "btn btn-small"} do
%i.icon-home
I want to give the inner html as a block, and not as attributes of the option hash. Can anyone give me any pointers on how to achieve this?
I thought it would a simple case of telling the nav_link method to accept a block:
def nav_link(title, path, html_options = {}, options = {}, &block)
LinkGenerator.new(request, title, path, html_options, options, &block).to_html
end
class LinkGenerator
include ActionView::Helpers::UrlHelper
include ActionView::Context
def initialize(request, title, path, html_options = {}, options = {}, &block)
#request = request
#title = title
#path = path
#html_options = html_options
#options = options
#block = block
end
def link
if #block.present?
link_to #path, html_options do
#block.call
#title
end
end
end
But this fails to output the icon, and instead inserts a number (4). I don't get it clearly. Anyone got any advice. Where can I go to read more about this sort of thing, as I really want to be able to figure stuff like this out without having to ask on stackoverflow.
I tried your problem and the following worked for me perfectly, in the helper:
def my_link(title, path, &block)
if block_given?
link_to path do
block.call
concat(title)
end
else
link_to title, path
end
end
Usage:
my_link "No title", User.first do
%i.icon-home
The solution in the end was as follows:
# capture the output of the block, early on if block_given?
def nav_link(title, path, html_options = {}, options = {}, &block)
LinkGenerator.new(request, title, path, html_options, options, (capture(&block) if block_given?)).to_html
end
I also had to modify my link method:
def link
if #block.present?
link_to(#path, html_options) do
#block.concat(#title)
end
else
link_to(#title, #path, html_options)
end
end
I've updated my gist. You could probably hack it up to accept more complex blocks.

Rails 3. How to add a helper that ActiveAdmin will use?

I'm creating a helper to be used by Formtastic but I get the undefined local variable or method error. I don't know where to put it so it can work.
I already tried in the application_helper.rb and in app/helpers/active_admin/view_helpers.rb
You can define them in app/helpers/ as you tried but you need to include them trough the active admin's initializer like this:
# in config/initializers/active_admin.rb
ActiveAdmin.setup do |config|
....
end
module ActiveAdmin::ViewHelpers
include ApplicationHelper
end
You need to put your helper functions in app/helpers/active_admin/views_helper.rb file
Example:
module ActiveAdmin::ViewsHelper #camelized file name
def my_helper
# do something
end
end
What I have found using ActiveAdmin 0.6.1 is that ActiveAdmin will look for helpers in app/helpers/active_admin/*_helper.rb, but the name doesn't really matter.
What does matter is:
the filename must end in "_helper.rb"
the module name must be the camel-case of the file name
the file must be in app/helpers/active_admin/ directory.
If anyone knows where this is officially documented, that would be awesome.
Here is an example: https://gist.github.com/afred/7035a657e8ec5ec08d3b
app/helpers/active_admin/view_helpers.rb
didn't help me
EDITED: i changed it to views_helper.rb & ViewsHelper accordingly and it worked
*but if you want to define it only for certain resource, you can do it in my way
i had to define
#app/helpers/active_admin/categories_helper.rb
module ActiveAdmin::CategoriesHelper
def helper_method
end
end
for my active_admin resource app/admin/categories.rb
Another way to do this is to make the specific ActiveAdmin controller generated behind-the-scenes include the helper. This method will allow making the inclusion of the helpers explicit per file rather than global.
ActiveAdmin.register MyModel do
controller do
include MyHelper
end
end
I can make it work in ActiveAdmin 0.6.1 (finally!). The solution is to create a helper module as following:
# app/helpers/active_admin_helpers.rb
module ActiveAdminHelpers
# make this method public (compulsory)
def self.included(dsl)
# nothing ...
end
# define helper methods here ...
def helper_method
...
end
end
then include this module this way:
# app/admin/[resource].rb
include ActiveAdminHelpers
ActiveAdmin.register [Resource] do
...
end
Actually, it's not a nice solution but it's DRY and working good. I have already read and tried a lot of methods and solutions such as ViewHelpers module (put under 'app/helpers' or 'app/admin/active_admin'), ActiveAdmin::DSL monkey patching, ... but those never worked in version 0.6.1 (I don't have any ideas about other versions) :(
Defining ActiveAdmin::ViewHelpers in app/admin/active_admin/view_helpers.rb works for me with activeadmin 0.3.4 and 0.5.0.
Using activeadmin 1.0.0.pre1 from git://github.com/activeadmin/activeadmin.git
Rails 4.2.1
This worked for me...
my_app/app/helpers/active_admin/resources_helper.rb
module ActiveAdmin
module ResourcesHelper
def resource_form_for(_resource, _params, _options = {}, &_block)
url = if _resource.new_record?
UrlBuilder.resources_path(_resource.class, _params)
else
UrlBuilder.resource_path(_resource.class, _params)
end
method = _resource.new_record? ? :post : :put
options = { url: url, method: method, builder: ActiveAdmin::FormBuilder }
options.merge!(_options)
semantic_form_for([:admin, _resource], options) do |f|
_block.call(f)
end
end
end
end
my_app/app/admin/balance_sheets.rb
ActiveAdmin.register BalanceSheet do
form partial: 'form'
end
my_app/app/views/admin/balance_sheets/_form.html.erb
<%= resource_form_for(resource, params) do |f| %>
<%= f.inputs "Fields" do %>
<%= f.input :progress_status %>
<%= f.input :crew %>
<%= f.input :shift %>
<%= f.input :expected_progress %>
<%= f.input :real_progress %>
<%= f.input :analyst, collection: User.analysts %>
<%= f.input :activity_ids, as: :check_boxes, collection: Activity.balance_sheet_activities %>
<%= f.input :worker_ids, as: :check_boxes, collection: Worker.all %>
<% end %>
<%= f.actions %>
<% end %>
You can also use ActiveAdmin partials :
render partial: 'admin/my_partial', locals: { var: my_var }
And inside app/views/admin/_my_partial.html.arb your active_admin ruby code.
What worked for me with Rails 3.2.11 and and gem activeadmin (0.5.1) was not adding the app/active_admin/view_helpers.rb file, or declaring any modules in config/initializers/active_admin.rb
I put my helpers logically, by model, into the app/*_helpers.rb files. Then inside the app/admin/model.rb file I used:
# app/admin/[resource].rb
ActiveAdmin.register [Resource] do
...
filter :gender, as: :select, collection: proc{genders}
...
end
To use the helper in filters, to display a drop down list of genders to filter on, in the list view. For the corresponding create form fields, I used:
# app/admin/[resource].rb
ActiveAdmin.register [Resource] do
form do |f|
f.inputs "Case Manager" do
...
f.input :gender, as: :radio, collection: genders
...
f.buttons
end
end
end
To display radio buttons for the input form.
Not sure why the proc{} is required outside of the form do |f| block, but if anyone can explain why it's a bad idea, I'll find a different way.

Custom form helpers

Is there a way that I can create a custom form helper so that instead of:
special_field_tag :object, :method
I can achieve something like:
form.special_field :method
Yes, you can add to the FormBuilder class and get access to the object passed into the form_for. I've done this for a lot of things: dates, times, measurements, etc. Heres an example:
class ActionView::Helpers::FormBuilder
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
include ActionView::Helpers::CaptureHelper
include ActionView::Helpers::AssetTagHelper
# Accepts an int and displays a smiley based on >, <, or = 0
def smile_tag(method, options = {})
value = #object.nil? ? 0 : #object.send(method).to_i
options[:id] = field_id(method,options[:index])
smiley = ":-|"
if value > 0
smiley = ":-)"
elsif smiley < 0
smiley = ":-("
end
return text_field_tag(field_name(method,options[:index]),options) + smiley
end
def field_name(label,index=nil)
output = index ? "[#{index}]" : ''
return #object_name + output + "[#{label}]"
end
def field_id(label,index=nil)
output = index ? "_#{index}" : ''
return #object_name + output + "_#{label}"
end
end
Which you can use like this:
<% form_for #quiz do |f| %>
<%= f.smile_tag(:score) %>
<% end %>
There are some instance variables created by Rails that you can access in these helper methods:
#object - the model object specified by the form
#object_name - the class name of the object
#template - I think its an instance of the ActionView, you can possibly bypass all the includes I added by calling methods on the template. Haven't tried that yet.
#options - options passed to the FormBuilder when its created by the form_for call
I wrote the field_id and field_name methods to create these attributes on the HTML input elements the same way the regular helpers do, I'm sure there is a way to tie into the same methods that Rails uses, but I haven't found it yet.
The sky is the limit on what you can do with these helper methods, they simply return strings. You can create entire HTML tables or pages in one, but you better have a good reason to.
This file should be added in the app/helpers folder
#Tilendor, thanks so much for the pointers. Here is an example of an enum_select form tag helper that uses Rails 4.1 enums to automatically populate the options of a select tag:
# helpers/application_helper.rb
module ApplicationHelper
class ActionView::Helpers::FormBuilder
# http://stackoverflow.com/a/2625727/1935918
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
def enum_select(name, options = {})
# select_tag "company[time_zone]", options_for_select(Company.time_zones
# .map { |value| [value[0].titleize, value[0]] }, selected: company.time_zone)
select_tag #object_name + "[#{name}]", options_for_select(#object.class.send(name.to_s.pluralize)
.map { |value| [value[0].titleize, value[0]] }, selected: #object.send(name))
end
end
end
The trickiest construct is #object.class.send(name.to_s.pluralize) which produces a hash of available values (e.g., Company.time_zones). Putting it in helpers/application_helper.rb makes it automatically available. It is used like:
<%= f.label :billing_status %>:
<%= f.enum_select :billing_status %><br />
Our app was displaying phone numbers in text fields, and we wanted to omit the country code for domestic numbers. I was using form helpers. After reading this and rails source a bit, i came to this solution:
class ActionView::Helpers::FormBuilder
def phone_text_field name, options = {}
value = object.public_send(name).to_s
if value.start_with? "9" # national number
value = value[1..-1]
options["value"] = value
end
text_field name, options
end
end
I put this on app/helpers/application_helper.rb and use it like i use text_field() helper. Hope this helps someone.

Resources