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
Related
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.
I am currently trying to make custom validations work with an input of dates, but, unfortunately, it doesn't seem to work.
There are two pages inside the application, Index page and Search page. Inside the index page there is a text field that takes in a date. I am using Chronic gem which parses text into dates. If the date is invalid, Chronic returns nil. If it is valid, it redirects to search page and shows the date.
The code I wrote so far doesn't seem to work properly, but what I want to achieve is..
1) to validate that Chronic doesn't return nil
2) to validate that date is greater than today's date
Please note that I am not using a database with this, I just want to be able to validate inputted date without ActiveRecord. If someone could help me with this, your help will be greatly appreciated.
views/main/index.html.erb
<%= form_tag({controller: "main", action: "search"}, method: "get") do %>
<%= label_tag(:q, "Enter Date:") %>
<%= text_field_tag(:q) %>
<%= submit_tag "Explore", name: nil %>
<% end %>
views/main/search.html.erb
<%= #show_date %>
main_controller.rb
def search
passed_info = Main.new
if passed_info.valid_date?
#show_date = passed_info
else
flash[:error] = "Please enter correct date!"
render :index => 'new'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :q
validates_presence_of :q
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
def valid_date?
require 'chronic'
if Chronic.parse(q).nil? || Chronic.parse(q) < Time.today
errors.add(:q, "is missing or invalid")
end
end
end
EDIT:
this is what goes wrong...
localhost:3000
then it redirects to ..
localhost:3000/main/search?utf8=%E2%9C%93&q=invalid+date+test
No validation, no date, nothing..
The Problem
Be more careful with return values. When you try to guard your controller with if valid_date?, what you're doing is checking to see if valid_date? returns false. If the parse fails, the return value is the output of errors.add, which in turn is the output of Array#<<. Relevantly, the output isn't nil or false, so it evaluates to true, thus the if clause passes and you move forward.
Potential Solution
You probably want to let the Rails Validation Framework do more work for you. Instead of treating valid_date? as a public method which the controller calls, call the valid? method that gets added by ActiveModel::Validations. valid? will return a boolean, based on whether all the model validations pass. Thus, you would, as is the Rails Way, call if model_instance.valid? in your controller.
This lets you just write validator methods in your model which express the logic you're trying to write. Right now, you have all validation logic for dates in a single method, with a single error message. Instead, you could put two methods, which add more descriptive individual error methods.
class YourClass
include ActiveModel::Validations
validate :date_is_valid
validate :date_not_before_today
private
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:q, "Date is invalid")
end
end
def date_not_before_today
if Chronic.parse(q) < Date.today
errors.add(:q, "Date cannot be before today")
end
end
end
Just as correctly suggested by ABMagil, I would like to post the full solution to my answer. In fact, this answer can apply really to anyone who wants to use validations using ActiveModel, with or without Chronic gem or dates involved. It can act as a valid template so to speak.
Frankly, most of my mistakes came from a really poor, at the time, understanding of what I actually tried to achieve. Most of the code needed major refactoring, see below the updates that I had to make. I tried to keep the code as well documented as possible.
Solution:
views/main/index.html.erb
<%= form_for #search, url: { action: "search" },
html: { method: :get } do |f| %>
# Displays error messages if there are any.
<% if #search.errors.any? %>
The form contains <%= pluralize(#search.errors.count, "error") %>.<br />
<% #search.errors.each do |attr, msg| %>
<%= msg %><br />
<% end %>
<% end %>
<%= f.label :q, "Enter Date:" %>
<%= f.text_field :q %>
<%= f.submit "Explore", :class => 'submit' %>
<% end %>
views/main/search.html.erb - same as before
<%= #show_date %>
main_controller.rb
def index
# Initializes a string from the form to a variable.
#search = Search.new
end
def search
# Retrieves the input from the form.
#search = Search.new(params[:search])
# Checks for validity,
# If valid, converts a valid string into a date.
# Redirects to search.html.erb
# If not valid, renders a new index.html.erb template.
if #search.valid?
#show_date = (Chronic.parse(params[:search][:q])).to_date
else
render :action => 'index'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
# Accepts the passed attribute from the form.
attr_accessor :q
# If form is submitted blank, then output the prompting message.
validates_presence_of :q, :message => "This text field can't be blank!"
# Two custom validations that check if passed string converts to a valid date.
validate :date_is_valid
validate :date_not_before_today
# Initializes the attributes from the form.
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
# Checks for persistence, i.e. if it's a new record and it wasn't destroyed.
# Otherwise returns false.
def persisted?
false
end
# ABMagil's code used for custom date validations
private
require 'chronic'
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:base, "Date is invalid")
end
end
def date_not_before_today
if !Chronic.parse(q).nil?
if Chronic.parse(q) < Date.today
errors.add(:base, "Date cannot be before today")
end
end
end
end
Result:
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).
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.
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.