We have an ActiveRecord model with an html attribute (say Post#body). Is there a nice way that calling body on a post returns an html_safe? string? E.g.:
class Post < ActiveRecord::Base
# is_html_escaped :body or somesuch magic
end
Post.first.body.html_safe? # => true
The problem otherwise is that we have to call raw everything we show that field.
Here's a way I found:
class Post < ActiveRecord::Base
def message
super.html_safe
end
def message=(new_mess)
new_mess = ERB::Util.html_escape(new_mess.sanitize) unless new_mess.html_safe?
super(new_mess)
end
end
FYI. I made a module for this
module SanitizeOnly
def self.included(mod)
mod.extend(ClassMethods)
end
module ClassMethods
def sanitize_on_input_only(*attribute_names)
attribute_names.map(&:to_s).each do | attribute_name |
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{attribute_name}
super.html_safe
end
def #{attribute_name}=(new_val)
new_val = ERB::Util.html_escape(new_val.sanitize) unless new_val.html_safe?
super(new_val)
end
RUBY
end
end
end
end
to use it just include it in your model and add the attributes you want to avoid using raw for to a sanitize_on_input_only line like the following:
sanitize_on_input_only :message, :another_attribute, ...
Related
How can I dynamically create methods like this using ruby metaprogramming ?
class CommentBridge < Bridge
def id(comment)
comment.id
end
def message(comment)
comment.message
end
def votes_count(comment)
comment.votes_count
end
end
I tried this but it is not working.
['id', 'message', 'votes_count'].each do |method|
define_method "#{method}" do |parameter|
method(parameter.method)
end
end
You should use public_send to call methods based on their name:
['id', 'message', 'votes_count'].each do |method|
define_method "#{method}" do |parameter|
parameter.public_send(method)
end
end
I do not think that you need different comment every time (probably you do). So I'd recommend to simply get rid of this comment argument.
There are the options.
Using RubyOnRails (I see you question is tagged so) you can use delegate (as #SimpleLime has already commented)
class CommentBridge < Bridge
attr_reader :comment
def initialize(comment_)
#comment = comment_)
end
delegate :id, :message, :votes_count, to: :comment
end
In case of pure Ruby 2 use Forwardable:
class CommentBridge
extend Forwardable
attr_reader :comment
def initialize(comment_)
#comment = comment_)
end
def_delegators :comment, :id, :message, :votes_count
end
If you want to provide additional methods on top of you comment object and forward all the rest methods use SimpleDelegator (assuming that this Brigde in namgin means that your class is just a wrapper):
class CommentDecorator < SimpleDelegator
def hello
'hello'
end
end
comment = Commend.find(params[:id])
decorated_comment = CommentDecorator.new(comment)
You can also define method missing:
class CommentBridge < Bridge
attr_reader :comment
def initialize(comment_)
#comment = comment_)
end
def method_missing(m, *args)
if [:id, :message, :comment].include?(m)
comment.public_send(method, *args)
else
super
end
end
end
Finally, you can create your own delegation-DSL on top of define_method, but I think this is the extra in that case.
I don't think that method_missing or define_method inside loop is neat although it works.
I'm fairly new to rails so bear with me.
I want to strip whitespace from a selective group of input forms.
But I would like a DRY solution.
So I was thinking there might be a solution such as a helper method, or a custom callback. Or a combination such as before_validation strip_whitespace(:attribute, :attribute2, etc)
Any help is awesome! Thanks!
EDIT
I have this in my model file ...
include ApplicationHelper
strip_whitespace_from_attributes :employer_name
... and I have this in my ApplicationHelper ...
def strip_whitespace_from_attributes(*args)
args.each do |attribute|
attribute.gsub('\s*', '')
end
end
but now I'm getting the error message:
undefined method `strip_whitespace_from_attributes' for "the test":String
EDIT II -- SUCCESS
I added this StripWhitespace module file to the lib directory
module StripWhitespace
extend ActiveSupport::Concern
module ClassMethods
def strip_whitespace_from_attributes(*args)
args.each do |attribute|
define_method "#{attribute}=" do |value|
#debugger
value = value.gsub(/\s*/, "")
#debugger
super(value)
end
end
end
end
end
ActiveRecord::Base.send(:include, StripWhitespace)
and then added this to any model class this wants to strip whitespace ...
include StripWhitespace
strip_whitespace_from_attributes #add any attributes that need whitespace stripped
I would go with sth like this (not tested):
module Stripper # yeah!
extend ActiveSupport::Concern
module ClassMethods
def strip_attributes(*args)
mod = Module.new
args.each do |attribute|
define_method "#{attribute}=" do |value|
value = value.strip if value.respond_to? :strip
super(value)
end
end
end
include mod
end
end
end
class MyModel < ActiveRecord::Base
include Stripper
strip_attributes :foo, :bar
end
m = MyModel.new
m.foo = ' stripped '
m.foo #=> 'stripped'
If you can get your attributes in to a single array (perhaps there's a [:params] key you can use instead), you can do the following:
class FooController < ApplicationController
before_create strip_whitespace(params)
private
def strip_whitespace(*params)
params.map{ |attr| attr.strip }
end
end
I have a database with a field 'update' corresponding to updated_at.
The database is not only used by a rails application and it's not possible to migrate the field currently.
In my model, I want to make an alias on the attribute :
class Model < ActiveRecord::Base
alias_attribute 'updated_at', 'update'
...
end
But this call the update method of the model.
So i found the alias_attribute definition here : github code
I want to monkey patch this for something like :
def self.alias_attribute(new_name, old_name)
module_eval <<-STR, __FILE__, __LINE__ + 1
def #{new_name}; self.attributes[#{old_name}]; end
def #{new_name}?; self.attributes[#{old_name}?; end
def #{new_name}=(v); self.attributes[#{old_name} = v; end
STR
end
How can i make this work?
How to decorate Plugin object to add automatically virtual page_links attribute to attributes if Plugin name is SomePluggin?
Example:
#page.plugins
# As is => [#<Plugin id: 241, url: "some_url", page_id: 118>]
# As I want to be: => [#<Plugin id: 241, url: "some_url", page_links: "1234,main_page,articles", page_id: 118>]
Current code:
module Cms
class SomePluggin
def initialize(plugin)
#url = plugin.url
#it doesn't work
plugin.page_links = "1234,main_page,articles"
plugin.attributes.merge!("page_links" => "1234,main_page,articles")
#decorate = SimpleDelegator.new plugin
end
def get_content
puts "content"
end
end
module Pluggin
extend ActiveSupport::Concern
included do
after_initialize :pluggin
end
delegate :get_content, to: :pluggin
attr_writer :pluggin
def pluggin
#pluggin ||= "Cms::Pluggin::#{name}".camelize.constantize.new(self) # name=SomePluggin
end
end
end
Model:
class Plugin < ActiveRecord::Base
attr_accessor :page_links
belongs_to :page
include Cms::Pluggin
end
I assume page_links would be a virtual attribute. Your code structure is complicated, and you can basically add page_links and page_links= methods to class Plugin with initialization, but if you want to keep this attribute in SomePluggin, you can do it in this way:
module Cms
class SomePluggin
attr_accessor :page_links
def initialize
self.page_links = "1234,main_page,articles"
end
def get_content
puts "content"
end
end
module Pluggin
extend ActiveSupport::Concern
included do
after_initialize :wrap_object
end
def wrap_object
pluggin
end
delegate :get_content, :page_links, :page_links=, to: :pluggin
attr_writer :pluggin
def pluggin
#pluggin ||= SomePluggin.new
end
end
end
Here I've added :page_links and :page_links= methods to SomePluggin and initial value setting in initialize method.
Some console output:
p = Plugin.new
p.page_links # => "1234,main_page,articles"
p.page_links = '123'
p.page_links # => "123"
I'm building a gem and I want part of its functionality to extend ActiveRecord::Associations::Builder::BelongsTo but I cannot figure out how to do it
so basically user should be able to specify:
class Event < ActiveRecord::Base
belongs_to :users, foo: true
end
anyone know how to do it ??
This wont work:
module Mygem
module BelongsToFoo
def valid_options
super + [:foo]
end
#... other functionality
end
end
class ActiveRecord::Associations::Builder::BelongsTo
extend MyGem::BelongsToFoo
end
console
ActiveRecord::Associations::Builder::BelongsTo.valid_options.include? :foo
#=> false ... :(
Event
ArgumentError: Unknown key: foo
belongs_to source code
=============================================================================
Update
flowing delwyns answer I tried to have a another look on my code and he is right it should be included however ActiveRecord::Associations::Builder::BelongsTo has a variable valid_options as well.
so I can do
ActiveRecord::Associations::Builder::BelongsTo.new(:a, :b, :c).valid_options.include? :foo
# => true
but also
ActiveRecord::Associations::Builder::BelongsTo.valid_options.include? :foo
# => true
so it should really look like this
module MyGem
module BelongsToFoo
extend ActiveSupport::Concern
included do
self.valid_options += [:foo]
end
def valid_options
super + [:foo]
end
def define_callbacks(model, reflection)
# this wont get executed
add_foo_callbacks(model, reflection)# if options[:foo]
super
end
def add_foo_callbacks(model, reflection)
# therefore this wont either
end
end
end
Even if I try this
module MyGem
module BelongsToFoo
def define_callbacks(model, reflection)
raise "dobugging"
end
end
end
nothing will happen, Rails completely ignore my method override
So yes I can define my own option, however they do nothing :( any suggestions ?
There is a built in approach for extending association proxies, see http://guides.rubyonrails.org/association_basics.html#association-extensions
class Event < ActiveRecord::Base
belongs_to :users, :extend => MyGem::SpecialTouch
end
module MyGem
module SpecialTouch
def touch
# do the magic
end
end
end
Then you could of course override or alias chain belongs_to so that it pops your :foo option from options hash, converts it to proper :extend => ... and (really or effectively) calls belongs_to.
valid_options is an instance method so you need to use include instead of extend.
module MyGem
def valid_options
super + [:foo]
end
end
class ActiveRecord::Associations::Builder::BelongsTo
include ::MyGem
end
relation = ActiveRecord::Associations::Builder::BelongsTo.new(:a, :b, :c)
relation.valid_options.include? :foo
#=> true
Hope that helps.