How to define a class like ActiveSupport::StringInquirer - ruby-on-rails

Define a class which when initialized with a string e.g. 'abc' will return true if the method 'abc?' is called on it. Any other method with a trailing '?' will return false. All other methods which doesn't have a trailing '?' will raise NoMethodError

You can use method_missing to respond to messages for which there is no method.
In method_missing we can check the method name and if it ends in a ? check if it minus the ? is equal to the string (self).
When using method_missing it is custom to also define respond_to?.
class StringInquirer < String
private
def method_missing(method_name, *args, &block)
if method_name.to_s.end_with?('?')
self == method_name.to_s.delete('?')
else
super
end
end
def respond_to?(method_name, include_private = false)
method_name.to_s.ends_with('?') || super
end
end
name = StringInquirer.new('sally')
name.sally? # => true
Note this is case sensitive.
name.Sally? # => false

class NewStrInq < String
def initialize(val)
self.class.send(:define_method, "#{val}?") do
true
end
end
def method_missing(method)
method.to_s[-1] == '?' ? false : (raise NoMethodError)
end
end

class StringInquirer < String
def initialize(str)
define_singleton_method(str + '?') { true }
super(str)
end
end
name = StringInquirer.new('sally')
name.sally? # => true
name.kim? # => NoMethodError
name.nil? # => false
By raising NoMethodError for all methods ending in a question mark you will loose nil? etc.

Related

extending an apipie-rails validator

I'd like to extend NumberValidator to also validate for min and max values. I'd like to still call the default NumberValidator and have it return its own description (Must be a number) should it fail. Right now, I'm always getting my own implementation's description ('Must be a number between...`)
This is what I have thus far:
class NumberValidator < Apipie::Validator::BaseValidator
def initialize(param_description, argument, options)
super(param_description)
#number_validator = Apipie::Validator::NumberValidator.new(param_description)
#type = argument
#options = options
end
def validate(value)
return false unless #number_validator.valid?(value)
if (#options[:min]) && (#options[:max])
self.class.validate(value) && value.to_i.between?(#options[:min], #options[:max])
end
true
end
def self.build(param_description, argument, options, block)
if argument == :number
self.new(param_description, argument, options)
end
end
def description
"Must be a number between #{#options[:min]} and #{#options[:max]}."
end
end

Extending ActiveModel::Serializer with custom attributes method

I am trying to create my own attributes method called secure_attributes where I pass it an array of attributes and the minimum level the authorized user needs to be to view those attributes. I pass the current level of the authorized user as an instance_option. I'd like to extend the Serializer class so I can use this method in multiple serializers, but Im having issues.
This is what i have so far:
in config/initializers/secure_attributes.rb
module ActiveModel
class Serializer
def self.secure_attributes(attributes={}, minimum_level)
attributes.delete_if {|attr| attr == :attribute_name } unless has_access?(minimum_level)
attributes.each_with_object({}) do |name, hash|
unless self.class._fragmented
hash[name] = send(name)
else
hash[name] = self.class._fragmented.public_send(name)
end
end
end
end
end
and then in the individual serializer I have things like this:
secure_attributes([:id, :name, :password_hint], :guest)
and then
def has_access?(minimum_level=nil)
return false unless minimum_level
return true # based on a bunch of logic...
end
But obviously secure_attributes cannot see the has_access? method and if I put has_access inside the Serializer class, it cannot access the instance_options.
Any idea how I can accomplish what I need?
Maybe you want to do following - but I still do not get your real purpose, since you never did anything with the attributes but calling them:
module ActiveRecord
class JoshsSerializer < Serializer
class << self
def secure_attributes(attributes={}, minimum_level)
#secure_attributes = attributes
#minimum_level = minimum_level
end
attr_reader :minimum_level, :secure_attributes
end
def initialize(attr, options)
super attr, options
secure_attributes = self.class.secure_attributes.dup
secure_attributes.delete :attribute_name unless has_access?(self.class.minimum_level)
secure_attributes.each_with_object({}) do |name, hash|
if self.class._fragmented
hash[name] = self.class._fragmented.public_send(name)
else
hash[name] = send(name)
end
end
def has_access?(minimum_level=nil)
return false unless minimum_level
return true # based on a bunch of logic...
end
end
end

Is a ':methods' option in 'to_json' substitutable with an ':only' option?

The to_json option has options :only and :methods. The former is intended to accept attributes and the latter methods.
I have a model that has an attribute foo, which is overwritten:
class SomeModel < ActiveRecord::Base
...
def foo
# Overrides the original attribute `foo`
"the overwritten foo value"
end
end
The overwritten foo method seems to be called irrespective of which option I write the foo under.
SomeModel.first.to_json(only: [:foo])
# => "{..., \"foo\":\"the overwritten foo value\", ...}"
SomeModel.first.to_json(methods: [:foo])
# => "{..., \"foo\":\"the overwritten foo value\", ...}"
This seems to suggest it does not matter whether I use :only or :methods.
Is this the case? I feel something wrong with my thinking.
The source code leads to these:
File activemodel/lib/active_model/serialization.rb, line 124
def serializable_hash(options = nil)
options ||= {}
attribute_names = attributes.keys
if only = options[:only]
attribute_names &= Array(only).map(&:to_s)
elsif except = options[:except]
attribute_names -= Array(except).map(&:to_s)
end
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
serializable_add_includes(options) do |association, records, opts|
hash[association.to_s] = if records.respond_to?(:to_ary)
records.to_ary.map { |a| a.serializable_hash(opts) }
else
records.serializable_hash(opts)
end
end
hash
end
File activeresource/lib/active_resource/base.rb, line 1394
def read_attribute_for_serialization(n)
attributes[n]
end
and it seems that an :only option calls attributes[n] and :methods option calls send(m). What is the difference?

Return object if no params passed in the method

I have a class method inside my model that I use as a scope
class Foo < ApplicationRecord
def self.bar(params)
if !params['some-param'].blank?
return Foo.where(....)
elsif !params['another-param'].blank?
return Foo.where(....)
end
self
end
end
# so in the controller I do something like this
my_var = Foo.bar(params).another_scope.all
which works fine. If I change the code to this though
def self.bar(params)
return self if params.empty?
if !params['some-param'].blank?
return Foo.where(....)
end
if !params['another-param'].blank?
return Foo.where(....)
end
end
this throws an error: undefined method 'another_scope' for nil:NilClass.
Lets assume that params are not empty. Both of params['some-param'] and params['another-param'] are empty. And your method wouldn't rentrun anything.
It's better:
def self.bar(params)
if params['some-param'].present?
Foo.where(....)
elsif params['another-param'].present?
Foo.where(....)
end
self
end
Try this:
def self.bar(params = {}) # with a default value
params.delete_if { |_, v| v.blank? } # Clean up keys with blank values
if params['some-param']
Foo.where(....)
elsif params['another-param']
Foo.where(....)
else # if some unknown key present in params OR the params are blank
self
end
end

Ruby - uninitialized constant

I have this 4 classes, the code is simple but when I try to generate, it returns an error like
in `<main>': uninitialized constant PruebasUnitarias (NameError)
The code is this and I dont know what is wrong, I thought it was because of requires, but I have all in one document and still crash.
class Complejo
def initialize (real, imaginario)
#real = real
#imaginario = imaginario
end
def sumar (complejo)
#real = #real + complejo.real
#imaginario = #imaginario + complejo.imaginario
end
attr_reader :real, :imaginario
end
class Prueba
def assertCierto(valor)
return valor
end
def assertFalso(valor)
return valor
end
def assertIgual(num1, num2)
if(num1 == num2)
return true
end
return false
end
def assertDistinto(num1, num2)
if(num1 != num2)
return true
end
false
end
def assertNil param
if (param == nil)
return true
end
false
end
def assertContiene(param1, param2)
param1.include?(param2)
end
end
class PruebasUnitarias::Prueba
def run
metodos = self.methods
if(self.respond_to? :inicializacion)
self.inicializacion
end
end
end
class PruebaComplejo < PruebasUnitarias::Prueba
def inicializacion
#c1 = Complejo.new(3,5)
#c2 = Complejo.new(1,-1)
end
def prueba_suma
#c1.sumar(#c2)
assertIgual(#c1.real, 4)
assertIgual(#c1.imaginario, 4)
end
def prueba_suma_cero
#c2.sumar(Complejo.new(0,0))
assertCierto(#c2.real==1)
assertCierto(#c2.imaginario==-1)
end
def prueba_suma_nula
#c2.sumar(nil)
assertIgual(#c2.real, 1)
assertIgual(#c2.imaginario, -1)
end
def imprimir (complejo)
puts "complejo: #{complejo.real}, #{complejo.imaginario}i"
end
end
You have to declare the module before you can put a class in it. Try this instead:
module PruebasUnitarias
class Prueba
...
end
end
class PruebaComplejo < PruebasUnitarias::Prueba
...
end
Just as a note about your programming style, you need to think in simpler terms when it comes to testing your values. Consider these changes:
def assertCierto(valor)
valor
end
def assertFalso(valor)
valor
end
def assertIgual(num1, num2)
num1 == num2
end
def assertDistinto(num1, num2)
num1 != num2
end
def assertNil param
param == nil
end
It's not necessary to use an if test to see if something is equal or not equal and then return true or false. The test itself does that so simply compare the values and let Ruby return the result by default.
1 == 1 # => true
1 == 2 # => false
1 != 1 # => false
1 != 2 # => true
Also, use snake_case for method names, not camelCase. ItsAReadabilityThing:
def assert_cierto(valor)
def assert_falso(valor)
def assert_igual(num1, num2)
def assert_distinto(num1, num2)
def assert_nil param

Resources