simple_form accept a hash for collection input - ruby-on-rails

Current behavior
With simple_form you need to pass an array:
<%= f.input :my_field, collection: [[true,"Yes"],[false,"No"]] %>
Expected behavior
It would be nice to be able to pass a hash, so you do not need to do invert.sort on every hash passed. Is there any way to do this for every input?
<%= f.input :my_field, collection: {"true"=> "yes", "false"=>"No" } %>
Is it possible to pass a hash directly into the input without invert.sort?

You can add your own helper my_simple_form_for to use your own YourFormBuilder
module ApplicationHelper
def my_form_for record, options = {}, &block
options[:builder] = MyFormBuilder
simple_form_for(record, options, &block)
end
end
Or just use it in this way:
<%= simple_form_for #record, builder: MyFormBuilder do |f| %>
In your own builder you can overwrite input:
class YourFormBuilder < SimpleForm::FormBuilder
def input(attribute_name, options = {}, &block)
options[:collection] = options[:collection].invert.sort if options[:collection].present? and options[:collection].kind_of? Hash
super
end
end

Based on our earlier Q&A, you could enhance the Hash extension to include as_select_options:
module DropdownExt
def self.extended(receiver)
receiver.each do |k,v|
define_method(k) do
v.is_a?(Hash) ? v.extend(DropdownExt) : v
end
end
define_method(:as_select_options) do
unless receiver.values.map{|v|v.class}.include?(ActiveSupport::HashWithIndifferentAccess)
receiver.invert.sort
else
[]
end
end
end
end
class Dropdowns
class << self
private
def dropdowns_spec
YAML.load_file("#{path}").with_indifferent_access
end
def path
Rails.root.join("spec/so/dropdowns/dropdowns.yaml") # <== you'll need to change this
end
end
dropdowns_spec[:dropdown].each do |k,v|
define_singleton_method k do
v.extend(DropdownExt)
end
end
%i(
truck_model
bike_model
).each do |to_alias|
singleton_class.send(:alias_method, to_alias, :car_model)
end
end
Which would let you do something like:
Dropdowns.car_model.field1.as_select_options
=> [["false", "no"], ["true", "yes"]]
Or, I suppose:
<%= f.input :my_field, collection: Dropdowns.car_model.field1.as_select_options %>
It doesn't avoid invert.sort. But, it does bury it a little bit and wrap it up in a convenient as_select_options method.

Related

Rails ActiveModel inaccessible values?

When i written my poll app i was get an error:
http://screenshot.sh/m3eeke1da49kL
So I tried to comment code and inspected value.
http://screenshot.sh/mGR8cOuGizBRx
Why i can't access the values? There is a code of helper method:
def poll_option(poll)
#option = VoteOption.find_by(poll_id: poll.id)
content_tag :div, class: 'form-group' do
content_tag(:label) do
unless current_user.voted_for?(poll)
radio_button_tag 'vote_option[id]', #option.id
end
#option.title
end
visualize_votes_for #option
end
#option.inspect
end
ok i resolved the problem but the code
def poll_option(poll)
#option = VoteOption.find_by(poll_id: poll.id)
#option.each do |o|
content_tag :div, class: 'form-group' do
content_tag(:label) do
unless current_user.voted_for?(poll)
radio_button_tag 'vote_option[id]', o.id
end
o.title
end
visualize_votes_for o
end
end
end
Gives an error that "each" is undefined method.

Search after multiple parameters, Ruby on Rails

I'm very new to Ruby on Rails and trying to create a search function that allows the user to serach multiple parameters at the same time; from, and to. Something to keep in mind is that there will probably be even more parameters later on in the development. I've got it to work when searching for one of the fields, but not more than that.
Search view:
<%= form_tag(journeys_path, :method => "get", from: "search-form") do %>
<%= text_field_tag :search_from, params[:search_from], placeholder: "Search from", :class => 'input' %>
<%= text_field_tag :search_to, params[:search_to], placeholder: "Search to", :class => 'input' %>
<%= submit_tag "Search", :class => 'submit' %>
<% end %>
Method:
class Journey < ActiveRecord::Base
def self.search(search_from)
self.where("from_place LIKE ?", "%#{search_from}%")
end
end
Controller:
class JourneysController < ApplicationController
def index
#journeys = Journey.all
if params[:search_from]
#journeys = Journey.search(params[:search_from])
else
#journeys = Journey.all.order('created_at DESC')
end
end
def search
#journeys = Journey.search(params[:search_from])
end
end
I've tried some gems and all kind of solutions that I've found in other questions, but I'm just not good enough at RoR yet to succesfully apply them correctly without help. I would appreciate any help I can get.
Thank you!
Model:
class Journey < ActiveRecord::Base
def self.search(search_from, search_to)
self.where("from_place LIKE ? and to_place LIKE ?", "%#{search_from}%", "%#{search_to}%")
end
end
Controller:
class JourneysController < ApplicationController
def index
if params[:search_from] and params[:search_to]
#journeys = search
else
#journeys = Journey.all.order('created_at DESC')
end
end
def search
#journeys = Journey.search(params[:search_from], params[:search_to])
end
end
The best approach here is to incapsulate your search form as a separate Ruby class. Using Virtus here helps to get type coercion for free.
class SearchForm
include Virtus.model # Our virtus module
include ActiveModel::Model # To get ActiveRecord-like behaviour for free.
attribute :from, String
attribute :to, String
# Just to check if any search param present,
# you could substitute this with validations and just call valid?
def present?
attributes.values.any?{|value| value.present?}
end
```
In Rails 3 IIRC you also have to include ActiveModel::Validations to be able to validate your form input if needed.
Now, let's see how to refactor controller. We instantiate form object from params and pass that to the model query method to fetch records needed. I also moved ordering out of if clause and used symbol ordering param - cleaner IMO.
def index
#search_form = SearchForm.new(search_params)
if #search_form.valid? && #search_form.present?
#journeys = Journey.search(#search_form)
else
#journeys = Journey.all
end
#journeys = #journeys.order(created_at: :desc)
end
def search
#journeys = Journey.search(SearchForm.new(search_params)
end
private
def search_params
params.require(:search_form).permit(:from, :to)
end
Now to the view: form_for will work perfectly with our form object, as will simple_form_for
<%= form_for #search_form, url: journeys_path, method: :get do |f| %>
<%= f.text_field :from, placeholder: "Search from", class: 'input' %>
<%= f.text_field :to, placeholder: "Search to", class: 'input' %>
<%= f.submit "Search", class: 'submit' %>
<% end %>
View looks now much shorter and cleaner. Incapsulating params in object makes working with search params muuuuch easier.
Model:
class Journey < ActiveRecord::Base
def self.search(search_form)
if search_form.kind_of?(SearchForm)
journeys = all # I'm calling Journey.all here to build ActiveRecord::Relation object
if search_form.from.present?
journeys = journeys.where("from_place LIKE ?", "%#{search_form.from}%")
end
if search_form.to.present?
journeys = journeys.where("to_place LIKE ?", "%#{search_form.to}%")
end
else
raise ArgumentError, 'You should pass SearchForm instance to Journey.search method'
end
end
end
Notice how I build ActiveRecord::Relation object by calling Journeys.all and applying each search param if present. Chaining where like that would put AND in between automatically, if you need OR Rails 4 has it: Journey.or(condition).
Pros of this approach:
You are using Plain Old Ruby Classes, almost no magic here, and it works like usual Rails model in many ways. Putting search params in the object makes it a lot easier to refactor code. Virtus is the only dependency, sans Rails itself of course, and it's more for convenience and to avoid writing boring boiler-plate code.
You can easily validate input if needed (If you really want to be strict about input and show user validation error instead of silently executing stupid query with contradicting conditions and returning no results).

Rails generalizing a method that uses models

I am trying to generalize few methods that will be used by multiple models/views/controllers but i'm having no luck. Here is the original code that works when it is just for 1 set called Trucks.
View
<h2>Trucks</h2>
<%= form_tag trucks_path, :method => 'get' do %>
<%= hidden_field_tag :direction, params[:direction] %>
<%= hidden_field_tag :sort, params[:sort] %>
<p>
Search:
<%= text_field_tag :search %>
by
<%= select_tag :search_column, options_for_select(Truck.translated_searchable_columns(['attribute1']), params[:search_column]) %>
<%= submit_tag "Search" %>
</p>
<% end %>
<!-- Display code goes here, but im not showing since its just a table -->
Controller
def index
#trucks = Truck.search(params[:search], params[:search_column]).order(sort_column(Truck, "truck_no") + " " + sort_direction)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #trucks }
end
end
Model
class Truck < ActiveRecord::Base
attr_accessible :attribute1, :attribute2, :attribute3
def self.search(keyword, column_name)
if self.column_names.include?(column_name.to_s)
where("trucks.#{column_name} LIKE ?", "%#{keyword}%")
else
scoped
end
end
def self.searchable_columns(unwanted_columns)
self.column_names.reject{ |column| unwanted_columns.include?(column) }
end
def self.translated_searchable_columns(unwanted_columns)
columns = self.searchable_columns(unwanted_columns)
result = columns.map{ |column| [Truck.human_attribute_name(column.to_sym), column] }
result
end
end
All this works without a hitch, now I can't figure out for the life of me how to move these methods to lib and have them generalized so that lets say Trailers is able to call in the same method and pass in its information and achieve the same result. I am trying to make this code DRY as possible. Could anyone explain me what I need to do to achieve this? How does lib access the database?
The concept you're looking for is called a "concern". Rails has a convenience module for implementing concerns called ActiveSupport::Concern. Here's how you might extract your model methods:
module Searchable
extend ActiveSupport::Concern
module ClassMethods
def search(keyword, column_name)
if column_names.include?(column_name.to_s)
where("#{table_name}.#{column_name} LIKE ?", "%#{keyword}%")
else
scoped
end
end
def searchable_columns(unwanted_columns)
column_names.reject{ |column| unwanted_columns.include?(column) }
end
def translated_searchable_columns(unwanted_columns)
columns = searchable_columns(unwanted_columns)
columns.map{ |column| [human_attribute_name(column.to_sym), column] }
end
end
end
And then in your model:
class Truck < ActiveRecord::Base
include Searchable
attr_accessible :attribute1, :attribute2, :attribute3
end
As for where exactly you should store the Searchable module, it's up to you -- it just has to be someplace that's included in config.autoload_paths, just like a model or controller. Rails 4 introduced a convention that model concerns are stored in app/models/concerns, and controller concerns in app/controllers/concerns, but there is nothing special about these locations other than being autoloaded by default.

Get Rails 3 to sanitize output by default instead of escaping it

By default, Rails 3 escapes strings you output directly – e.g., <%= '<h1>' %> renders as <h1>
Because of this I have to annoyingly do this a lot:
<%= sanitize #post.body %>
Is there any way I can make this the default? I.e., I want this:
<%= #post.body %>
to be equivalent to:
<%= sanitize #post.body %>
instead of:
<%= h #post.body %>
as it is by default
class ActiveSupport::SafeBuffer
def concat(value)
super(ERB::Util.h(value))
end
alias << concat
def dirty?
false
end
end
Have fun being XSS'd. Do not use in production. This does disable XSS protection entirely and you can't even explicitly tell a piece of data is unsafe. I'd rather do
class Post
def body_with_raw
body_without_raw.html_safe
end
alias_method_chain :body, :raw
end
or even
class ActiveRecord::Base
def self.html_safe(*attributes)
attributes.each do |attribute|
name = attribute + "with_raw"
before = attribute + "without_raw"
define_method name do
before.html_safe
end
alias_method_chain attribute, "raw"
end
end
end
so you can
class Post
html_safe :body
end
Based on Tass' answer, I feel like this might work (but I'm not sure):
class ActiveSupport::SafeBuffer
def concat(value)
if dirty? || value.html_safe?
super(value)
else
# super(ERB::Util.h(value)) # this is what Rails does by default
super(ActionController::Base.helpers.sanitize(value))
end
end
end

Multilevel block methods in rails

I am making a view helper to render set of data in a format. I made these classes
require 'app/data_list/helper'
module App
module DataList
autoload :Builder, 'app/data_list/builder'
##data_list_tag = :ol
##list_tag = :ul
end
end
ActionView::Base.send :include, App::DataList::Helper
helper is
module App
module DataList
module Helper
def data_list_for(object, html_options={}, &block)
builder = App::DataList::Builder
arr_content = []
object.each do |o|
arr_content << capture(builder.new(o, self), &block)
end
content_tag(:ol, arr_content.join(" ").html_safe, html_options).html_safe
end
end
end
end
builder is
require 'app/data_list/column'
module App
module DataList
class Builder
include App::DataList::Column
include ActionView::Helpers::TagHelper
include ActionView::Helpers::AssetTagHelper
attr_reader :object, :template
def initialize(object, template)
#object, #template = object, template
end
protected
def wrap_list_item(name, value, options, &block)
content_tag(:li, value).html_safe
end
end
end
end
column module is
module App
module DataList
module Column
def column(attribute_name, options={}, &block)
collection_block, block = block, nil if block_given?
puts attribute_name
value = if block
block
elsif #object.respond_to?(:"human_#{attribute_name}")
#object.send :"human_#{attribute_name}"
else
#object.send(attribute_name)
end
wrap_list_item(attribute_name, value, options, &collection_block)
end
end
end
end
Now i write code to test it
<%= data_list_for #contracts do |l| %>
<%= l.column :age %>
<%= l.column :contact do |c| %>
<%= c.column :phones %>
<% end %>
<%= l.column :company %>
<% end %>
Every thing is working fine , age , contact , company is working fine. But phones for the contact is not showing.
Does any one have an idea, i know i have missed something in the code. Looking for your help.
Updated question with complete source is enter link description here
There are two issues I can see in the column module.
1) If a block is provided you're setting it to nil - so if block is always returning false. 2) Even if block wasn't nil you're just returning the block as the value, not actually passing control to the block. You should be calling block.call or yielding. Implicit blocks execute faster, so I think your column module should look more like this:
module DataList
module Column
def column(attribute_name, options={})
value = begin
if block_given?
yield self.class.new(#object.send(attribute_name), #template)
elsif #object.respond_to?(:"human_#{attribute_name}")
#object.send :"human_#{attribute_name}"
else
#object.send(attribute_name)
end
end
wrap_list_item(attribute_name, value, options)
end
end
end
The solution is now posted in the discussion.

Resources