Breakdown of this Ruby code? - ruby-on-rails

Would anyone be kind enough to dissect the merge! method? Its usage of conditions and variable assignment looks rather terse, and I'm having a difficult time following it. Would love to hear a Ruby-savvy developer break this apart.
module ActiveRecord
class Errors
def merge!(errors, options={})
fields_to_merge = if only=options[:only]
only
elsif except=options[:except]
except = [except] unless except.is_a?(Array)
except.map!(&:to_sym)
errors.entries.map(&:first).select do |field|
!except.include?(field.to_sym)
end
else
errors.entries.map(&:first)
end
fields_to_merge = [fields_to_merge] unless fields_to_merge.is_a?(Array)
fields_to_merge.map!(&:to_sym)
errors.entries.each do |field, msg|
add field, msg if fields_to_merge.include?(field.to_sym)
end
end
end
end

module ActiveRecord
class Errors
def merge!(errors, options={})
# This method has two major sections.
# 1. Figure out which fields we want to merge, based on the options.
# First check to see if we're supposed to whitelist or blacklist
# any fields.
# fields_to_merge is simple being assigned the return value of
# the if/elsif/else block.
#
fields_to_merge = if only=options[:only]
# Whitelist
# Implies that options[:only] looks like :only => :foo, but that
# seems short-sighted and should probably be refactored to work
# like the blacklist, complete with to_sym conversions.
only
elsif except=options[:except]
# Blacklist
# You can pass in a single field or an array.
# :except => [:foo, :bar, :yourmom] or :except => :foo
# In the latter case, this is about to be converted to [:foo] so that
# it's an array either way.
except = [except] unless except.is_a?(Array)
# For each array element, convert that element to a symbol.
# "foo" becomes :foo
# This is a short hand for "map" which is equivalent to the following:
# except.map!{|field| field.to_sym}
except.map!(&:to_sym)
# The "entries" method comes from Enumerable and works like to_a.
# In the case of a hash, you end up with an array of arrays.
# Assuming there are already errors, the output of that will look
# something like:
# [['user', 'can't be blank'], ['some_other_field', 'has problems']]
#
# Mapping this to :first means "send the 'first' message to each array
# element. Since the elements are themselves arrays, you end up
# with ['user', 'some_other_field']
#
# Now we do a 'select' on that array, looking for any field names that
# aren't in our 'except' array.
errors.entries.map(&:first).select do |field|
!except.include?(field.to_sym)
end
else
# No filters. Just dump out an array of all field names.
errors.entries.map(&:first)
end
# Make sure that we have an array, which at this point is only possible
# if we're using an :only => :foo.
fields_to_merge = [fields_to_merge] unless fields_to_merge.is_a?(Array)
# Make sure that they are all symbols (again with the :only, see the
# note above about refactoring).
fields_to_merge.map!(&:to_sym)
# 2. Now we know what fields we care about, yank in any errors with a
# matching field name from the errors object.
errors.entries.each do |field, msg|
# 'add' is a method on ActiveRecord::Errors that adds a new error
# message for the given attribute (field).
# In this case, any error from the errors object passed into this
# method with a field name that appears in the 'fields_to_merge'
# array is added to the existing errors object.
#
add field, msg if fields_to_merge.include?(field.to_sym)
end
end
end
end

Related

Ruby\Rails sandboxing

I have a case, when the text from the DB field should be "evaled" in the sandbox mode - with whitelist of methods and constants, allowed to invoke.
Gem https://github.com/tario/shikashi fits to this perfectly, but it seems to me, that it's abandoned.
I even can't use run Basic Example 2 (only Basic Example 1 works fine):
require "rubygems"
require "shikashi"
include Shikashi
def foo
# privileged code, can do any operation
print "foo\n"
end
s = Sandbox.new
priv = Privileges.new
# allow execution of foo in this object
priv.object(self).allow :foo
# allow execution of method :times on instances of Fixnum
priv.instances_of(Fixnum).allow :times
#inside the sandbox, only can use method foo on main and method times on instances of Fixnum
s.run(priv, "2.times do foo end")
Because it fails with an Error Cannot invoke method foo on object of class Object (SecurityError)
This gem uses another gem evalhook that looks for me complicated in order to fix the issue. There are another gems like this one but they are even more abandoned.
As far as I understood using $SAFE is not a good idea, because it has vulnerabilities.
Are there another approaches for such feature? Maybe manipulating with Binding object?
My problem was not so hard to solve without any gems. The idea is that you have whitelist of methods and constants WHITELIST and this class checks, if all methods and constants are in the whitelist
# gem install 'parser'
require 'parser/current'
class CodeValidator
attr_reader :errors, :source
WHITELIST = {:send => [:puts, :+, :new], :const => [:String]}
class Parser::AST::Node
def value
return children[1] if [:send, :const].include? type
fail NotImplementedError
end
end
def initialize(source)
#errors = []
#source = source
end
def valid?
!insecure_node?(root_node)
end
private
def exclude_node?(node)
blacklisted_node_types.include?(node.type) && !WHITELIST[node.type].include?(node.value)
end
def blacklisted_node_types
WHITELIST.keys
end
def insecure_node?(node)
return !!add_error_for_node(node) if exclude_node?(node)
node.children.each { |child_node| return true if child_node.class == Parser::AST::Node && insecure_node?(child_node) }
false
end
def root_node
#root_node ||= Parser::CurrentRuby.parse source
end
def add_error_for_node(node)
errors << "#{node.type} not allowed: #{node.value}"
end
end
c = CodeValidator.new("s = 'hello ' + String.new('world'); puts s.inspect")
p c.valid? # => false
p c.errors # => ["send not allowed: inspect"]

Access object attribute inside module

I have a class with the attribute .weekday saved as an integer value.
I'm trying to create a method in a module that converts that numeric value to the corresponding weekday.
This is how I like to work:
MyClass.weekday
=> 2
MyClass.weekday.my_module_method
=> "Tuesday"
Is it possible to do this conversion with a module method or am I thinking wrong here?
I can access the object from within the module mehtod, by self, but I don't seem to be able to do self.weekday.
What you are trying to do is certainly possible. You are correct when you point to ActiveRecord::Inflector as something that works similarly. This approach modifies the Fixnum class itself to add new methods and although I don't generally recommend ad-hoc patching of core classes, you can see it in action in active_support/core_ext/integer/inflections.rb :
require 'active_support/inflector'
class Integer
# Ordinalize turns a number into an ordinal string used to denote the
# position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
#
# 1.ordinalize # => "1st"
# 2.ordinalize # => "2nd"
# 1002.ordinalize # => "1002nd"
# 1003.ordinalize # => "1003rd"
# -11.ordinalize # => "-11th"
# -1001.ordinalize # => "-1001st"
#
def ordinalize
ActiveSupport::Inflector.ordinalize(self)
end
end
In your case I might do something like:
module WeekdayInflector
def weekday
Date::DAYNAMES[self]
end
end
class Fixnum
include WeekdayInflector
end
which will at least help others track down the methods you added by looking at the module. Please note that this will affect ALL instances of Fixnum and could lead to conflicts if you include a Gem that tries to do the same thing. It is worth asking whether this tradeoff is worth it or if defining a simple view helper is the better way to go.

What exactly does this Rails module do, and how does it work?

I am fairly new to Ruby and I fail to see the connection between all those classes and methods. Could you please explain a little what exactly each method does:
module Naming
# Returns an ActiveModel::Name object for module. It can be
# used to retrieve all kinds of naming-related information.
def model_name
#_model_name ||= begin
namespace = self.parents.detect do |n|
n.respond_to?(:use_relative_model_naming?) && n.use_relativve_model_naming?
end
ActiveModel::Name.new(self, namespace)
end
end
# Returns the plural class name of a record or class. Examples:
#
# ActiveModel::Naming.plural(post) # => "posts"
# ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
def self.plural(record_or_class)
model_name_from_record_or_class(record_or_class).plural
end
# Returns the singular class name of a record or class. Examples:
#
# ActiveModel::Naming.singular(post) # => "post"
# ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
def self.singular(record_or_class)
model_name_from_record_or_class(record_or_class).singular
end
# Identifies whether the class name of a record or class is uncountable. Examples:
#
# ActiveModel::Naming.uncountable?(Sheep) # => true
# ActiveModel::Naming.uncountable?(Post) => false
def self.uncountable?(record_or_class)
plural(record_or_class) == singular(record_or_class)
end
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
# For isolated engine:
# ActiveModel::Naming.route_key(Blog::Post) #=> post
#
# For shared engine:
# ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
def self.singular_route_key(record_or_class)
model_name_from_record_or_class(record_or_class).singular_route_key
end
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
# For isolated engine:
# ActiveModel::Naming.route_key(Blog::Post) #=> posts
#
# For shared engine:
# ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
#
# The route key also considers if the noun is uncountable and, in
# such cases, automatically appends _index.
def self.route_key(record_or_class)
model_name_from_record_or_class(record_or_class).route_key
end
# Returns string to use for params names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
# For isolated engine:
# ActiveModel::Naming.param_key(Blog::Post) #=> post
#
# For shared engine:
# ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
def self.param_key(record_or_class)
model_name_from_record_or_class(record_or_class).param_key
end
private
def self.model_name_from_record_or_class(record_or_class)
(record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
end
def self.convert_to_model(object)
object.respond_to?(:to_model) ? object.to_model : object
end
end
end
I know there are comments for each method but I still fail to understand basic meta.
this module is part of ActiveModel, and it helps ensuring naming conventions.
The goal of all this is to provide a standard interface that helps deducing from a single object :
where a file belongs in the app structure (this is the magic behind render in the controller, that deduces from your controller name where the view should be, for instance)
what standard REST routes should lead to this resource
what key in the params hash will generate a form_for helper for a specific object
and so on...
it is difficult to explain any more this module, as it is ubiquitously used by independant logic bits that rely on naming conventions.

What are the consequences of overriding Array.to_param within an ActiveResource based plugin

An active_resource based class:
Contact.search(:email => ['bar#foo.com','foo#bar.com'])
would produce this:
?email[]=bar#foo.com&email[]=foo#bar.com
The specific API that I'm working with requires this:
?email=bar#foo.com&email=foo#bar.com
So playing around I have found that:
ActiveResouce calls:
# Find every resource
find_every(options)
which calls:
# Builds the query string for the request.
def query_string(options)
"?#{options.to_query}" unless options.nil? || options.empty?
end
So if I update:
class Array
# Converts an array into a string suitable for use as a URL query string,
# using the given +key+ as the param name.
#
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
def to_query(key)
prefix = "#{key}[]"
collect { |value| value.to_query(prefix) }.join '&'
end
end
to this:
class Array
# Converts an array into a string suitable for use as a URL query string,
# using the given +key+ as the param name.
#
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
def to_query(key)
prefix = "#{key}"
collect { |value| value.to_query(prefix) }.join '&'
end
end
it works!! however I'm not particularly happy redefining Array.to_param because this may have unforeseen issues, especially as this plug in needs to work within rails.
Is there another way I can patch only my version?
I would definitely recommend NOT monkey patching an array method like that. If you only have a single model, could you override the search method?
class Contact
def self.search(options={})
super(options).gsub('[]','')
end
end
As this behaviour is standard through the API I'm using I was able to add this patch to my ActiveRecord::Base class.
def query_string(options)
begin
super(options).gsub('%5B%5D','')
rescue
end
end
Thanks to Beerlington for pointing me in the right direction for this.

RAILS OVERRIDING VIEWS renderization

I want to override how rails creates a view *.html.erb
In ActionView package I already Tried to do it. Doing it
class ERB
class Compiler # :nodoc:
..
class Buffer # :nodoc:
def compile(s)
...
#It stores in a buffer each ruby chunk in the views inside of a Buffer.
end
end
end
...
# Here it is where is called compile method.
# The thing is that if my view is made up of several *.html.erb files such as partials this method will be invoked each time.
#INVOKED PER EACH html.erb file
def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout')
puts ">>> initialize"
#safe_level = safe_level
# ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(:default => "%d %b %Y")
compiler = ERB::Compiler.new(trim_mode)
# raise "need a block"
# ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(:default => nil)
set_eoutvar(compiler, eoutvar)
#src = compiler.compile(str)
#filename = nil
end
end
end
I would like to find out when the process on the bottom starts. I mean which class, file, etc I have to override to see where Rails starts invoking all html.erb for an specific view.
I think that I should be here:
require 'delegate'
require 'optparse'
require 'fileutils'
require 'tempfile'
require 'erb'
module Rails
module Generator
module Commands
class Create
# Generate a file for a Rails application using an ERuby template.
# Looks up and evaluates a template by name and writes the result.
#
# The ERB template uses explicit trim mode to best control the
# proliferation of whitespace in generated code. <%- trims leading
# whitespace; -%> trims trailing whitespace including one newline.
#
# A hash of template options may be passed as the last argument.
# The options accepted by the file are accepted as well as :assigns,
# a hash of variable bindings. Example:
# template 'foo', 'bar', :assigns => { :action => 'view' }
#
# Template is implemented in terms of file. It calls file with a
# block which takes a file handle and returns its rendered contents.
def template(relative_source, relative_destination, template_options = {})
puts "EEEEEEEEEEEEEEEEEEEEEe"
file(relative_source, relative_destination, template_options) do |file|
# Evaluate any assignments in a temporary, throwaway binding.
vars = template_options[:assigns] || {}
b = template_options[:binding] || binding
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
# Render the source file with the temporary binding.
ERB.new(file.read, nil, '-').result(b)
end
end
end
end
end
end
But I do not find any trace from the puts method.
All renaming are placed in a file called /lib/*_extensions.erb and in /config/initializers/extensions.rb I have the next:
Dir[File.dirname(__FILE__) + "/../../lib/*_extensions.rb"].each do |fn|
require fn
end
I do not want to reveal why I am doing this.
Thanks
I wanted to use a before_render it does not exist at least in Rails 2.3.4.
So if you want to do it, I did as the next:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
# Scrub sensitive parameters from your log
# filter_parameter_logging :password
protected
def render(options = nil, extra_options = {}, &block) #:doc:
puts "BEFORE render"
puts "Setting %H:%M:%S"
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:default => "%H:%M:%S")
# call the ActionController::Base render to show the page
super
puts "AFTER render"
puts "Resetting"
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:default => nil)
end
end

Resources