I would like to get RuboCop to enforce a style like this:
# bad
alias_method :pharmacy, :recipient
alias_method :pharmacy=, :recipient=
before_save -> do
self.label = "Prescription" if label.blank?
end
before_save -> do
self.recipient ||= appointment.patient.pharmacy
end
# good
alias_method :pharmacy, :recipient
alias_method :pharmacy=, :recipient=
before_save -> do
self.label = "Prescription" if label.blank?
end
before_save -> do
self.recipient ||= appointment.patient.pharmacy
end
Is this possible?
Yes, it is possible.
There is a way to extend the Rubocop rule set by using extensions
https://docs.rubocop.org/rubocop/extensions.html.
Related
I am creating an object that acts like an array, but needs to do some data initialization before items can be added to the array.
class People
def initialize(data_array)
#people_array = data_array.map {|data| Person.new(data) }
end
def <<(data)
#people_array << Person.new(data)
end
# def map(&block) ...
# def each(&block) ...
# etc...
end
I would like to support all the same methods that the array supports and delegate to those methods so that I don't have to rewrite all of them. What is the simplest/cleanest way to achieve this?
Maybe inheriting the Array class is a good solution:
class People < Array
def initialize(data_array)
super(data_array.map {|data| Person.new(data) })
end
def <<(item)
super(Person.new(item))
end
end
However, it has some disadvantages as mentioned in the comments and in axel's answer.
An alternative solution is composition and mixins:
class People
include Enumerable
def initialize(data_array)
#collection = data_array.map {|data| Person.new(data) }
end
def each(&block)
#collection.each(&block)
end
def <<(data)
#collection << Person.new(data)
end
end
However, this solution only gives you methods provided by the Enumerable module, along with the << operator.
To see what methods the Array itself defines, easily do:
(Array.instance_methods - (Enumerable.instance_methods + Object.instance_methods)).sort
=> [:&, :*, :+, :-, :<<, :[], :[]=, :assoc, :at, :bsearch, :clear, :collect!,
:combination, :compact, :compact!, :concat, :delete, :delete_at, :delete_if, :each,
:each_index, :empty?, :fetch, :fill, :flatten, :flatten!, :index, :insert, :join,
:keep_if, :last, :length, :map!, :pack, :permutation, :pop, :product, :push, :rassoc,
:reject!, :repeated_combination, :repeated_permutation, :replace, :reverse, :reverse!,
:rindex, :rotate, :rotate!, :sample, :select!, :shift, :shuffle, :shuffle!, :size, :slice,
:slice!, :sort!, :sort_by!, :to_ary, :transpose, :uniq, :uniq!, :unshift, :values_at, :|]
However I would consider this a dirty solution, and would encourage going with Axel's answer.
You can use a Delegator for that
class SimpleDelegator < Delegator
def initialize(obj)
super # pass obj to Delegator constructor, required
#delegate_sd_obj = obj # store obj for future use
end
end
I would discourage from extending Array.
Of course, you can always meta-hack it
class People
def model
#arr ||= []
end
def method_missing(method_sym, *arguments, &block)
define_method method_sym do |*arguments|
model.send method_sym, *arguments
end
send(method_sym, *arguments, &block)
end
#probably should implement respond_to? here
end
I like this approach a little better because you aren't confusing implementing against Array versus extending Array. You can build a custom matcher to only implement particular array methods for example that are available for your People class.
module Framework
class CreateTableDefinition
attr_accessor :host, :username, :password
end
end
def create_table(table_name)
obj = Framework::CreateTableDefinition.new
yield(obj) if block_given?
end
create_table :users do |config|
config.host :localhost
end
And here is the error I get
-:13:in `block in <main>': wrong number of arguments (1 for 0) (ArgumentError)
from -:9:in `create_table'
from -:12:in `<main>'
If I change the code to
config.host = :localhost
it works fine. But what I want is to work as described above config.host :localhost
You missed assignment:
config.host = :localhost
Edit
If you want to get rid of assignments, you need to define setter methods without = at the end. This might generate quote a lot of code, so I would rather go with some meta-programming (because it's fun!)
class MyConfigClass
def self.attributes(*args)
args.each do |attr|
define_method attr do |value|
#attributes[attr] = value
end
end
end
def initialize
#attributes = {}
end
def get(attr)
#attributes[attr]
end
end
class CreateTableDefinition < MyConfigClass
attributes :host, :username, :password
end
c = CreateTableDefinition.new
c.host :localhost
c.get(:host) #=> :localhost
Try manually making a method that does what you want instead of using the attr_accessor shortcut.
class CreateTableDefinition
attr_accessor :username, :password
def host(sym)
#host = sym
end
def get_host
#host
end
end
If you don't like the idea of writing those methods for every attribute, look into writing your own helper, something like attr_rails_like, and mix it in to the Class object. This article might be helpful.
I often write stuff that looks like this:
def AModel < ActiveRecord::Base
belongs_to :user
def SomeCodeThatDoesSomeCalculations
# some code here
end
def SomeCodeThatDoesSomeCalculations!
self.SomeCodeThatDoesSomeCalculations
self.save
end
end
Is there a better way to generate the functions with the suffix "!" ?
If you're doing it really often you can do smth like that:
class Model < ActiveRecord::Base
def self.define_with_save(method_name)
define_method "#{method_name}!" do
send method_name
save
end
end
def save # stub method for test purpose
puts 'saving...'
end
def do_stuff
puts 'doing stuff...'
end
define_with_save :do_stuff
end
m = Model.new
m.do_stuff
# => 'doing stuff...'
m.do_stuff!
# => 'doing stuff...'
# => 'saving...'
If you want that in multiple models may be you'd like to create your own base class for them containing this define_with_save class method, or you can add it to ActiveRecord::Base itself if you are sure you need it.
Btw, I hope you're not really naming you your methods in SomeCodeThatDoesSomeCalculations notation as they are usually named like some_code_that_does_some_calculations.
In Ruby, is there a way to do something like
class Foo
attr_reader :var_name :reader_name #This won't work, of course
def initialize
#var_name = 0
end
end
# stuff here ....
def my_method
foo = Foo.new
assert_equal 0,foo.reader_name
end
In other words, is there a standard way to make an accessor method for a variable that uses a different name than the variable. (Besides hand-coding it, of course.)
You could use alias_method:
class Foo
attr_reader :var_name
alias_method :reader_name, :var_name
def initialize
#var_name = 0
end
end
The var_name method built by attr_reader would still be available though. You could use remove_method to get rid of the var_name method if you really wanted to (but make sure you get everything in the right order):
class Foo
attr_reader :var_name
alias_method :reader_name, :var_name
remove_method :var_name
def initialize
#var_name = 0
end
end
If you really wanted to, you could monkey patch an attr_reader_as method into Module:
class Module
def attr_reader_as(attr_name, alias_name)
attr_reader attr_name
alias_method alias_name, attr_name
remove_method attr_name
end
end
class Foo
attr_reader_as :var_name, :reader_name
def initialize
#var_name = 0
end
end
A better attr_reader_as would take a hash (e.g. { var_name: :reader_name, var_name: :reader_name2}) but I'll leave that as an exercise for the reader.
You can't pass any options to attr_reader
Is this a handcoding?
class Foo
def initialize
#var_name = 0
end
def reader_name; #var_name; end
end
First off, my mantra: A good programmer plays his teammates good. Following conventions is playing others good.
What you want seems unconventional, and as #edgerunner states, bad practice (read: I would've smacked you). All I'm saying, think twice about this, I don't know your use case, it might prove valid... Anyway, a little fun during easter should be allowed, so I practiced some metaprogramming and played with Mixins.
class Foo
include MyAttrReader
my_attr_reader :var_name, :reader_name
def initialize
#var_name = 0
end
end
pretty clean. The module does the trick (be sure to load this module before the class)
module MyAttrReader
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def my_attr_reader(attribute, read_attribute)
define_method "#{read_attribute}" do
instance_variable_get "##{attribute}"
end
end
end
end
Testing in irb
ruby-1.9.2-p180 :001 > load 'my_attr_reader.rb'
=> true
ruby-1.9.2-p180 :002 > load 'foo.rb'
=> true
ruby-1.9.2-p180 :003 > f = Foo.new
=> #<Foo:0x00000100979658 #var_name=0>
ruby-1.9.2-p180 :004 > f.reader_name
=> 0
ruby-1.9.2-p180 :005 > f.var_name
NoMethodError: undefined method `var_name' for #<Foo:0x00000100979658 #var_name=0>
I hope you see how unintuitive this will be for others. You could override the to_s method to show how to access #var_name, but I wouldn't go there... or define the var_name method too (two getter-methods). Stick to conventions. Anyhow, I had great fun, good luck!
What's the best way to abstract this pattern:
class MyClass
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
A good solution should take superclasses into consideration and be able to handle still being able to have an initializer to do more things. Extra points for not sacrificing performance in your solution.
A solution to that problem already (partially) exists, but if you want a more declarative approach in your classes then the following should work.
class Class
def initialize_with(*attrs, &block)
attrs.each do |attr|
attr_accessor attr
end
(class << self; self; end).send :define_method, :new do |*args|
obj = allocate
init_args, surplus_args = args[0...attrs.size], args[attrs.size..-1]
attrs.zip(init_args) do |attr, arg|
obj.instance_variable_set "##{attr}", arg
end
obj.send :initialize, *surplus_args
obj
end
end
end
You can now do:
class MyClass < ParentClass
initialize_with :foo, :bar
def initialize(baz)
#initialized = true
super(baz) # pass any arguments to initializer of superclass
end
end
my_obj = MyClass.new "foo", "bar", "baz"
my_obj.foo #=> "foo"
my_obj.bar #=> "bar"
my_obj.instance_variable_get(:#initialized) #=> true
Some characteristics of this solution:
Specify constructor attributes with initialize_with
Optionally use initialize to do custom initialization
Possible to call super in initialize
Arguments to initialize are the arguments that were not consumed by attributes specified with initialize_with
Easily extracted into a Module
Constructor attributes specified with initialize_with are inherited, but defining a new set on a child class will remove the parent attributes
Dynamic solution probably has performance hit
If you want to create a solution with absolute minimal performance overhead, it would be not that difficult to refactor most of the functionality into a string which can be evaled when the initializer is defined. I have not benchmarked what the difference would be.
Note: I found that hacking new works better than hacking initialize. If you define initialize with metaprogramming, you'd probably get a scenario where you pass a block to initialize_with as a substitute initializer, and it's not possible to use super in a block.
This is the first solution that comes to my mind. There's one big downside in my module: you must define the class initialize method before including the module or it won't work.
There's probably a better solution for that problem, but this is what I wrote in less than a couple of minutes.
Also, I didn't keep performances too much into consideration. You probably can find a much better solution than me, especially talking about performances. ;)
#!/usr/bin/env ruby -wKU
require 'rubygems'
require 'activesupport'
module Initializable
def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
alias_method_chain :initialize, :attributes
class_inheritable_array :attr_initializable
end
end
module ClassMethods
def attr_initialized(*attrs)
attrs.flatten.each do |attr|
attr_accessor attr
end
self.attr_initializable = attrs.flatten
end
end
module InstanceMethods
def initialize_with_attributes(*args)
values = args.dup
self.attr_initializable.each do |attr|
self.send(:"#{attr}=", values.shift)
end
initialize_without_attributes(values)
end
end
end
class MyClass1
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
class MyClass2
def initialize(*args)
end
include Initializable
attr_initialized :foo, :bar
end
if $0 == __FILE__
require 'test/unit'
class InitializableTest < Test::Unit::TestCase
def test_equality
assert_equal MyClass1.new("foo1", "bar1").foo, MyClass2.new("foo1", "bar1").foo
assert_equal MyClass1.new("foo1", "bar1").bar, MyClass2.new("foo1", "bar1").bar
end
end
end
class MyClass < Struct.new(:foo, :bar)
end
I know this is an old question with perfectly acceptable answers but I wanted to post my solution as it takes advantage of Module#prepend (new in Ruby 2.2) and the fact that modules are also classes for very simple solution. First the module to make the magic:
class InitializeWith < Module
def initialize *attrs
super() do
define_method :initialize do |*args|
attrs.each { |attr| instance_variable_set "##{attr}", args.shift }
super *args
end
end
end
end
Now let's use our fancy module:
class MyClass
prepend InitializeWith.new :foo, :bar
end
Note that I left our the attr_accessible stuff as I consider that a separate concern although it would be trivial to support. Now I can create an instance with:
MyClass.new 'baz', 'boo'
I can still define an initialize for custom initialization. If my custom initialize take an argument those will be any extra arguments provided to the new instance. So:
class MyClass
prepend InitializeWith.new :foo, :bar
def initialize extra
puts extra
end
end
MyClass.new 'baz', 'boo', 'dog'
In the above example #foo='baz', #bar='boo' and it will print dog.
What I also like about this solution is that it doesn't pollute the global namespace with a DSL. Objects that want this functionality can prepend. Everybody else is untouched.
This module allows an attrs hash as an option to new(). You can include the module in a class with inheritance, and the constructor still works.
I like this better than a list of attr values as parameters, because, particularly with inherited attrs, I wouldn't like trying to remember which param was which.
module Attrize
def initialize(*args)
arg = args.select{|a| a.is_a?(Hash) && a[:attrs]}
if arg
arg[0][:attrs].each do |key, value|
self.class.class_eval{attr_accessor(key)} unless respond_to?(key)
send(key.to_s + '=', value)
end
args.delete(arg[0])
end
(args == []) ? super : super(*args)
end
end
class Hue
def initialize(transparent)
puts "I'm transparent" if transparent
end
end
class Color < Hue
include Attrize
def initialize(color, *args)
p color
super(*args)
p "My style is " + #style if #style
end
end
And you can do this:
irb(main):001:0> require 'attrize'
=> true
irb(main):002:0> c = Color.new("blue", false)
"blue"
=> #<Color:0x201df4>
irb(main):003:0> c = Color.new("blue", true, :attrs => {:style => 'electric'})
"blue"
I'm transparent
"My style is electric"