Where to put custom Array methods or Enumerable methods in Rails - ruby-on-rails

I have custom array methods like
class Array
def decreasing?
for i in (0...self.size)
return false if self[i] > self[i+1]
end
true
end
def increasing?
for i in (0...self.size)
return false if self[i] < self[i+1]
end
true
end
end
And
module Enumerable
def sorted?
each_cons(2).all? { |a, b| (a <=> b) <= 0 }
end
end
Currently I have them in a model file randomly. Where is a better place to put these codes in Rails?

I would put it in an initializer (in config/initializers) called array_extensions.rb and enumerable_extensions.rb.

I think it can be under /lib directory.

Related

Is it possible to call super to invoke alias method in a method that is being aliased

Let's say in the activerecord model car there are two boolean fields: suv and blue
If there is a method in the car model defined as such
def suv
something_true? ? super : false
end
alias :blue :suv
Now if something_true? is true, the "super" works if i invoke car.suv. However, it does not work if i invoke car.blue, then the car.blue instead returns the value of suv stored in the database. Is there anyway to make this work?
It's a clever idea but I don't think it will work. Even if it's accessed through an alias, calling super inside the method suv will only call suv. You can use metaprogramming though:
class A
def a; 1; end
def b; 2; end
end
class B < A
def initialize(condition)
#condition = condition
end
%i{a b}.each do |fn|
define_method(fn) do
#condition ? super() : "default"
end
end
end
puts B.new(false).a # => "default"
puts B.new(false).b # => "default"
puts B.new(true).a # => 1
puts B.new(true).b # => 2

Neat way to get and set keys of json column in Rails

I have a model/table with a json column in it as follows
t.json :options, default: {}
The column can contain many keys within it, something like this
options = {"details" : {key1: "Value1", key2: "Value2"}}
I want to set and get these values easily. So i have made getters and setters for the same.
def key1
options['details']&.[]('key1')
end
def key1=(value)
options['details'] ||= {}
options['details']['key1'] ||=0
options['details']['key1'] += value
end
But this just adds lines to my code, and it does not scale when more details are added. Can you please suggest a clean and neat way of doing this?
Use dynamic method creation:
options['details'].default_proc = ->(_,_) {{}}
ALLOWED_KEYS = %i[key1 key2 key3]
ALLOWED_KEYS.each do |key|
define_method key do
options['details'][key] if options['details'].key?(key)
end
define_method "#{key}=" do |value|
(options['details'][key] ||= 0) += value
end
end
You can just pass the key as a parameter as well right?
def get_key key=:key1
options['details']&.[](key)
end
def set_key= value, key=:key1
options['details'] ||= {}
options['details'][key] ||=0
options['details'][key] += value
end
Simple & Short
Depending on re-usability you can choose different options. The short option is to simply define the methods using a loop in combination with #define_method.
class SomeModel < ApplicationRecord
option_accessors = ['key1', 'key2']
option_accessors.map(&:to_s).each do |accessor_name|
# ^ in case you provide symbols in option_accessors
# this can be left out if know this is not the case
define_method accessor_name do
options.dig('details', accessor_name)
end
define_method "#{accessor_name}=" do |value|
details = options['details'] ||= {}
details[accessor_name] ||= 0
details[accessor_name] += value
end
end
end
Writing a Module
Alternatively you could write a module that provide the above as helpers. A simple module could look something like this:
# app/model_helpers/option_details_attribute_accessors.rb
module OptionDetailsAttributeAccessors
def option_details_attr_reader(*accessors)
accessors.map(&:to_s).each do |accessor|
define_method accessor do
options.dig('details', accessor)
end
end
end
def option_details_attr_writer(*accessors)
accessors.map(&:to_s).each do |accessor|
define_method "#{accessor}=" do |value|
details = options['details'] ||= {}
details[accessor] ||= 0
details[accessor] += value
end
end
end
def option_details_attr_accessor(*accessors)
option_details_attr_reader(*accessors)
option_details_attr_writer(*accessors)
end
end
Now you can simply extend your class with these helpers and easily add readers/writers.
class SomeModel < ApplicationRecord
extend OptionDetailsAttributeAccessors
option_details_attr_accessor :key1, :key2
end
If anything is unclear simply ask away in the comments.

How can I cleanly define "antonym" or "opposite" methods in Ruby / Rails?

I'm pretty often defining methods and their antonyms in the code I'm writing, as in:
def happy?
#happiness > 3
end
def sad?
!happy?
end
Which is fine, but I'm a little surprised that Ruby or ActiveSupport doesn't give me something like:
def happy?
#happiness > 3
end
alias_opposite :sad? :happy?
Or am I just looking in the wrong place?
There is no such method in popular libraries, but there is how this could be implemented
class Module
def alias_opposite(a, b)
define_method(a) { !self.send(b) }
end
end
Usage
class A < Struct.new(:happiness)
def happy?
happiness > 3
end
alias_opposite :sad?, :happy?
end
p A.new(1).sad? # => true
p A.new(5).sad? # => false
I suspect this pattern is not as common in ruby because the unless keyword often does the trick:
# ...
clap_your_hands if happy?
stomp_your_feet unless happy?
# ...
Of course, its simple to roll your own:
module Antonymator
def define_antonym(as, of)
define_method(as.to_sym) do |*args|
return !(send(of.to_sym, *args))
end
end
end
# Usage Example
class AreThey
extend Antonymator
define_antonym :uneql?, :eql?
define_antonym :nonconsecutive?, :consecutive?
def eql?(a, b)
a == b
end
def consecutive?(a, b)
a.next == b
end
end
are_they = AreThey.new
puts are_they.uneql? 1, 2 # true
puts are_they.nonconsecutive? 1, 2 # false
If your methods return a Boolean, you can always include the positive method in the negative method.
def drinking_age?(age)
age > #restricted_age
end
def not_drinking_age?(age)
!drinking_age?(age)
end
#restricted_age = 20
Hope that helps.
I guess it depends on what 'opposite' means in the context.

I know how to set class methods, but how do I set instance methods on the fly?

I asked a previous question on class methods, but I really want to understand how to do this for instance methods as well. Thanks! =)
The code below sets class methods for a given array:
class Testing
V4_RELATIONSHIP_TYPES=[1=>2,3=>4]
V4_RELATIONSHIP_TYPES.keys.each do |key|
self.class.send(:define_method, "get_#{key}_type".downcase) do
return GuidInfo.get_or_new(PARAMS, V4_RELATIONSHIP_TYPES[key])
end
end
end
#so i can call Testing.get_1_key()
The question is: how can I get the same set of methods for the instance?
self.send(:method, value)
class Testing
V4_RELATIONSHIP_TYPES = { 1 => 2, 3 => 4 }
V4_RELATIONSHIP_TYPES.each do |key, value|
define_method("get_#{key}_type".downcase) do
return GuidInfo.get_or_new(PARAMS, value)
end
end
end
# Now you can call Testing.new.get_1_key

Sort collection of object in rails?

I know I can use something like User.sort {|a, b| a.attribute <=> b.attribute} or User.find and order, but is it a way like comparable interface in Java, so every time I called sort on User object it will do sort on the predefined attributes.
Thanks,
You can do that by defining the <=> method for your objects. That way, you should be able to just say: collection.sort for non-destructive sort, or collection.sort! for in-place sorting:
So, example here:
class A
def <=>(other)
# put sorting logic here
end
end
And a more complete one:
class A
attr_accessor :val
def initialize
#val = 0
end
def <=>(other)
return #val <=> other.val
end
end
a = A.new
b = A.new
a.val = 5
b.val = 1
ar = [a,b]
ar.sort!
ar.each do |x|
puts x.val
end
This will output 1 and 5.

Resources