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.
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 trying to implement my own validations in Ruby for practice.
Here is a class Item that has 2 validations, which I need to implement in the BaseClass:
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
def initialize(attributes = {})
#price = attributes[:price]
#name = attributes[:name]
end
validates_presence_of :name
validates_numericality_of :price
end
My problem is: the validations validates_presence_of, and validates_numericality_of will be class methods. How can I access the instance object to validate the name, and price data within these class methods?
class BaseClass
attr_accessor :errors
def initialize
#errors = []
end
def valid?
#errors.empty?
end
class << self
def validates_presence_of(attribute)
begin
# HERE IS THE PROBLEM, self HERE IS THE CLASS NOT THE INSTANCE!
data = self.send(attribute)
if data.empty?
#errors << ["#{attribute} can't be blank"]
end
rescue
end
end
def validates_numericality_of(attribute)
begin
data = self.send(attribute)
if data.empty? || !data.integer?
#valid = false
#errors << ["#{attribute} must be number"]
end
rescue
end
end
end
end
Looking at ActiveModel, you can see that it doesn't do the actual validation when validate_presence_of is called. Reference: presence.rb.
It actually creates an instance of a Validator to a list of validators (which is a class variable _validators) via validates_with; this list of validators is then called during the record's instantiation via callbacks. Reference: with.rb and validations.rb.
I made a simplified version of the above, but it is similar to what ActiveModel does I believe. (Skipping callbacks and all that)
class PresenceValidator
attr_reader :attributes
def initialize(*attributes)
#attributes = attributes
end
def validate(record)
begin
#attributes.each do |attribute|
data = record.send(attribute)
if data.nil? || data.empty?
record.errors << ["#{attribute} can't be blank"]
end
end
rescue
end
end
end
class BaseClass
attr_accessor :errors
def initialize
#errors = []
end
end
EDIT: Like what SimpleLime pointed out, the list of validators will be shared across and if they are in the base class, it would cause all the items to share the attributes (which would obviously fail if the set of attributes are any different).
They can be extracted out into a separate module Validations and included but I've left them in in this answer.
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
##_validators = []
def initialize(attributes = {})
super()
#price = attributes[:price]
#name = attributes[:name]
end
def self.validates_presence_of(attribute)
##_validators << PresenceValidator.new(attribute)
end
validates_presence_of :name
def valid?
##_validators.each do |v|
v.validate(self)
end
#errors.empty?
end
end
p Item.new(name: 'asdf', price: 2).valid?
p Item.new(price: 2).valid?
References:
presence.rb
with.rb
validators.rb
class variable _validators
First, let's try to have validation baked into the model. We'll extract it once it's working.
Our starting point is Item without any kind of validation:
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
#name = name
#price = price
end
end
We'll add a single method Item#validate that'll return an array of strings representing errors messages. If a model is valid the array will be empty.
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
#name = name
#price = price
end
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
private
def validators
[]
end
end
Validating a model means iterating over all associated validators, running them on the model and collecting results. Notice we provided a dummy implementation of Item#validators that returns an empty array.
A validator is an object that responds to #run and returns an array of errors (if any). Let's define NumberValidator that verifies whether a given attribute is an instance of Numeric. Each instance of this class is responsible for validating a single argument. We need to pass the attribute name to the validator's constructor to make it aware which attribute to validate:
class NumberValidator
def initialize(attribute)
#attribute = attribute
end
def run(model)
unless model.public_send(#attribute).is_a?(Numeric)
["#{#attribute} should be an instance of Numeric"]
end
end
end
If we return this validator from Item#validators and set price to "foo" it'll work as expected.
Let's extract validation-related methods to a module.
module Validation
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
private
def validators
[NumberValidator.new(:price)]
end
end
class Item
include Validation
# ...
end
Validators should be defined on a per-model basis. In order to keep track of them, we'll define a class instance variable #validators on the model class. It'll simply by an array of validators specified for the given model. We need a bit of meta-programming to make this happen.
When we include any model into a class then included is called on the model and receives the class the model is included in as an argument. We can use this method to customize the class at inclusion time. We'll use #class_eval to do so:
module Validation
def self.included(klass)
klass.class_eval do
# Define a class instance variable on the model class.
#validators = [NumberValidator.new(:price)]
def self.validators
#validators
end
end
end
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
def validators
# The validators are defined on the class so we need to delegate.
self.class.validators
end
end
We need a way to add validators to the model. Let's make Validation define add_validator on the model class:
module Validation
def self.included(klass)
klass.class_eval do
#validators = []
# ...
def self.add_validator(validator)
#validators << validator
end
end
end
# ...
end
Now, we can do the following:
class Item
include Validation
attr_accessor :name, :price
add_validator NumberValidator.new(:price)
def initialize(name: nil, price: nil)
#name = name
#price = price
end
end
This should be a good starting point. There're lots of further enhancements you can make:
More validators.
Configurable validators.
Conditional validators.
A DSL for validators (e.g. validate_presence_of).
Automatic validator discovery (e.g. if you define FooValidator you'll automatically be able to call validate_foo).
If your goal is to mimic ActiveRecord, the other answers have you covered. But if you really want to focus on a simple PORO, then you might reconsider the class methods:
class Item < BaseClass
attr_accessor :price, :name
def initialize(attributes = {})
#price = attributes[:price]
#name = attributes[:name]
end
# validators are defined in BaseClass and are expected to return
# an error message if the attribute is invalid
def valid?
errors = [
validates_presence_of(name),
validates_numericality_of(price)
]
errors.compact.none?
end
end
If you need access to the errors afterwards, you'll need to store them:
class Item < BaseClass
attr_reader :errors
# ...
def valid?
#errors = {
name: [validates_presence_of(name)].compact,
price: [validates_numericality_of(price)].compact
}
#errors.values.flatten.compact.any?
end
end
I don't understand the point to implement PORO validations in Ruby. I'd do that in Rails rather than in Ruby.
So let's assume you have a Rails project. In order to mimic the Active Record validations for your PORO, you need to have also 3 things:
Some kind of a save instance method within your PORO (to call the validation from).
A Rails controller handling CRUD on your PORO.
A Rails view with a scaffold flash messages area.
Provided all 3 these conditions I implemented the PORO validation (just for name for simplicity) this way:
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
include ActiveModel::Validations
class MyValidator
def initialize(attrs, record)
#attrs = attrs
#record = record
end
def validate!
if #attrs['name'].blank?
#record.errors[:name] << 'can\'t be blank.'
end
raise ActiveRecord::RecordInvalid.new(#record) unless #record.errors[:name].blank?
end
end
def initialize(attributes = {})
#price = attributes[:price]
#name = attributes[:name]
end
# your PORO save method
def update_attributes(attrs)
MyValidator.new(attrs, self).validate!
#...actual update code here
save
end
end
In your controller you have to manually process the exception (as your PORO is outside ActiveRecord):
class PorosController < ApplicationController
rescue_from ActiveRecord::RecordInvalid do |exception|
redirect_to :back, alert: exception.message
end
...
end
And in a view - just a common scaffold-generated code. Something like this (or similar):
<%= form_with(model: poro, local: true) do |form| %>
<% if poro.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(poro.errors.count, "error") %> prohibited this poro from being saved:</h2>
<ul>
<% poro.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, id: :poro_name %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
That's it. Just keep it all simple.
I've got a presenter which I would like to make the each_with_index method available in. I've added include Enumerable in my base presenter however, I'm still getting a no method error. My current code is below:
index.erb
<% #bids.each_with_index do |bid, index| %>
<% present bid do |bid_presenter| %>
<div class="large-4 medium-4 columns <%= bid_presenter.last_column %>"></div>
<% end %>
<% end %>
bid_presenter.rb
class BidPresenter < BasePresenter
presents :bid
def last_column
if index == bid.size - 1
'end'
end
end
end
base_presenter.rb
class BasePresenter
include Enumerable
def initialize(object, template)
#object = object
#template = template
end
private
def self.presents(name)
define_method(name) do
#object
end
end
# h method returns the template object
def h
#template
end
# if h is missing fallback to template
def method_missing(*args, &block)
#template.send(*args, &block)
end
end
bids_controller.erb
# GET /bids
# GET /bids.json
def index
#bids = current_user.bids
end
You are forwarding all not explicitly implemented methods to #template. Whatever #template is doesn't seem to properly implement each. You need to make #template respond properly to each.
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.
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