Source code for rspec "describe" method (and others)? - ruby-on-rails

I'm sauntering through Michael Hartl's Rails Tutorial right now, and am finding that I'm constantly encouraged to use wonderful methods that inexplicably do amazing things. He does a generally competent job of explaining what they do, but there is no real nitty gritty of why and how they work.
Specifically, I have just been plundering the rspec gem on github searching for the source code to the "describe" method. I cannot find it. Having now read a large amount of the source code (at an apprehension rate of about 25%) searching for it, I know that once found, I will need to look at its parent classes and modules to understand a certain amount of inheritance before I can really grasp (and then never let go of) the flesh and bones of "describe".
I don't mind struggling to grasp the concept, I'm a fan of attempting to read code in new languages before I fully understand it so that I can read it again later and use the comparison of my comprehension as a gauge of my fluency. I'd just like a kicker. Either a description or a file location with maybe a little helper hint to get me started.
For example...
I found this:
# RSpec.describe "something" do # << This describe method is defined in
# # << RSpec::Core::DSL, included in the
# # << global namespace (optional)
and rpsec/core/dsl states:
# DSL defines methods to group examples, most notably `describe`,
# and exposes them as class methods of {RSpec}. They can also be
# exposed globally (on `main` and instances of `Module`) through
# the {Configuration} option `expose_dsl_globally`.
but then there is no "class Describe" or def "describe" or such in that file.
SO: can anyone tell me where the "describe" method is, how it works, exactly, or (if not) why I am naively searching for the wrong thing in the wrong locations?

As you may know, there is no difference between describe and context methods and you can use them interchangably. Rspec developers could not let themselves to repeat the same code for different method names, so they moved the declaration to
module RSpec
module Core
class ExampleGroup
def self.define_example_group_method(name, metadata={})
# here they really define a method with given name using ruby metaprogramming
define_singleton_method(name) do |*args, &example_group_block|
And call that method a bit later for all the same-functionality DSL methods:
define_example_group_method :example_group
define_example_group_method :describe
define_example_group_method :context
So in case you are looking for describe method source, dive into define_example_group_method with assumption that name argument equals to describe and example_group_block is your block body.

The RSpec code base is not a trivial thing to get your head round. However, these links should get you started ...
This line defines the describe keyword:
https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/example_group.rb#L246
The method above that line does the heavy lifting for you. Take your time reading it.
This part then exposes the generated method:
https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/dsl.rb#L54
Good luck!

Related

Problem with constant autoloading in a Rails project (works occasionally)

I am working with a Rails project and don't quite understand how Rails autoloading works in my particular case. I read some articles about Rails' autoloading and its pitfalls but those didn't really help me
I am building a processor for tasks (exercises). Each task has its custom processor class in Tasks::<TaskName>::Processor that mixes in module Tasks::Processor that contain shared code for task processors. Processors contain class Get (for processing GET requests) located in Tasks::<TaskName>::Processor::Get that mixes in Tasks::Processor::Get containing generic Get's code.
I've simplified the code a little bit so it's easier to understand and removed all the business logic but it's still enough to reproduce the problem.
So the problem is:
when I run Tasks::TaskOne::Processor.new.get it works fine, but if I run Tasks::TaskTwo::Processor.new.get after that it throws an error: NoMethodError: undefined method `new' for Tasks::Processor::Get:Module. It also works the other way round: if I run TaskTwo's processor's code first then it works fine but the TaskOne's processor will throw the error. It just fails to find the specific implementation of Get and instead finds the generic module and tries to instantiate it which is obviously impossible.
Here is the code together with the structure.
Shared code:
app/models/tasks/processor.rb:
module Tasks
# generic Processor (mixed in by custom processors)
module Processor
# ...
end
end
app/models/tasks/processor/get.rb:
module Tasks
module Processor
# generic Get
module Get
# ...
end
end
end
TaskOne's code:
app/models/tasks/task_one/processor.rb:
module Tasks
module TaskOne
# processor for task_one
class Processor
include Tasks::Processor # mix in generic task processor
def get
Get.new.call
end
end
end
end
app/models/tasks/task_one/processor/get.rb:
module Tasks
module TaskOne
class Processor
# task_one's processor's custom Get
class Get
include Tasks::Processor::Get # mix in generic Get
def call
puts "in task_one's Processor's Get"
end
end
end
end
end
And practically identical code for the TaskTwo:
app/models/tasks/task_two/processor.rb:
module Tasks
module TaskTwo
# processor for task_two
class Processor
include Tasks::Processor # mix in generic task processor
def get
Get.new.call
end
end
end
end
app/models/tasks/task_two/processor/get.rb:
module Tasks
module TaskTwo
class Processor
# task_two's processor's custom Get
class Get
include Tasks::Processor::Get # mix in generic Get
def call
puts "in task_two's Processor's Get"
end
end
end
end
end
It has most likely something to do with Rails' autoloading, because when I use plain ruby and manually require all the files and try to run the code the problem doesn't happen.
Could you, please, explain why it works like this and tell me what the best way to avoid this problem is? Seems like Rails doesn't like the fact that I have a class and a module with same name and it gets confused, but I thought it shouldn't be a problem as they are in different namespaces.
I could have just named the generic class something different, but I'd really like to understand why using the same class name for both specific implementation and generic one only works for the first thing to load but not for the next. Thank you very much for your help!
P.S. my version of Ruby is 2.5.1 and Rails version is 5.2.1
I was literally reading about autoloading yesterday. Your problem is the same as the one outlined here:
https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#when-constants-aren-t-missed
Basically, any time you write Get.new.call, you need to be more specific. It doesn’t know which Get to use in the tree of possible Gets. The first time you call it, it hasn’t had to load up more than one Get class, and so it actually finds the right one. After that call, you’ve now auto loaded MORE classes, and now things start to get dicey. You need to either qualify your Get to be more specific, and/or use require_dependency to force the right classes to be loaded in. However given your case, I think require_dependency will just make it fail every time, since you’ll now have all of the classes loaded up.

RSpec - Using `let` in spec helper

I have a large model that takes time to initialize in my RSpec tests.
I want it potentially available to every example, but want to only load it if an example requires it.
This seems like the perfect use for let()'s lazy loading - only load it when you need it.
In any particular spec file I can do
require "spec_helper"
feature "foo" do
let(:big_class) { MyBigClass.new(bar) }
...
end
This will make big_class available to every example in that spec file.
Is there a way to make this more global so that EVERY spec file and example can use it? I couldn't find a good way to initialize let inside the spec helper.
You may simply define a shared context and include it in every example. Regarding your particular question, it should look like following:
RSpec.shared_context "Global helpers" do
let(:big_class) { MyBigClass.new(bar) }
end
RSpec.configure do |config|
config.include_context "Global helpers"
end
However, it's rarely a good idea to include a shared context in all examples, and that big_class helper from your question really looks like something domain-specific. You can steer the shared context inclusion by metadata, for example when you want to include given shared context in feature specs only (they all have :type => :feature metadata set by default), you can do it this way:
RSpec.configure do |config|
config.include_context "Feature spec helpers", :type => :feature
end
You might consider other approaches:
Use mock objects instead of real ones.
Refactor the initializer and extract the slow operation to another method
Mock objects of course bring their own set of drawbacks; they can become stale and make tests more brittle. But for some tests that is not an issue.
Refactoring initializers is a favorite of mine. E.g.
MyBigObject.new(args)
becomes
MyBigObject.new(args).setup
or :load_data or :connect_to_database_on_the_moon or whatever is taking a long time. You get the picture.
Obviously this means changing your code, but I find that often works out to be helpful in other ways, and it certainly makes testing easier.
You don't want to use let. From the docs:
Use let to define a memoized helper method. The value will be cached across
multiple calls in the same example but not across examples.
You'll end up instantiating MyBigClass lots of times. I would recommend creating a global helper method somewhere in spec_helper.rb (or similar) that used memo-ization on it's own to return the cached value if it's already been setup.
Also be very careful with all this as you're violating the rule of isolated tests. Might be fine for what you're doing, but it's a red flag.
Use global before hook with an instance variable
From the docs:
RSpec.configure do |c|
c.before(:example) { #big_class = MyBigClass.new(bar) }
end
RSpec.describe MyExample do
it { expect(#big_class).to eql(MyBigClass.new(bar)) } # This code will pass
end
For more details check the suggestions in this answer

Finding out where methods are defined in Ruby/Rails (as opposed to Java)

I am just getting started with Ruby on Rails. Coming from the Java world, one thing that I am wondering is how do Ruby/Rails developers find out where methods are actually defined.
I am used to just clicking on the method in Eclipse to find where is is defined even in third party libraries (supposing I have the source code).
A concrete example: I am trying to find out how the Authlogic gem apparently changes the constructor of my User class to require an additional parameter (called :password_confirmation) even though the User class doesn't even inherit from anything related to Authlogic.
Probably I am just overlooking something really obvious here (or maybe I still can't wrap my head around the whole "convention over configuration" thing ;-))
It's slightly difficult to quickly find the method location for dynamic languages like Ruby.
You can use object.methods or object.instance_methods to quickly find out the methods.
If you are using Ruby 1.9, you can do something like this:
object.method(:method_name).source_location
For more information on source_location - click here
The Pry gem is designed precisely for this kind of explorative use-case.
Pry is an interactive shell that lets you navigate your way around a program's source-code using shell-like commands such as cd and ls.
You can pull the documentation for any method you encounter and even view the source code, including the native C code in some cases (with the pry-doc plugin). You can even jump directly to the file/line where a particular method is defined with the edit-method command. The show-method and show-doc commands also display the precise location of the method they're acting on.
Watch the railscast screencast for more information.
Here are some examples below:
pry(main)> show-doc OpenStruct#initialize
From: /Users/john/.rvm/rubies/ruby-1.9.2-p180/lib/ruby/1.9.1/ostruct.rb # line 46:
Number of lines: 11
visibility: private
signature: initialize(hash=?)
Create a new OpenStruct object. The optional hash, if given, will
generate attributes and values. For example.
require 'ostruct'
hash = { "country" => "Australia", :population => 20_000_000 }
data = OpenStruct.new(hash)
p data # -> <OpenStruct country="Australia" population=20000000>
By default, the resulting OpenStruct object will have no attributes.
pry(main)>
You can also look up sourcecode with the show-method command:
pry(main)> show-method OpenStruct#initialize
From: /Users/john/.rvm/rubies/ruby-1.9.2-p180/lib/ruby/1.9.1/ostruct.rb # line 46:
Number of lines: 9
def initialize(hash=nil)
#table = {}
if hash
for k,v in hash
#table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
pry(main)>
See http://pry.github.com for more information :)
None of people advising Pry gem mentionned the method called find-method, which is probably what author was looking for.
Here's the example:
pry(main)> find-method current_user
Devise::Controllers::Helpers
Devise::Controllers::Helpers#current_user
WebsocketRails::ConnectionAdapters::Base
WebsocketRails::ConnectionAdapters::Base#current_user_responds_to?
Then, you can browse the method code by following #banister's tips.
You could use something like pry. See its railscast also.
There are several ways to change an existing class. E.g. if you want to modify the String class write:
class String
def my_custom_method
puts "hello!"
end
end
But there are other options like mixing in modules or adding/modifying methods by using meta-programming.
Anyhow, having some object you can always:
puts obj.methods.inspect
Either do it in your code or use the debugger.
The other option is to read the code. In particular you should read the gem's unit tests (./spec, ...). There are quite a lot of authors stating that unit tests make documentation obsolete.
In Ruby you can also add both class and instance methods to a given class by using mixins.
Essentially if you have a module you can add its methods to a given class using both include and extend class methods. A brief example on how those works is the following
Module A
def foo
"foo"
end
end
Module B
def bar
"bar"
end
end
Class YourClass
include A
extend B
end
p YourClass.new.foo # gives "foo" because the foo method is added as instance method
p YourClass.bar # gives "baz" because the bar method is added as class method
Because Ruby is a dynamic language, these statements can be used everywhere. So to come to your question there is no need to extend an authlogic class to get its methods. Many plugins uses this instruction when loaded
ActiveRecord::Base.send :include, ModuleName
In this way they tell to every AR object to include some plugin defined module and you get all the methods in AR objects.
Another technique used by many acts_as plugins is to include their modules only when the acts_as call is used in the base class.
Other useful references
What is the difference between include and extend in Ruby?
A quick tutorial about mixins

Ruby on Rails: alias_method_chain, what exactly does it do?

I've tried reading through various blog posts that attempt to explain alias_method_chain and the reasons to use it and not use it. In particular, I took heed to:
http://weblog.rubyonrails.org/2006/4/26/new-in-rails-module-alias_method_chain
and
http://yehudakatz.com/2009/03/06/alias_method_chain-in-models/
I still do not see any practical use for alias_method_chain. Would anyone be able to explain a few things.
1 - is it still used at all?
2 - when would you use alias_method_chain and why?
1 - is it still used at all?
Apparently yes, alias_method_chain() is still used in Rails (as of version 3.0.0).
2 - when would you use
alias_method_chain and why?
(Note: the following is largely based on the discussion of alias_method_chain() in Metaprogramming Ruby by Paolo Perrotta, which is an excellent book that you should get your hands on.)
Let's start with a basic example:
class Klass
def salute
puts "Aloha!"
end
end
Klass.new.salute # => Aloha!
Now suppose that we want to surround Klass#salute() with logging behavior. We can do that what Perrotta calls an around alias:
class Klass
def salute_with_log
puts "Calling method..."
salute_without_log
puts "...Method called"
end
alias_method :salute_without_log, :salute
alias_method :salute, :salute_with_log
end
Klass.new.salute
# Prints the following:
# Calling method...
# Aloha!
# ...Method called
We defined a new method called salute_with_log() and aliased it to salute(). The code that used to call salute() still works, but it gets the new logging behavior as well. We also defined an alias to the original salute(), so we can still salute without logging:
Klass.new.salute_without_log # => Aloha!
So, salute() is now called salute_without_log(). If we want logging, we can call either salute_with_log() or salute(), which are aliases of the same method. Confused? Good!
According to Perrotta, this kind of around alias is very common in Rails:
Look at another example of Rails
solving a problem its own way. A few
versions ago, the Rails code contained
many instances of the same idiom: an
Around Alias (155) was used to add a
feature to a method, and the old
version of the method was renamed to
something like
method_without_feature(). Apart from
the method names, which changed every
time, the code that did this was
always the same, duplicated all over
the place. In most languages, you
cannot avoid that kind of duplication.
In Ruby, you can sprinkle some
metaprogramming magic over your
pattern and extract it into its own
method... and thus was born
alias_method_chain().
In other words, you provide the original method, foo(), and the enhanced method, foo_with_feature(), and you end up with three methods: foo(), foo_with_feature(), and foo_without_feature(). The first two include the feature, while the third doesn't. Instead of duplicating these aliases all around, alias_method_chain() provided by ActiveSupport does all the aliasing for you.
alias_method_chain has been deprecated in Rails 5 in favour of Module#prepend.
Pull request: https://github.com/rails/rails/pull/19434
Changelog: https://github.com/rails/rails/blob/b292b76c2dd0f04fb090d49b90716a0e6037b41a/guides/source/5_0_release_notes.md#deprecations-4
I'm not sure if it's gone out of style with Rails 3 or not, but it is still actively used in versions before that.
You use it to inject some functionality before (or after) a method is called, without modifying any place that calls that method. See this example:
module SwitchableSmtp
module InstanceMethods
def deliver_with_switchable_smtp!(mail = #mail)
unless logger.nil?
logger.info "Switching SMTP server to: #{custom_smtp.inspect}"
end
ActionMailer::Base.smtp_settings = custom_smtp unless custom_smtp.nil?
deliver_without_switchable_smtp!(mail = #mail)
end
end
def self.included(receiver)
receiver.send :include, InstanceMethods
receiver.class_eval do
alias_method_chain :deliver!, :switchable_smtp
end
end
end
That's an addition to ActionMailer to allow swapping out of the SMTP settings on each call to deliver!. By calling alias_method_chain you are able to define a method deliver_with_switchable_smtp! in which you do your custom stuff, and call deliver_without_switchable_smtp! from there when you're done.
alias_method_chain aliases the old deliver! to your new custom method, so the rest of your app doesn't even know deliver! now does your custom stuff too.
is it used at all?
Seems so. It's a common practice among Rails developers
when would you use alias_method_chain and why?
Despite the warnings, alias_method_chain is still the main strategy used when injecting functionality to an existing method, at least was in Rails 2.x and is followed by many people extending it. Yehuda ought to remove alias_method_chain from rails 3.0 to say from his posts and comments in Rails tickets. It is still used by many extensions that add custom behavior at certain points of the execution, such as loggers, error reporters, benchmarking, data injection, etc.
IMO, the best alternative is to include a module, thus you have decoration over delegation. (For example, follow example 4 in this post). That way you can alter the objects even individually if you'd like, without polluting the class' methods. The downside to this is that the method lookup chain increases for each module you inject, but this is what modules are for anyway.
Very interesting question, will keep a look on what other people think about it.

Ruby on Rails: Learning ActionController class - Question on $:.unshift activesupport_path and autoload method

Inside ActionController class (rails/actionpack/lib/action_controller.lib) I found several weird code. I don't really have a mentor to learn Ruby on Rails from, so this forum is my only hope:
Question #1: Could anyone help me explain these lines of codes?
begin
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
end
end
Especially the line with $:.unshift activesupport_path
In my thought, it tries to require active_support class, and if that doesn't work, it looks if activesupport_path is a directory, if it is, then . . . I totally lost it.
Question #2: What autoload method is for?
module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :Base, 'action_controller/base'
autoload :Benchmarking, 'action_controller/benchmarking'
autoload :Caching, 'action_controller/caching'
autoload :Cookies, 'action_controller/cookies'
.
.
.
Question #3: If I later find a method I don't understand what for, how is the best way to find out? As for that autoload method case, I tried to find it across my project (I have my Rails code frozen there) but couldn't find any clue. I searched for "def autoload". Am I doing things wrong? Is my IDE, TextMate just doesn't cut it?
Thank you!
In order for a file to be required you have to ensure that the path to it is in the Ruby $LOAD_PATH variable. This is has a short-hand version $: for legacy reasons, inheriting this from Perl.
When you call require, the interpreter looks for a .rb file in each of the paths given there until it finds a match. If it finds one, it is loaded. If not you get an exception.
Often you will see lines like this in files:
# script/something
# This appends "script/../lib" to the $LOAD_PATH, but this expands to
# something like "/home/user/project/lib" depending on the details of
# your installation.
$: << File.expand_path(File.join('..', 'lib'), File.dirname(__FILE__))
You can use standard Array modifiers on $LOAD_PATH like unshift, push, and <<.
The first block of code is attempting to load active_support and only if that fails does it go about modifying the $LOAD_PATH to include the likely location of this file based on the path to the file making the require call. They do this because typically all gems from the Rails bundle are installed in the same base directory.
The reason for using unshift is to put that path at the highest priority, inserted at the front of the list. The << or push method adds to the end, lowest priority.
When you require a file it is loaded in, parsed, and evaluated, an operation which can take a small but measurable amount of time and will consume more memory to hold any class or method definitions inside the file, as well as any data such as string constants that may be declared. Loading in every single element of a library like ActiveRecord using require will require a considerable amount of memory, and this will import every database driver available, not just the ones that are actually used.
Ruby allows you to declare a class and a path to the file where it is defined, but with the advantage of not actually loading it in at that moment. This means that references to that class don't cause script errors in other parts of your application that make use of them.
You will often see declarations like this:
class Foo
# Declare the class Foo::Bar to be defined in foo/bar.rb
autoload(:Bar, 'foo/bar')
end
When using autoload you need to keep in mind that the class name is always defined within the scope of the module or class declaring it. In this example Bar is within Foo, or Foo::Bar using Ruby naming conventions.
When you make use of the Bar class, the foo/bar.rb file will be required. Think of it as creating a stub Bar class that transforms into the real class once it's actually exercised.
This is a great way of keeping a lot of options open, with many different modules ready to use, but without having to load everything into memory up front.
As for the third question, searchable documentation like APIDock will help you try and find more information on methods. The distinction between Ruby and Rails is often blurred, so you may have to check through both to be sure. Rails adds a lot of methods to core Ruby classes, so don't take the listing of methods available to be complete on either side. They work in conjunction.
Sometimes it pays to search for def methodname when trying to find out about where methodname originates, although this covers only conventional declarations. That method may be an alias from a mechanism like method_alias or may have been dynamically created using define_method, you can never really be sure until you dig around. At least 90% of the methods in Rails are declared the conventional way, though, so most of the time a simple search will yield what you want.

Resources