How could I enforce all keys in a struct without need to duplicate all the keys? To clarify, I'd like to DRY this:
defmodule Ticket do
#enforce_keys [:origin, :destination, :price]
defstruct [:origin, :destination, :price]
end
I can use additional variable:
defmodule Ticket do
struct_keys = [:origin, :destination, :price]
#enforce_keys struct_keys
defstruct struct_keys
end
It works properly, but looks noisy. Is there any better approach?
You can pass #enforce_keys to defstruct since #enforce_keys is just a normal module attribute:
defmodule Ticket do
#enforce_keys [:origin, :destination, :price]
defstruct #enforce_keys
end
iex(1)> defmodule Ticket do
...(1)> #enforce_keys [:origin, :destination, :price]
...(1)> defstruct #enforce_keys
...(1)> end
iex(2)> %Ticket{}
** (ArgumentError) the following keys must also be given when building struct Ticket: [:origin, :destination, :price]
expanding struct: Ticket.__struct__/1
iex:2: (file)
Related
In search of a fast stripe mock that didn't require an extra server to be running and produced realistic enough responses to use, I built the following class which works, but which is missing some functionality of the MagicMock, things like assertCalledWith, assertCalledOnce
I'm using this Mock class like so:
#mock.patch('stripe.Subscription', new_callable=StripeMockSubscription)
def test_something(self, _): #because the mock doesn't behaving like a MagicMock
But I would prefer to use it like:
#mock.patch('stripe.Subscription', new_callable=StripeMockSubscription)
def test_something(self, subscription_mock: mock.MagicMock):
but for that to work I need to figure out how to give the mock class MagicMock powers to track calls, parameters, etc... so that I get the side effects AND I get the MagicMock behavior.
It is possible to just mock out each method but then you end up with a stack of mocks on the top of the test that borders on unreadable. (Yes, I know you can put them on the test class, but you still have to enumerate all the arguments and if you don't need a mock on a specific test then you have unused parameters. (thanks for nothing pylint))
TLDR;
This is one of several classes for mocking out a portion of the stripe API, How do I turn this into something that also has the behavior of MagicMock on each of the methods? ie
subscription_mock.retrieve.assertCalledOnce()
class StripeMockSubscription(real_stripe.Subscription):
""" Mimics stripe.Subscription.* sort of. The #classmethod is from the actual stripe
API library """
#classmethod
def retrieve(cls, id: str, api_key=None, **kwargs):
subscription = Subscription.objects.for_stripe_test_fixtures().get(stripe_id=id)
context = {
'sub': subscription,
'schedule': 'null',
}
schedule = Schedule.objects.filter(subscription=subscription).first()
if schedule:
context['schedule'] = mark_safe(f"\"{schedule.schedule['id']}\"")
if subscription.paid_until:
context.update({
'start_date': subscription.paid_until-timedelta(days=subscription.price.period_in_days),
'end_date': subscription.paid_until,
})
else:
context.update({
'start_date': timezone.now(),
'end_date': timezone.now()+timedelta(days=subscription.price.period_in_days),
})
sub = load_template_fixture('subscription.json', context=context)
sub['url'] = 'https://stripe.com/goto-here/'
# self['updated'] = 0
return real_stripe.Subscription.construct_from(values=sub, key='FakeAPIKey')
#classmethod
def modify(cls, sid, **params):
""" TODO: This could be better, for right now we just fake it until we make it! """
return StripeModifyResponse()(sid, **params)
#classmethod
def create(cls, sid, **params):
""" TODO: This could be better, for right now we just fake it until we make it! """
return StripeCreateResponse()(sid, **params)
Let's say I have five classes represented like this:
class Operator:
def __init__(self):
pass
class Pow(Operator):
pass
class Mul(Operator):
pass
class Div(Operator):
pass
class Add(Operator):
pass
class Sub(Operator):
pass
And an array of tokens some of them are numbers and the others are objects instantiated from the classes above {Pow, Mul, Div, Add, Sub}.
When I loop through the array, I want to be able to check for the type of token like this:
for token in tokens:
if type(token) is Operator:
# do something
Instead of doing this:
for token in tokens:
if type(token) in [Pow, Mul, Div, Add, Sub]:
# do something
I don't know if there is a good alternative for the second for loop.
(Any code refactoring is welcomed)
Use method isinstance(token, Operator)
here is example
ar = Div()
if isinstance(ar, Operator):
print("true")
for token in tokens:
if isinstance(token, Operator):
# do something
I'm using ActiveAdmin gem together with Pundit (and Rolify) gem.
This is how I wrote my policy (taken from: https://github.com/activeadmin/activeadmin/blob/master/spec/support/templates/policies/application_policy.rb):
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def show?
scope.where(id: record.id).exists?
end
def create?
user.has_role?(:staff, record.company)
end
def update?
scope.where(id: record.id).exists?
end
def destroy?
scope.where(id: record.id).exists?
end
def destroy_all?
true
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
company_ids = Company.with_role(:staff, user).map(&:id)
scope.where(company_id: company_ids)
end
end
end
end
This causes N+1 query each time scope.where(id: record.id).exists?. On index page, show?, update? and destroy? are called for each record in the table.
How can I avoid the N+1 query in this case?
I'm trying to:
1) Include/preload roles together with user for calls to current_user
2) I'm trying to memoize the scope or use some array method to prevent hitting the db with where and exists? methods. But scope.find still makes the db query for every new row.
Thanks!
first of all, I suggest adding a method to the User object to return company_ids where it is staff helps.
class User #or AdminUser right?
def company_ids
#company_ids ||= Company.with_role(:staff, self).map(&:id)
end
end
than you can change
def destroy?
scope.where(id: record.id).exists?
end
to
def destroy?
return true user.admin?
user.company_ids.include?(record.company_id)
end
and resolve method for Scope now looks this way
def resolve
if user.admin?
scope.all
else
scope.where(company_id: user.company_ids)
end
end
end
I have three identical methods in my module, that do (almost) exactly the same thing. Instead of repeating the function definitions, I am trying to define them once to keep code minimal and same for all of them.
So far I've tried using Code.eval_string:
defmodule MyModule do
Enum.each ~w(method1 method2 method3), fn method ->
#method method
Code.eval_string """
def #{#method}(map) when is_map(map) do
do_map_stuff(:#{#method}, map)
end
def #{#method}(arg) do
do_other_stuff(:#{#method}, arg)
end
"""
end
## Other Methods
end
but this throws ArgumentError:
Compiling 1 file (.ex)
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:4297: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3299: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
nofile:1: (file)
I think quote/unquote might be the way to go, but I'm not exactly sure how to do this using them (I've already read the Meta Guide on the Elixir website).
Something like this?
defmodule MyModule do
def do_map_stuff(method, arg) do
IO.inspect([method, arg])
end
Enum.each [:method1, :method2, :method3], fn method ->
def unquote(method)(map) when is_map(map) do
do_map_stuff(unquote(method), map)
end
def unquote(method)(arg) do
do_map_stuff(unquote(method), arg)
end
end
end
Let's say I have a modules Silent and Definer. I want to define a couple of functions for Silent, based on its attribute. Let me explain:
defmodule Silent do
#function_names [:a, :b, :c]
use Definer
end
defmodule Definer do
defmacro __using__(_) do
quote do
Enum.each(#function_names, fn(n) ->
def unquote(n)() do # line 5
IO.puts "a new method is here!"
end
end)
end
end
end
But this approach actually doesn't work because I have undefined function n/0 on line 5. How can I implement desired functionality?
You need to pass unquote: false to quote in Definer.__using__/1 to be able to inject an unquote fragment inside a quote.
defmodule Definer do
defmacro __using__(_) do
quote unquote: false do
Enum.each(#function_names, fn(n) ->
def unquote(n)() do # line 5
IO.puts "a new method is here!"
end
end)
end
end
end
defmodule Silent do
#function_names [:a, :b, :c]
use Definer
end
Silent.a
Silent.b
Silent.c
prints
a new method is here!
a new method is here!
a new method is here!
A similar case is documented in detail in the Kernel.SpecialForms.quote/2 docs which also mentions how to use bind_quoted if you want to both inject some variables into a quote and create unquote fragments.