Is it possible to rename an existing Erlang module? I have some code in several Erlang modules that I want to use in an Elixir project (assertion library).
I don't want to convert all the Erlang modules to Elixir as they are complete, but I want to rename them, and possible add additional functions to them. Both modules are simply collections of functions, they don't implement behaviors or do anything unusual.
I want to be able to take an existing Erlang module:
-module(foo_erl).
-export([some_fun/1]).
some_fun(Arg) ->
ok.
And write an Elixir module to extend the Erlang one:
defmodule ExFoo do
somehow_extends_erlang_module :foo_erl
another_fun(arg) do
:ok
end
end
And then be able to use the functions defined in the Erlang module:
iex(1)> ExFoo.some_fun(:arg) #=> :ok
Is this possible in Elixir? If so, how would I go about doing this?
Here's the first thing that I can suggest. I'm 100% sure that it could be solved in a more elegant way. I'll back to this later since I'm not an Elixir guru yet.
defmodule Wrapper do
defmacro __using__(module) do
exports = module.module_info[:exports] -- [module_info: 0, module_info: 1]
for {func_name, arity} <- exports do
args = make_args(arity)
quote do
def unquote(func_name)(unquote_splicing(args)) do
unquote(module).unquote(func_name)(unquote_splicing(args))
end
end
end
end
defp make_args(0), do: []
defp make_args(arity) do
Enum.map 1..arity, &(Macro.var :"arg#{&1}", __MODULE__)
end
end
defmodule WrapperTest do
use ExUnit.Case, async: true
use Wrapper, :lists
test "max function works properly" do
assert (max [1, 2]) == 2
end
end
In general, we suggest to avoid wrapping Erlang modules. In case you want to wrap many modules in an automatic fashion, velimir's post is right on the spot.
If you have one-off case though, where you definitely want to wrap an Erlang module, I would suggest to simply use defdelegate:
defmodule MyLists do
defdelegate [flatten(list), map(fun, list)], to: :lists
end
Related
I've been playing around with Elixir/Phoenix third-party modules. ( Modules that are used to fetch some data from a 3rd party service ) One of those module looking like so:
module TwitterService do
#twitter_url "https://api.twitter.com/1.1"
def fetch_tweets(user) do
# The actual code to fetch tweets
HTTPoison.get(#twitter_url)
|> process_response
end
def process_response({:ok, resp}) do
{:ok, Poison.decode! resp}
end
def process_response(_fail), do: {:ok, []}
end
The actual data doesn't matter in my question. So now, I'm interested in how can I dynamically configure the #twitter_url module variable in tests to make some of the tests fail on purpose. For example:
module TwitterServiceTest
test "Module returns {:ok, []} when Twitter API isn't available"
# I'd like this to be possible ( coming from the world of Rails )
TwitterService.configure(:twitter_url, "new_value") # This line isn't possible
# Now the TwiterService shouldn't get anything from the url
tweets = TwitterService.fetch_tweets("test")
assert {:ok, []} = tweets
end
end
How can I achieve this?
Note: I know I can use :configs to configure #twiter_url separately in dev and test environments, but I'd like to be able to test on a real response from the Twitter API too, and that would change the URL on the entire Test environment.
One of the solutions that I came up with was
def fetch_tweets(user, opts \\ []) do
_fetch_tweets(user, opts[:fail_on_test] || false)
end
defp _fetch_tweets(user, [fail_on_test: true]) do
# Fails
end
defp _fetch_tweets(user, [fail_on_test: false]) do
# Normal fetching
end
But that just seems hackish and silly, there must be a better solution to this.
As it was suggested by José in Mocks And Explicit Contracts, the best way would be probably to use a dependency injection:
module TwitterService do
#twitter_url "https://api.twitter.com/1.1"
def fetch_tweets(user, service_url \\ #twitter_url) do
# The actual code to fetch tweets
service_url
|> HTTPoison.get()
|> process_response
end
...
end
Now in tests you just inject another dependency when necessary:
# to test against real service
fetch_tweets(user)
# to test against mocked service
fetch_tweets(user, SOME_MOCK_URL)
This approach will also make it easier to plug in different service in the future. The processor implementation should not depend on it’s underlying service, assuming the service follows some contract (responds with json given a url in such a particular case.)
config sounds like a good way here. You can modify the value in the config at runtime in your test and then restore it after the test.
First, in your actual code, instead of #twitter_url, use Application.get_env(:my_app, :twitter_url).
Then, in your tests, you can use a wrapper function like this:
def with_twitter_url(new_twitter_url, func) do
old_twitter_url = Application.get_env(:my_app, :twitter_url)
Application.set_env(:my_app, :twitter_url, new_twitter_url)
func.()
Application.set_env(:my_app, :twitter_url, old_twitter_url)
end
Now in your tests, do:
with_twitter_url "<new url>", fn ->
# All calls to your module here will use the new url.
end
Make sure you're not using async tests for this as this technique modifies global environment.
I would like to create some changeable boundary module. Ideally the result would look like any other module but the behaviour could be set at compile time or in the configuration files. I think I am looking for something like define in erlang
Say I have a SystemClock module and a DummyClock tuple module. Ideally the Clock module would be one or other of the two modules above chosen in the config file.
In config/test.ex
define(Clock, {DummyClock, 12345678})
Later
Clock.now
# => 12345678
In config/prod.ex
define(Clock, SystemClock)
Later
Clock.now
# => 32145687
I think the easiest way to do this is with configs and Application.get_env/2.
in config/test.exs
config :my_application, clock: DummyClock
in config/dev.exs and config/prod.exs
config :my_application, clock: RealClock
in the code that uses the clock
defp clock, do: Application.get_env(:my_application, :clock)
def my_function(arg1, arg2) do
now = clock.now
# ...
end
An option that I've used many times is Meck, which you can get/read about here. For your example, we might write something like:
test "something that depends on Clock calls Clock.now" do
:meck.new(Clock, [])
:meck.expect(Clock, :now, fn -> 12345678 end)
# we expect that the module under test is getting it's result from Clock
assert ModuleUnderTest.execute == 12345678
end
Instead of re-compiling your code to get :test behavior, Meck replaces the functionality of your modules on the fly, during your tests.
The discussion referenced in other comments here points out some reasons not to use mocking; Yes, you have to sacrifice certain things (sequential testing, and José's opinion of your testing code), but in exchange, you can dramatically simplify testing code in tricky situations.
I'm using ExUnit for testing my Elixir app, which is a card game.
I find that with every test I write, I start by creating a fresh deck of cards.
test "Do This Crazy Thing do
deck = Deck.create()
[...]
end
test "Do This Other Crazy Unrelated Thing" do
deck = Deck.create()
[...]
end
Is there a way to factor this out so that a new deck can just be created before every test case? I know there's something close to this with setup do [...] end, but I don't think that's the solution for me.
Do I need a different test framework? Do I need to use setup in some way I haven't thought of yet?
-Augie
You can use def setup with the meta has just for this.
Example:
defmodule DeckTest do
use ExUnit.Case
setup do
{:ok, cards: [:ace, :king, :queen] }
end
test "the truth", meta do
assert meta[:cards] == [:ace, :king, :queen]
end
end
Here's some more info
Another option that could work depending on your needs:
defmodule DeckTest do
use ExUnit.Case
defp cards, do: [:ace, :king, :queen]
test "the truth" do
assert cards == [:ace, :king, :queen]
end
end
I want to define a function available_translations which lists the translations I have made for my application into the I18n module.
I tried putting the following into the file lib/i18n.rb, but it doesn't work when I try to use it from the rails console:
module I18n
# Return the translations available for this application.
def self.available_translations
languages = []
Dir.glob(Rails.root.to_s + '/config/locales/*.yml') do |filename|
if md = filename.match #^.+/(\w+).yml$#
languages << md[1]
end
end
languages
end
end
Console:
ruby-1.9.2-p290 :003 > require Rails.root.to_s + '/lib/i18n.rb'
=> false
ruby-1.9.2-p290 :004 > I18n.available_translations
NoMethodError: undefined method `available_translations' for I18n:Module
...
Besides solving my concrete problem, I would be very pleased to learn how this whole module thing in Ruby on Rails works because it still confuses me, so I would appreciate links to the docs or source code very much.
Either of these will solve your problem:
move the code to config/initializers/i18n.rb, or
require your file from config/application.rb, or
name your class otherwise (to trigger autoload)
The code in lib/i18n.rb wil not be loaded by autoload since I18n name will be already loaded, so either you load it yourself or change the class name (and file name) so the new name will trigger autoload behavior.
BTW, the I18n.available_locales() method is presented in rails.
Is there a way to disable SQL query logging when I'm executing commands in the console? Ideally, it would be great if I can just disable it and re-enable it with a command in the console.
I'm trying to debug something and using "puts" to print out some relevant data. However, the sql query output is making it hard to read.
Edit:
I found another solution, since setting the logger to nil sometimes raised an error, if something other than my code tried to call logger.warn
Instead of setting the logger to nil you can set the level of the logger to 1.
ActiveRecord::Base.logger.level = 1 # or Logger::INFO
To turn it off:
old_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = nil
To turn it back on:
ActiveRecord::Base.logger = old_logger
This might not be a suitable solution for the console, but Rails has a method for this problem: Logger#silence
ActiveRecord::Base.logger.silence do
# the stuff you want to be silenced
end
Here's a variation I consider somewhat cleaner, that still allows potential other logging from AR. In config/environments/development.rb :
config.after_initialize do
ActiveRecord::Base.logger = Rails.logger.clone
ActiveRecord::Base.logger.level = Logger::INFO
end
For Rails 4 you can put the following in an environment file:
# /config/environments/development.rb
config.active_record.logger = nil
In case someone wants to actually knock out SQL statement logging (without changing logging level, and while keeping the logging from their AR models):
The line that writes to the log (in Rails 3.2.16, anyway) is the call to debug in lib/active_record/log_subscriber.rb:50.
That debug method is defined by ActiveSupport::LogSubscriber.
So we can knock out the logging by overwriting it like so:
module ActiveSupport
class LogSubscriber
def debug(*args, &block)
end
end
end
I used this: config.log_level = :info
edit-in config/environments/performance.rb
Working great for me, rejecting SQL output, and show only rendering and important info.
In Rails 3.2 I'm doing something like this in config/environment/development.rb:
module MyApp
class Application < Rails::Application
console do
ActiveRecord::Base.logger = Logger.new( Rails.root.join("log", "development.log") )
end
end
end
Just as an FYI, in Rails 2 you can do
ActiveRecord::Base.silence { <code you don't want to log goes here> }
Obviously the curly braces could be replaced with a do end block if you wanted.
I use activerecord 6.0.3.3 and I had to include ActiveSupport::LoggerSilence
include ActiveSupport::LoggerSilence
ActiveSupport::LoggerSilence.silence do
## everything you want to silence
end
This however did not work with anything related to creating or deleting SQL tables like ActiveRecord::Migration.drop_table. For this to be silenced I added:
ActiveRecord::Schema.verbose = false
I had to solve this for ActiveRecord 6, and I based my answer on fakeleft's response, but it wasn't quite right, since it was suppressing other logging such as the logging of nested views. What I did was created config/initializers/activerecord_logger.rb:
# Suppress SQL statement logging if necessary
# This is a dirty, dirty trick, but it works:
if ENV["ACTIVERECORD_HIDE_SQL"].present?
module ActiveRecord
class LogSubscriber
def sql(event)
end
end
end
end
The log subscriber in AR 6 has a sql event that we want to hide, so this is very narrowly targeted to skip that event.