FactoryGirl specifying create_list count for deeply nested association - factory-bot

The following is the association set-up:
Categories has many Chapters
Chapters has many Pages
Pages have one Text or Audio
I would like to generate records at the Category level and to specify the number of Chapters, Pages, and an optional Text or Audio record.
I am aware of using create_list to specify the first level of associations (Chapters). Is there any way to specify the count for the deeper associations? Initial thought tells me to nest the after(:create). Would that be the correct approach?
FactoryGirl.define do
factory :category do
ignore do
chapters_count 1
end
trait :with_chapters do
after(:create) do |instance, evaluator|
create_list :chapter, evaluator.chapters_count, category: instance
end
end
trait :with_chapters_and_pages do
...?
end
trait :with_chapters_and_pages_with_text do
...?
end
trait :with_chapters_and_pages_with_audio do
...?
end
end
end

After playing around with FactoryGirl and reading the syntax/methods.rb code, I came up with a working solution. If others have a better approach, please share :)
# category_factory.rb
FactoryGirl.define do
# Fields
# --------------------------------------------------
(...)
# Transient Attributes
# --------------------------------------------------
ignore do
chapter_count 1 # Number of associated Chapters to create
page_count 1 # Number of Chapter associated Pages to create
page_type :with_text # Page type either :with_text (default) or :with_audio
end
# Traits
# --------------------------------------------------
trait :with_chapter do
after(:create) do |instance, evaluator|
create_list :chapter, evaluator.chapter_count, category: instance
end
end
trait :with_chapter_and_page do
after(:create) do |instance, evaluator|
create_list :chapter, evaluator.chapter_count, :with_page, category: instance, page_count: evaluator.page_count, page_type: evaluator.page_type
end
end
end
# chapter_factory.rb
FactoryGirl.define do
# Fields
# --------------------------------------------------
(...)
category
# Transient Attributes
# --------------------------------------------------
ignore do
page_count 1 # Number of Chapter associated pages to create
page_type :with_text # Page type either :with_text (default) or :with_audio
end
# Traits
# --------------------------------------------------
trait :with_page do
after(:create) do |instance, evaluator|
create_list :page, evaluator.page_count, evaluator.page_type, chapter: instance
end
end
end
# page_factory.rb
FactoryGirl.define do
# Fields
# --------------------------------------------------
(...)
chapter
# Traits
# --------------------------------------------------
trait :with_text do
after(:create) do |instance, evaluator|
create :text, page: instance
end
end
trait :with_audio do
after(:create) do |instance, evaluator|
create :audio, page: instance
end
end
end
# text_factory.rb & audio_factory.rb
(...)
There is some repeat with the transient attributes. Not sure how to DRY it up...

Related

How do I make a macro for a struct generate a function method matching on the struct?

Pardon any confused terminology in the title, but imagine I want to have a little macro to mark structs I create as usable for some nefarious purpose. I write this little module:
module Usable
export #usable, isusable
isusable(::Type{T}) where T = false
macro usable(expr::Expr)
name = expr.args[2]
return quote
$expr
Usable.isusable(::Type{$name}) = true # This in't working
end
end
end
However, trying to use my macro
julia> include("Usable.jl")
Main.Usable
julia> using Main.Usable
julia> #usable struct Foo
bar::String
end
results in
ERROR: UndefVarError: Foo not defined
The struct was evidently defined just fine
julia> Foo("soup")
Foo("soup")
so it would seem the definition is required earlier than I expect. I am obviously missing something, but I can't figure out what.
Always have a look at the output of your macros:
julia> #macroexpand #usable struct Foo
bar::String
end
quote
#= REPL[1]:11 =#
struct Foo
#= REPL[4]:2 =#
bar::Main.Usable.String
end
#= REPL[1]:13 =#
(Main.Usable.Usable).isusable(::Main.Usable.Type{Main.Usable.Foo}) = begin
#= REPL[1]:13 =#
true
end
end
The problem is that the macro output is expanded within the module it has been defined, which messes up what the names mean. In this case, we want Foo to refer to the namespace where it was defined, though:
quote
#= REPL[1]:11 =#
struct Foo
#= REPL[10]:2 =#
bar::String
end
#= REPL[1]:13 =#
Usable.isusable(::Type{Foo}) = begin
#= REPL[1]:13 =#
true
end
end
It's actually simple to get that -- just escape the output:
macro usable(expr::Expr)
name = expr.args[2]
return esc(quote
$expr
$Usable.isusable(::Type{$name}) = true
end)
end
But do read the macro documentation again. esc is quite intricate, and you don't want to blindly apply it to everything you write.
The other thing (and I hope to get this right) is to splice in the module itself -- $Usable -- instead of referring to it by name. Otherwise you might have a problem if you rename the module name outside.
In the described scenario almost always you should use Julia's powerful type system and multiple dispatch mechanism not a macro. (Perhaps you have a good reason to do so but this is information for others.)
The pattern is to simply define the desired behavior through an abstract type that is later inherited by a custom struct.
One example is here for adding a Comparable behavior to composite structs.
abstract type Comparable end
import Base.==
==(a::T, b::T) where T <: Comparable =
getfield.(Ref(a),fieldnames(T)) == getfield.(Ref(b),fieldnames(T))
And now using:
julia> struct MyStruct <: Comparable
f::Vector{String}
end;
julia> MyStruct(["hello"]) == MyStruct(["hello"])
true

how to change value in map.continer child class

i have this superClass
classdef SysSignal<handle
%SIGNALP Summary of this class goes here
% Detailed explanation goes here
properties (SetAccess = public, GetAccess = public )
SetOfSignal = containers.Map('KeyType','char', 'ValueType', 'any')
end
methods
%constructor
function sys=SysSignal()
end
function sys=adds(sys,varargin)
%convert form cell to signalup
for n=1:length(varargin)
signal=varargin{1,n};
if isempty(signal.name)
continue
else
%add to the map
sys.SetOfSignal(signal.name)= signal;
end
end
end
and that child class
classdef Signalp<SysSignal
%SIGNALP Summary of this class goes here
% Detailed explanation goes here
properties
name
lineNo
color='red'
sumrole
end
properties (SetAccess=private,GetAccess=public)
arrPrice
LineLength
LineProportion
end
methods
%constructor
function sig=Signalp(varargin)
end
end
end
now i want to change the properties like this:
sys=SysSignal;
s=Signalp;
s.name='dd';
sys.adds(s)
sys.SetOfSignal('dd').sumrole='hello'
but i get that error :
??? Error using ==> subsasgn
Only one level of indexing is supported by a containers.Map.
how can i change the values from the superClass?
The answer is to seperate sys.SetOfSignal('dd').sumrole='hello' into two statements:
dd = sys.SetOfSignal('dd');
dd.sumrole='hello'
That is because sys.SetOfSignal('dd').sumrole doesn't do what you'd intuitively think it does. Indeed the containers.Map class overloads the subscripted reference and subscripted assignment operators. In essence they 'intercepts' the (), {} and . operators at the same time (ie .sumrole is 'sent' to the map.Containers object - instead of your object!). In my opinion that's a really weird design decision but we must live with it!
Have a look at these links for more info:
subsref, subsasgn
gregswiss's answer is correct, except that accessing the containers map creates a copy, so you finish by replacing the old value with the new modified one
dd = sys.SetOfSignal('dd');
dd.sumrole='hello'
sys.SetOfSignal('dd') = dd;

Matlab - Combining enumeration classes with non-static methods

I am attempting to combine an enumeration class with non-static methods in Matlab. I wish to create a 'LogEvent' class, which has the function 'log' which takes as an input argument an enumeration member (such as LogEvent.INFO, LogEvent.ERROR, or LogEvent.WARNING) and a string, for the purpose of appending this message in a file. I wish to use this LogEvent class repeatedly, for different programs, and as such the class has the property 'fileName' which is specified during construction and refers to the output file. Below is the code forming my classdef file:
classdef LogEvent
%Class definition for logging events.
properties
fileName;
end
methods
function obj = LogEvent(outFile)
obj.fileName = outFile;
end
function log(obj,type,logStr)
switch (type)
case LogEvent.INFO
typeStr = 'INFO';
case LogEvent.WARNING
typeStr = 'WARNING';
case LogEvent.ERROR
typeStr = 'ERROR';
end
FID = fopen(obj.fileName,'a');
Str = sprintf('%s - %s: %s\n',datestr(now),typeStr,logStr);
fprintf(FID,Str);
fclose(FID);
end
end
enumeration
INFO,
WARNING,
ERROR
end
end
Now admittedly I don't have a lot of experience programming so I may be approaching this the completely wrong way, though I have tried googling this problem but with little result - I may not know some particular keywords which would 'hit the nail on the head'. It is my belief though because multiple instances of this class need to be created (to refer to different files), the 'log' function needs to be non-static. I get this error message attempting to create an instance of this class though:
Error using LogEvent
While creating an instance of class 'LogEvent':
No value has been provided for the enumeration member named 'INFO'. For an
enumeration derived from a built-in class, a value must be provided for each
enumeration member.
Error in ZOHB (line 10)
obj.Log = LogEvent('ZOHB.log');
Inside the 'ZOHB' class, I attempt to create an instance of the LogEvent class, and assign it as a property of the ZOHB class.
In Matlab's enumeration scheme, the enumerated values must be instances of the class containing the enum. So e.g. WARNING would have to a certain LogEventinstance.
E.g. like in this example from the docs:
classdef Bearing < uint32
enumeration
North (0)
East (90)
South (180)
West (270)
end
end
Which means in your case, you'd have to specify arguments which would fit your LogEvent-constructor - this is what the error message says, basically.
Which is of course totally nonsense in your use-case.
In your special case, you'd better make ERROR, WARNING and INFO constant properties:
properties (Constant)
INFO = 1;
WARNING = 2;
ERROR = 3;
end
You can access constants in a static manner, so your remaining code should pretty much work with this version.

In MATLAB, is it possible to check if an object already exists before creating a new one?

I'm trying to figure out how to ask the user whether they want to replace the previous object of the same class with the default object, or simply use the previous object, when calling the constructor.
I'm looking for actions in both these cases:
>>obj = Obj()
'obj' already exists. Replace it with default? (y/n): y
%clear obj and call default constructor to create new obj
>>obj = Obj()
'obj' already exists. Replace it with default? (y/n): n
%cancel call of Obj()
How would I do this? I've messed around with the default constructor, to no avail.
EDIT: If it makes any difference, Obj is a subclass of Handle.
The following solution stems from several workarounds/hacks and is not part of the standard MATLAB's OO constructs. Use with caution.
You need to:
evalin() into the 'caller' workspace the names and classes of the 'base' workpsace variables
retrieve the last executed command
extract the name of the assigned variable with e.g. regexp()
compare names and classes. If a total match occurs, i.e. the variable in the 'base' workspace is being overwritten with a new instance of the same class, ask the user for input(). If the user chooses to preserve the existing object, overwrite the new instance with the existing one through evalin('caller',...).
The class foo:
classdef foo < handle
properties
check = true;
end
methods
function obj = foo()
% variable names and sizes from base workspace
ws = evalin('base','whos');
% Last executed command from window
fid = fopen([prefdir,'\history.m'],'rt');
while ~feof(fid)
lastline = fgetl(fid);
end
fclose(fid);
% Compare names and classes
outname = regexp(lastline,'\<[a-zA-Z]\w*(?=.*?=)','match','once');
if isempty(outname); outname = 'ans'; end
% Check if variables in the workspace have same name
idx = strcmp({ws.name}, outname);
% Ask questions
if any(idx) && strcmp(ws(idx).class, 'foo')
s = input(sprintf(['''%s'' already exists. '...
'Replace it with default? (y/n): '],outname),'s');
% Overwrite new instance with existing one to preserve it
if strcmpi(s,'n')
obj = evalin('caller',outname);
end
end
end
end
end
Class in action:
% create class and change a property from default (true) to false
clear b
b = foo
b =
foo with properties:
check: 1
b.check = false
b =
foo with properties:
check: 0
% Avoid overwriting
b = foo
'b' already exists. Replace it with default? (y/n): n
b
b =
foo with properties:
check: 0
The weaknesses (see points above):
applies only to cmw line and script executed commands, not functions (see link to extend to function calls). Also, might break in case of problems reading history.m.
the current regex fails on a==b.
Dangerous because the evalin() on user input leaves potential security threats open. Even if the input is filtered with the regexp and the string comparison, the construct might pose a problem if the code is revisited later on.
Singleton
try this, not sure if you are familiar with it, but this mean, you only have one global instance of this specific object.
You could use the function isobject() (see doc here) to check if the variable is an object. If true, you could then check the class of the object with class() (see doc here) and compare it to the class of the object you want to build. Something like (just to give you an idea):
if isobject(obj)
if class(obj) == myclass
% ask to replace it or not
else
% create new one over the object of a different class
end
else
% create new one
end
If I understand your question correctly, you probably want to put this as a constructor function for your class. I think you will have to pass the variable name when calling the constructor: obj = Obj(obj).

Passing objects to other objects in MATLAB

I'm starting out with object-oriented programming in MATLAB, and I'm confused on how to best pass objects to other objects, as MATLAB doesn't feature static type definitions.
I have three different classes, all of which include some constants. Now, I want to use the constants defined in two of the classes in the methods of the third class - how should I do this? The classes are in no hierarchy.
So, I'm looking for something like #include in C++.
Problem illustrated below. How to write "*Object1" and "*Object2" references to access const1 and const2?
classdef Object1
properties (Constant)
const1 = 100;
end
methods
function Obj1 = Object1()
end
end
classdef Object2
properties (Constant)
const2 = 200;
end
methods
function Obj2 = Object2()
end
end
classdef Object3
properties (Immutable)
property3
end
methods
function Obj3 = Object3()
Obj3.property3 = *Object1.const1 + *Object2.const2;
end
end
Just remove the asterisks, and I think you have what you need.
There are a couple of other syntax errors in your code (replace Immutable with SetAccess = immutable, and add missing ends to the classdefs), but once I made those changes, I get:
a = Object3
a =
Object3
Properties:
property3: 300
Methods
In general, to reference a Constant property from another class, just prefix the property with the class name (and possibly package name, if the classes are in a package).