if you have something like:
module Real
A = 1
end
when you do defined?(Real::A) you get 'constant' which is a truish value. Now if i do something like:
module Virtual
def self.constants
[:A] + super
end
def self.const_missing(sym)
return 1 if sym == :A
super
end
def self.const_defined?(sym)
return true if sym == :A
super
end
end
defined?(Virtual::A) return nil. Is there some way to overwrite defined? behaviour to take metaprogrammed constants into acccount?
defined? is actually an operator (and not just syntactic sugar like +) and as such cannot be redefined. The proper solution would be to not use defined? for checking but aforementioned const_defined?. defined? isn't intended for meta-programming and works on the parser level, which is why it can give rather detailed information on the type of expression.
Related
I want to write a method in ruby to replace a current method that simply returns a hash.
So currently we have this
def some_stats
{
:foo => "bar",
:zoo => "zar"
}
end
which can be called like this:
some_stats[:foo]
I want to replace this with a method that is called with :foo, which does some more complicated things that just building a hash. However, I'd like it to be called in the same way, with the square bracket notation. EG
#this is a bit pseudo-cody but hopefully you get the idea.
def some_stats[](key)
if key.to_s =~ /something/
#do something
return something
else
#do something else
return something_else
end
end
This would still be called like some_stats[:foo]
I can't work out the syntax for defining the method name: the above, def some_stats[](key) doesn't work. I feel like this should be possible though.
You're quite close. object[sth] is just a syntax sugar for object.[](sth). So to do what you need you have to define some_stats method that returns the object which defines [] method:
class Stats
def [](key)
if key.to_s =~ /something/
#do something
return something
else
#do something else
return something_else
end
end
def some_stats
Stats.new
end
some_stats[:something_new] #=> something
some_stats[:not_new] #=> something_else
Your original method is not called with square brackets; it is called without any argument, but returns an object (in your case a Hash), which has method :[] defined and hence understands square brackets. Your method call is equivalent to
(some_stats())[:foo]
or, more explicitly, to
x = some_stats()
x[:foo]
Therefore you need - inside your method return an instance of some class which offers the [] method. Say you have written a class named MyClass which has a [] defined. You could then write a function
def some_stats
if _your condition goes here_
{ ... } # Return your Hash
else
Myclass.new
end
end
This would be used as
some_stats[:whatever]
However, to be really useful, your condition would make use of a parameter to the method, i.e.
def some_stats(param)
if param < 4000
{ ... } # Return your Hash
else
Myclass.new
end
end
And you would invoke it by
some_stats(4711)[:whatever]
What does a ruby method ending with an "=" mean?
See the available methods in this print out:
2.2.0 :066 > obj.methods(false)
=> [:label=, :label, :description=, :description, :thumbnail=, :thumbnail, :attribution=, :attribution, :license=, :license, :logo=, :logo, :see_also=, :seeAlso=, :see_also, :seeAlso, :related=, :related, :within=, :within, :metadata=, :metadata, :sequences=, :sequences, :structures=, :structures, :viewing_hint=, :viewingHint=, :viewing_hint, :viewingHint, :viewing_direction=, :viewingDirection=, :viewing_direction, :viewingDirection, :service=, :service]
For example whats this difference between label= and label?
foo= is no different than any other method, except:
it requires precisely one argument and
Ruby permits you to add spaces before the = character.
class Foo
def foo=(other)
puts 'hi'
end
end
Foo.new.foo = 7
hi
class Goo
def goo=
puts 'hi'
end
end
Goo.new.goo=
Ruby says, "I'm waiting for an argument...". So we provide one:
4
and then she complains about what she asked you to do:
#=> ArgumentError: wrong number of arguments (1 for 0)
= methods are typically used to create a setter for an instance variable (if attr_acccessor or attr_writer is not used):
class Foo
def initialize(foo)
#foo=foo
end
# getter
def foo
#foo
end
# setter
def foo=(other)
#foo = other
end
end
f = Foo.new('dog')
f.foo
#=> "dog"
f.foo = 'cat'
#=> "cat"
f.foo
#=> "cat"
the methods ending with "=" are setting the instance variable
look at the answer here: why-use-rubys-attr-accessor-attr-reader-and-attr-writer
It is equivalent of setter methods in other languages, it is just convention so it looks more natural to say
obj.description="Fun Site"
vs
obj.setDescription("Fun Site")
There is nothing special about methods that end in =
You can see this by running the code below:
def bob=
puts "bob="
end
p send('bob='.to_sym)
What is special is the '=' infix operator. When you write self.bob = "bill". It is interpreted as self.send('bob='.to_sym, "bill").
Putting a ? at the end of a method is a hint that it returns a boolean (true/false). Methods that end in ! hint that the method affects the instance. See String#chomp vs String#chomp.
You can find out more about ruby operators at http://www.tutorialspoint.com/ruby/ruby_operators.htm and more about naming conventions at https://github.com/bbatsov/ruby-style-guide#naming
I'm trying to DRY up some code, and I feel like Ruby's variable assignment must provide a way to simplify this. I have a class with a number of different instance variables defined. Some of these are intended to be hidden (or read-only), but many are public, with read/write access.
For all of the variables with public write-access, I want to perform a certain method after each assignment. I know that, in general, I can do this:
def foo=(new_foo)
#foo = new_foo
post_process(#foo)
end
def bar=(new_bar)
#bar = new_bar
post_process(#foo)
end
However, it seems that there should be a nice way to DRY this up, since I'm doing essentially the same thing after each assignment (ie, running the same method, and passing the newly-assigned variable as a parameter to that method). Since I have a number of such variables, it would be great to have a general-purpose solution.
Simpler solution
If you assign those variables in batch, you can do something like this:
kv_pairs = {:foo => new_foo_value,
:bar => new_bar_value}
kv_pairs.each do |k, v|
self.send(k.to_s + '=', v)
post_process(v)
end
Metaprogramming
Here's some ruby magic :-)
module PostProcessAssignments
def hooked_accessor( *symbols )
symbols.each { | symbol |
class_eval( "def #{symbol}() ##{symbol}; end" )
class_eval( "def #{symbol}=(val) ##{symbol} = val; post_process('#{symbol}', val); end" )
}
end
end
class MyClass
extend PostProcessAssignments
hooked_accessor :foo
def post_process prop, val
puts "#{prop} was set to #{val}"
end
end
mc = MyClass.new
mc.foo = 4
puts mc.foo
Outputs:
foo was set to 4
4
Is def greet; puts "hello"; end the only way to define a method on one line in Ruby?
You can avoid the need to use semicolons if you use parentheses:
def hello() :hello end
No Single-line Methods
From rubocop/ruby-style-guide#no-single-line-methods:
Avoid single-line methods. Although they are somewhat popular in the
wild, there are a few peculiarities about their definition syntax that
make their use undesirable. At any rate - there should be no more
than one expression in a single-line method.
NOTE: Ruby 3 introduced an alternative syntax for single-line method
definitions, that's discussed in the next section of the guide.
# bad
def too_much; something; something_else; end
# okish - notice that the first ; is required
def no_braces_method; body end
# okish - notice that the second ; is optional
def no_braces_method; body; end
# okish - valid syntax, but no ; makes it kind of hard to read
def some_method() body end
# good
def some_method
body
end
One exception to the rule are empty-body methods.
# good
def no_op; end
Endless Methods
From rubocop/ruby-style-guide#endless-methods:
Only use Ruby 3.0's endless method definitions with a single line
body. Ideally, such method definitions should be both simple (a
single expression) and free of side effects.
NOTE: It's important to understand that this guideline doesn't
contradict the previous one. We still caution against the use of
single-line method definitions, but if such methods are to be used,
prefer endless methods.
# bad
def fib(x) = if x < 2
x
else
fib(x - 1) + fib(x - 2)
end
# good
def the_answer = 42
def get_x = #x
def square(x) = x * x
# Not (so) good: has side effect
def set_x(x) = (#x = x)
def print_foo = puts("foo")
P.S.: Just to give an up-to-date full answer.
def add a,b; a+b end
The semicolon is the inline statement terminator for Ruby
Or you can use the define_method method. (Edit: This one's deprecated in ruby 1.9)
define_method(:add) {|a,b| a+b }
Ruby 3.0.0 adds "endless" definitions for methods with exactly one statement:
def greet = puts("hello")
Note that the one-statement limitation means that this can't be written as:
# NOT ALLOWED
def greet = puts "hello"
SyntaxError: unexpected string literal, expecting `do' or '{' or '('
def greet = puts "hello"
^
It seems that this change was intended to either encourage the use of one-line methods or adjust to the reality that they are very common but hard to read -- "this kind of simple method definition [is estimated to] account for 24% of the entire method definitions" of the ruby/ruby code base.
Another way:
define_method(:greet) { puts 'hello' }
May be used if you don't want to enter new scope for method while defining it.
Yet another way:
def greet() return 'Hello' end
Ruby enthusiasts! I am trying to write a DSL in ruby and i would like to be able to create some magic methods (not sure that is the most accurate term for what i want).
I would like to be able to do things like the following:
a = [1, 2, 3]
b = 2
(a contains b)
And have it resolve to true or false.
Essentially, how can i define the function "contains" so that it takes an array a and a variable b and performs a.contains?(b), but without all of the associated ruby-specific syntax?
if you want a DSL that doesn't use ruby syntax, you need to write a parser at the very least to perform the transformation (raganwalds rewrite lib might be a starting point, http://github.com/raganwald/rewrite)
That said, you don't want to do this. This is more code to maintain and Ruby has already made a lot of the tough decisions that make writing a language syntax hard. Natural language programming also isn't much easier for nonprogrammers to use as the exactness of the format is the challenging aspect (see applescript for instance).
You can abuse method_missing. The tricky thing is, that you cannot access the blocks local variables directly. You'll have to capture the blocks inner binding somewhere (unfortunately block.binding returns the block's outer binding).
You can run this code:
DSL.new do
a = [1, 2, 3]
b = 2
a contains b
end
With the following:
class DSL
attr_reader :last_binding
def initialize(&block)
set_trace_func method(:trace).to_proc
instance_eval(&block)
set_trace_func nil
end
def trace(event, file, line, id, binding, klass)
if event.to_s == "call" and klass == self.class and id.to_s == "method_missing"
#last_binding ||= #current_binding
set_trace_func nil
else
#current_binding = binding
end
end
def lvars
eval('local_variables', last_binding).map(&:to_s)
end
def method_missing(name, *args)
name = name.to_s
if lvars.include? name
eval(name, last_binding).send(*args.flatten)
else
["#{name}?", *args]
end
end
end
class Array
alias contains? include?
end
The closest thing I could think of would be:
def contains var, useless_symbol, arr
arr.include? var
end
Then you could call it like:
contains b, :in, a
I don't think there is any way to be able to use infix notation in your own functions.