<dkubb>
I would assume something needs the tuple and will return a new tuple with the value filled in
<dkubb>
since it can't modify the tuple
<snusnu>
right, that's more or less exactly what i've assumed too
<dkubb>
we don't really have many tuple-valued functions.. meaning functions which return a tuple
<dkubb>
I need to actually add those for update to work properly
<dkubb>
right now update works by using a proc and returning a tuple, but you can't introspect that and build things like SQL UPDATE statements
<snusnu>
ah ok, i get it .. well yeah, sounds important ;)
<dkubb>
it's not particularly difficult
<dkubb>
it's just in my queue
<snusnu>
sweet
<dkubb>
it should make ROM implementation easier
<snusnu>
i assume such a function (speaking of key generation again) would live within a relation (and for more complex cases, would "live on through joins etc")
<dkubb>
since you won't have to insert into multiple relations. you can just join them and insert a single tuple
<snusnu>
dunno if that makes any sense (my last statement)
<dkubb>
yeah, it would probably be an attribute that knows what to do
<dkubb>
or more like a function that you add via extend
<snusnu>
yeah, so, not trying to be greedy … if we prepare rom-relation to have such an attribute, do you think we can get reasonably close to the api it will have? just askin' cause you already "agreed" that "something will need to accept a tuple and return a new tuple"
<snusnu>
we can always change api of course
<snusnu>
but again, i don't wanna be greedy
<snusnu>
:)
<snusnu>
otoh, i guess it really doesn't make sense
<snusnu>
given the more complex handling of RA ops you mentioned, where the function gets "altered"/"built fresh" for results of ra ops
<snusnu>
ok, i give up for now
<snusnu>
heh
<snusnu>
a simple solution would probably never be able to insert like the real thing could, it'd probably only ever work for base relation inserts i guess
<snusnu>
maybe you know what i mean, all inserts are eventually propagated down to base relations … hmmm … well …. nevermind .. i should just keep on doing what i did before i was asking that question :)
<snusnu>
which was killing mutants, heh
<dkubb>
hmm
<dkubb>
you could try, but *I* don't have the information to do it well yet. I don't know if you'd have better information than I do about RA and how this should all work
<dkubb>
I suspect it'll take experimentation and a few dead ends before I'll know how it's going to work
<dkubb>
to get even reasonably close I would have to be involved directly, at which point I might as well do it
<dkubb>
in the meantime I'd take 5 minutes and setup objects to set a default PK using simple_uuid so you can move onto other parts of ROM implementation
<dkubb>
:P
<dkubb>
I had not planned to do any key generation stuff in axiom for at least a few months. there's too many other things in front of it in the queue
<snusnu>
dkubb: i absolutely agree, and we both know that by no means i have better information about RA ;) it's just since i had that thought, i needed a bit more info wether it was feasible at that point or not, i have that info now, thx for that :)
<snusnu>
dkubb: it's funny tho, how seemingly "simple" stuff unfolds to be pretty complex once you get a little hint (like the one you just gave me)
<dkubb>
it'll probably something I can implement without *too* much difficulty
<dkubb>
most of the time the complexity comes from figuring out all the use cases
<dkubb>
most of the stuff in RA, even the harder stuff, was usually built up from very simple rules which are easy to test and understand
<dkubb>
it just takes lots of experimentation before you know if it's the right approach
<snusnu>
yeah, understanding the mathematics behind it, and wrapping those up in a nice ruby oo api with nice internals, is quite a different beast i can imagine :) and i can only say that i'm already having a really hard time with the mathematics ;)
<dkubb>
it's not that bad, it just takes time
<snusnu>
i don't even want to remember getting in touch with RA at uni for the first time .. back then, my brain was not ready for that kind of stuff *at all* .. i heard about computers like 2 years before, used them only for one tho :p
<snusnu>
never liked mathematics
<snusnu>
heh
<dkubb>
hehe
<snusnu>
i got my first pc 3 months before i started to study business informatics, heh .. i mostly started that because i was afraid of the maths in "real" informatics, and because i didn't know how to use that new pc i got … i actually got that one only because our english teacher said she won't correct handwritten homework anymore
<snusnu>
hehe
<dkubb>
you'll get it when you start to compose queries with it
<dkubb>
there's a lot of similarities between it and SQL, only it has less edge cases
<snusnu>
yeah, i already notice how it's much easier to grasp it now, that my brain is used to programming .. math sometimes doesn't seem that hard anymore
<dkubb>
when we get the new SQL generator going I'll add a #to_sql method to Axiom::Relation so you can see what it'll do
<snusnu>
sweet
<dkubb>
same with something for graphviz
<snusnu>
yeah, i wrote a little bit of graphviz for dm-mapper, helped me a lot during initial implementation of the graph
<dkubb>
me too
<dkubb>
I used it to understand the optimizer
<snusnu>
so you compared original trees with the collapsed ones, visually i mean?
<snusnu>
s/collapsed/pruned/ or whatever is the appropriate word
<snusnu>
optimized, heh
jfredett has joined #rom-rb
<snusnu>
hah nice, just introduced a new feature to substation, and found a bug in the integration specs cause of it
cored has quit [Ping timeout: 240 seconds]
<dkubb>
snusnu: yeah, I viewed the original against the optimized tree
<dkubb>
snusnu: some optimizations were easier to identify visually
<dkubb>
hmm, there's one side effect of the new mutant stuff which isn't so good
<dkubb>
when run against Axiom with the axiom-types integration it tries to mutate Axiom::Types*, but because there are no specs for them it is running the entire axiom spec suite
<snusnu>
dkubb: sounds like it needs an exclude option?
<snusnu>
dkubb: also, have you ever encountered something like this in reek:
<snusnu>
mungo:substation snusnu$ be rake metrics:reek
<postmodern>
hey i want to help out with ROM, but don't know where to start?
<snusnu>
i copied the exact output reek gave me to its config, and i get that error .. and yes, i tried retyping it myself, still the same
<snusnu>
postmodern: hm, want to dive into rom directly or help with the tooling?
<postmodern>
snusnu, probably work on rom
<postmodern>
snusnu, i did contribute a little to mutant
<postmodern>
snusnu, dev-tools kind of scares me though ;)
<dkubb>
postmodern: the code scares you or how strict it is?
<snusnu>
postmodern: hehe … well tbh, atm solnic might know best where help is needed *now* …
<postmodern>
dkubb, well i tend to prefer having all my dev tasks defined in the Rakefile, in case i need to customize something
<postmodern>
snusnu, yeah i was looking at rom-mapper and noticed some overly complex code, but it didn't have specs so i couldn't refactor
<snusnu>
postmodern: there's plenty of other stuff that needs help tho, like, sql generation, the axiom-types refactor
<snusnu>
postmodern: ok, they should have specs by now, given solnic says it's 100% mutation covered :)
<postmodern>
re-pulling
<postmodern>
snusnu, so what are the missing pieces so far?
<postmodern>
i remember veritas-sql-generator could generate all read-only queries
<postmodern>
still need inserts and migrations?
<snusnu>
right
<snusnu>
"but" there's a new project in town, dkubb/sql .. which will be responsible for generating the sql eventually
<postmodern>
ha
<postmodern>
nice
<snusnu>
migrations is definitely something we need, but probably they will be done completely different from what we had before, also built on top of RA ops
<snusnu>
we also still need a unit of work for the session, which will be capable of persisting objects in the correct order, given its dependencies .. but that leads to another open field, and that#s relationships, strictly speaking we don't need relationships, but we need the concept of FKs .. (not only) so that we can infer correct dependencies inside the uow
<snusnu>
and we need something that can handle server generated values (keys) .. but that's probably a job for dkubb in axiom, or at least it needs heavy dkubb guidance ;)
<postmodern>
dkubb, feel free to crib from ronin-sql
<snusnu>
the initial alpha will mostly be concerned with being able to map readonly data to objects, and "persisting" them via the session .. the session will not be able to resolve deps at this point .. and by persisting, we're talking about the axiom-memory-adapter
<snusnu>
dkubb: i "fixed" reek by excluding: - /Substation::DSL::Registry#\[\]=/
<snusnu>
postmodern: to make myself more clear, the initial alpha will also be able to do writes with the memory adapter, but it obviously won't persist anything, and it will not be able to use any server generated keys, i.e. the memory adapter doesn't create keys for you, you have to pass them yourself
<postmodern>
snusnu, so you'd have to reload the objects from the memory store?
<postmodern>
snusnu, oh i see
<postmodern>
snusnu, how hard would it be to rig up a Serial type, and have it increment an internal counter in the adapter?
<dkubb>
we'd have all these issues even if we didn't have insert propagation, only they'd be handled in the ROM layer
<dkubb>
I had to deal with all this stuff in DM1 a while ago too
<snusnu>
postmodern: what also comes to mind, is that we need a way to serialize constraints to SQL .. it's probably related to axiom-types, but i bet dkubb can give you more hints
<snusnu>
btw note that i'm not listing these things ordered by priority, just as they come to my mind
<postmodern>
snusnu, i would think serializing constraints is similar to your idea of relational options
<postmodern>
snusnu, that way you could diff the schema, and undo constraints if necessary
<dkubb>
postmodern: how I was thinking about migrations is a bit different from how they typically get done
<snusnu>
dkubb: say you have def coerce_name(name); name.to_sym; end .. would you bother to move that inside a class method, as reek suggested? i'm having a hard time justifying the additional public method that would mean (i'd keep the instance method for not having to repeat self.class.)
<postmodern>
dkubb, being able to invert them would be key
<dkubb>
the typical way is to define a DSL that mirrors DDL statements or specific tasks
<dkubb>
the way I was thinking about it is you start off with a relation either inferred from the schema (if it exists) or you start off with "TABLE DUM", which in RA is a relation with no attributes and no rows. you then apply normal relational operations to describe how you want to transform the data
<dkubb>
the act of performing a migration and querying is actually the same thing. you're specifying a transformation of one relation into another
<dkubb>
RA provides operations to do all the possible transformations, whether it's adding an attribute, removing, adding a CHECK constraint (through restrict), and so on
<postmodern>
what about more SQL specific things, like type changes?
<dkubb>
well, most of the possible transformations.. I won't say it's absolutely perfect, but it's really close
<dkubb>
that's a good question. we could add operators that are effective shorthand for extend/project/rename that "replace" an attribute
<postmodern>
dkubb, also would be cool to think of ways we could apply the migrations in memory
<postmodern>
dkubb, in case the underlying adapter doesn't support them (cough sqlite)
<postmodern>
dkubb, which would fit nicely with your relation manipulation idea
<snusnu>
postmodern: good point you have there in that issue
<postmodern>
snusnu, test isolation
<snusnu>
i'm a fan of that yeah, even tho it sometimes means a bit more maintenance burden
<snusnu>
altho i suspect that this is probably mostly true during initial development, and things start to settle down once the code gets more and more stable
<postmodern>
snusnu, although you can cheat, by only testing behaviors once in the semipublic API, and only focus on testing the code within the public API methods that build ontop of the semipublic API
<snusnu>
yeah, with rom we kinda agreed tho, that we only make a distinction between public and private api .. semipublic (in dm1 speak, the api plugin developers use) was kinda hard to define .. so we settled with just public/private
<postmodern>
then i would move the semipublic api into the public one
<postmodern>
make AttributeSet public
<postmodern>
since developers will likely use it for introspection
<snusnu>
postmodern: yeah, we were talking about that briefly, and both suspected that it will eventually be public api, and then it would also get dedicated specs, at least for all the @api public methods in it
<snusnu>
postmodern: most probably solnic would accept a PR for that, i'm pretty sure it's safe to assume that it will be public api anyway
snusnu has quit [Quit: Leaving.]
<dkubb>
postmodern: yeah, I would guess a set not null could be inferred from: relation.restrict { |r| r[:name].ne(nil) }
<dkubb>
postmodern: also it would be relatively easy to do the in-memory idea you thought about
<dkubb>
postmodern: what we'd do is walk the ast, derive as much information as we could from it to form the DDL statements, and then rewrite the ast so that the extra restrictions are applied on top of the base relations
<postmodern>
dkubb, ah that's how
<postmodern>
dkubb, i was envisioning a much less efficient way, of just executing the unapplied migrations on every result set
<dkubb>
constraints are really important, so I'm totally willing to make adaptions too if it turns out we need to tweak some things
<postmodern>
dkubb, ah and filter out NULL values if we can't apply SET NOT NULL?
<postmodern>
dkubb, or modify the query to filter out NULL values, since we specified that column shouldn't be NULL
<dkubb>
postmodern: exactly. we can effectively create something like a view
<dkubb>
except it's writable
<postmodern>
dkubb, in fact you could save this query into some sort of "base query" for the model
<postmodern>
dkubb, so that all other queries are composed with this base query
<dkubb>
the cool thing about axiom is if you do: restriction = relation.restrict { |r| r[:name].ne(nil) }; restrict.insert(...) you can't insert something that does not comply with the constraint
<dkubb>
postmodern: yeah, we have something like that in the memory adapter right now, but I planned to make it the same api across all adapters. under the hookd it's just a simple Hash mapping a name to the relation. the relation can be anything really
<dkubb>
postmodern: ROM::Relation introduces a kind of schema object which allows you to have named relations from different adapters. we were talking about pushing that down to the Axiom layer so that independent of ROM we can describe a "schema" that could contain relations from multiple data stores
<dkubb>
from the pov of the user it works like a Hash
<postmodern>
hmm interesting
<postmodern>
not sure if Relational Algebra has the concept of a schema
<postmodern>
but might be useful for restricting relational algebra statements to a set of columns
<dkubb>
afaik there is a schema conctept. at least it's in all the books I've read about it
<dkubb>
it's more like you have a set of named base relations, with relationships declared between them. the database has constraints that span one or more relations, and the relations themselves have constraints that span one or more attributes. the attributes themselves are typed and have thier own constraints and representations
<postmodern>
it's been a while, but they only mention the schema to help us evaluate the expressions against some data
<dkubb>
that's roughly what I was going for, with a more ruby-ish interface than what Tutorial D or other relational languages provide
<dkubb>
postmodern: you studied math in school didn't you?
<postmodern>
the databases class was required for the CS:BS :)
<dkubb>
heh
<postmodern>
covered a little RA, normalization/schema-design, SQL, write your own app
<dkubb>
I'm not yet sure how to represent index management in RA
<dkubb>
we may need a few small extensions to the base ops I've already implemented
<postmodern>
i almost feel we need a higher-level RA for this
<dkubb>
one idea I had, independently of this work, was if instead of adding an explicit index you'd provide a sample query you wanted to execute and then the system could figure out the optimal indexes for it
<postmodern>
an index is sort of like a rename, because you also change the type
<dkubb>
or you could provide a set of possible queries, and a nice index could be created to optimize for all of them
<postmodern>
idk, you could get away with auto-defining indexes based on the type
<dkubb>
yeah, I suspect we may need a separate object or some dsl for configuring indexes, but I'd love to infer as much as possible from an RA expression
<postmodern>
you'd need some code that profiled your app, and saw which queries took the longest
<dkubb>
indexes are kind of out-of-band from normal query composition
<postmodern>
and defined indexes on the properties with relatively small lengths
<postmodern>
yeah
<postmodern>
well indexes are part of SQL's meta-DDL
<postmodern>
like triggers and such
<dkubb>
the question isn't can we do it, it's can we do it elegantly ;)
<snusnu>
solnic: i find that discussion amusing btw :)
<solnic>
snusnu: reading
<snusnu>
solnic: i realize how my initial comment may've seemed a bit OT, but i was chatting with postmodern while writing things up
<snusnu>
solnic: and boy was it late, heh
jfredett has quit [Quit: Leaving.]
<snusnu>
solnic: reload, i added 2 minor comments
<snusnu>
oh, GH pushes those updates anyway i guess
<solnic>
snusnu: yeah it has auto-reload
<solnic>
snusnu: I think we’re in a total agreement btw
<solnic>
see my comment
<snusnu>
see mine :)
<solnic>
snusnu: crap, I made a significant typo:
<solnic>
"you can't pre-maturely design entire system and assume every public method should be part of public interface."
<solnic>
notice: can’t :P
<solnic>
I hope you read that with “can’t” anyway :D
<solnic>
because the context suggests that
<solnic>
stupid written communication gaaaah
<snusnu>
yeah, never even noticed, read can't :)
jfredett has joined #rom-rb
snusnu has quit [Quit: Leaving.]
snusnu has joined #rom-rb
jfredett has quit [Quit: Leaving.]
cored has joined #rom-rb
<cored>
hello
<solnic>
hi cored
jfredett has joined #rom-rb
<snusnu>
solnic: btw, re our spec topic, once i find the time, i will nail down the public api of substation, introduce bogus, and nuke irrelevant specs
<snusnu>
solnic: it will be a good experience, and i can't wait to look at the diff … i want to do it in a separate PR
<solnic>
snusnu: cool!
<snusnu>
solnic: i'm mostly saying that now to justify for myself that i'm going to write not needed specs *now*
<snusnu>
;)
<solnic>
haha
<snusnu>
solnic: but really, i want to see the diff between —rspec-dm2 and what we have by then
dkubb has joined #rom-rb
<snusnu>
solnic: also, to be fair, i'm not yet entirely sure if the specs i'll write now will end up being public api
<dkubb>
good morning
<snusnu>
good morning dkubb
<snusnu>
dkubb: do you by any chance know if Array#| maintains the order? i.e. if i do ary_1 | ary_2, does it behave like ary_1 + ary_2 in case the intersection was empty?
<dkubb>
snusnu: yeah, afaik the order is maintained. I don't know if it's part of the contract though. I would look it up in rubyspec to see if it's in the specs
<dkubb>
I believe I have relied on the order preserving behaviour too
<snusnu>
cool, saves me from doing a method expectation *and* from asserting the order
<snusnu>
dkubb: btw, that was for implementing your suggestions, re enforcing chains to have disjoint processors
<snusnu>
dkubb: i'll push that in a minute
<snusnu>
dkubb: the implementation is a bit more "dirty" than simply using Array#| because i want to inform the user and thus raise if such an event occurs
<cored>
dkubb: I saw some failing from travis
<dkubb>
cored: yeah, I need to check that. I merged in the axiom-types stuff. I finished it last night
<dkubb>
cored: I think travis got in a weird state. I'm going to push up a small tweak and make sure it can build axiom
<cored>
dkubb: oh, entirely?
<dkubb>
cored: no, something to do with not being able to contact github when it was attempting to build things
<dkubb>
cored: ok, it's starting to build now. hopefully it's without any issues. I had to disable mutant though because there was a bug that caused it to run 100x slower with axiom
<solnic>
dkubb: re axiom+mutant issues, you should narrow down namespaces in mutant config
<solnic>
dkubb: I added support for multiple namespaces recently
<solnic>
in devtools
<dkubb>
solnic: does it allow you to specify a namespace for exclusion?
<solnic>
nope
<solnic>
I knew you’d ask about that ;)
<dkubb>
solnic: I don't want to maintain a list of all the possible namespaces. I'd rather say Axiom - Axiom::Types
<solnic>
maybe mutant runner supports that?
<dkubb>
as I break down axiom more into axiom-logic, axiom-functions, etc I'll need this more
<dkubb>
axiom itself will (hopefully) become more like a meta-gem
<solnic>
btw I will investigate what causes the slow down
<solnic>
my change definitely contributed to that
<dkubb>
solnic: did you revert that change? I can test axiom now again
<solnic>
but I’m not sure if it’s *the* reason
<dkubb>
just to see if it makes a difference
<solnic>
dkubb: no I will do that later today
<solnic>
I was busy with client work today
<dkubb>
no problem
<snusnu>
solnic: btw, dunno if you read the logs, but dkubb and i were talking about the server generated keys, and it's somewhat tricky to make it work across all possible relations, so dkubb said he's probably the best person to do it
<snusnu>
solnic: in the meantime, we should just stick to injecting uuids or whatever manually
<snusnu>
solnic: so no premature api for injecting some factory for now
<dkubb>
I wonder when travis is going to fix ruby-head
<snusnu>
yeah :/
<dkubb>
I don't understand why it's such a problem. I'm guessing they have some serious build issues with their vms
<dkubb>
shouldn't it be a configuration setting in a chef recipe?
<dkubb>
I'm sure I'm simplifying stuff
<solnic>
snusnu: we could handle that on Relation#insert level
<dkubb>
but at the same time, I've done exactly this kind of thing before and it was closer on the trivial scale
<solnic>
or monkey patch axiom-memory-adapter
<solnic>
snusnu: I can try to spike something out today
<snusnu>
solnic: talk to dkubb about it, i did yesterday, and decided to hold off with that, after i heard his arguments
<dkubb>
you can spike out whatever you like, but I probably can't add it properly in a quick way :P
<snusnu>
solnic: better yet, read the logs ;) it was some time around 4-6am CEST ;)
<snusnu>
dkubb: exactly, that's why i'm surprised, must be reek being smart about seeing the raise?
<dkubb>
snusnu: hmm, maybe it thinks raise is a method on the object. as a test, try using Kernel.raise and see what reek does
<dkubb>
snusnu: if that actually does cause a utility function to be found, then I think it's probably a bug in reek
<snusnu>
dkubb: good point, reek still doesn't complain tho
<snusnu>
dkubb: so i guess all is good
<dkubb>
hmm
<dkubb>
I wonder why. it's weird
<dkubb>
maybe submit a ticket to the reek project to ask why it's not identified as a utility method?
<snusnu>
yeah, it does the right thing, but i have no idea why :)
<dkubb>
wait, if you use Kernel.raise then reek *does* identify it as a utility method?
<snusnu>
imo, sometimes, the utility method warnings go a bit too far, i mean, it's fine they warn, but i oftentimes wonder if it's really worth it to add a public class method somewhere
<snusnu>
dkubb: no, it doesn't
<snusnu>
dkubb: it's still happy, both with #raise or Kernel.raise
<dkubb>
snusnu: so yeah, I would probably submit that as a bug
<dkubb>
snusnu: I dunno. I think lots of utility methods is probably a signal of a design problem. I would rather reek tell me so I can consider it
<dkubb>
with that said I think I have too many utility methods in axiom I need to refactor out
<snusnu>
dkubb: yeah, same here, i like how it tells me about it, but sometimes i think i know better
<snusnu>
dkubb: it seems like oftentimes, with utility methods, it's hard to come up with some "generic" object that includes the methods … i don't see a point in concstructing an instance of an object that itself is a collection of weakly related methods … feels no better than thinking of a class as an instance of Class
<dkubb>
snusnu: I see utility methods as a signal that there's some concept I haven't identified yet. I make a singleton method because the work has to be done somewhere, and then I make a point of reviewing it each time I have to touch the class to see if I've thought of a concept it identifies
<dkubb>
snusnu: like for example, in the axiom join operator I have a bunch of stuff thta builds indexes. in working on the group operator I realized I had to make the same kind of index, so I'm in the process of pulling that stuff out into an Index object that I can use in both places
<dkubb>
snusnu: it's ok not to always have the answers. prematurely moving a utility method into an object isn't much better than just accepting the utility method
<snusnu>
dkubb: yeah, agreed … definitely there are cases where new concepts evolve after some time … what about simple coercion methods tho? like def coerce_name(name); name.to_sym; end
<snusnu>
dkubb: ok, i could introduce a Name object, but that'd be overkill i assume
<snusnu>
dkubb: anyways .. it's not *that* important atm :)
<solnic>
snusnu: I care less and less about reek output tbh, I’m starting to rely more on my “senses” these days
<solnic>
snusnu: I also kinda write stuff in oop/functional way so reek can complain a lot
<snusnu>
solnic: usually i do too, but i still like the safety net of reek, reminding me about cases i overlooked
<solnic>
yeah sure
<solnic>
it does a nice job nevertheless
<snusnu>
solnic: and yes, i found that our style sometimes goes against reek's pov
<solnic>
what I meant is that I’m less alarmed when it complains about some things
<snusnu>
yeah, i'd 2nd that, it mostly makes me curious
<solnic>
it’s great at finding for example FeatureEnvy smell
<solnic>
which IMHO is almost always something I want to refactor
<snusnu>
yeah, agreed
<solnic>
also I like its settings for max args count, method count etc
<solnic>
this is always an indication that something mighty be wrong
<snusnu>
one thing i really cannot follow, is flay .. i like it, it usually points out bad dupes, but i honestly am not able to "predict" flay scores
<solnic>
so I’m definitely not against reek
<snusnu>
yeah
<solnic>
yeah haha
<snusnu>
like, look at the change i will push in a sec
<solnic>
recently I realized I’m updating flay and flog thresholds in a totally braindead way
<snusnu>
pls elaborate
<solnic>
I’m just updating them so that my build passes :P
<solnic>
whenever something has a high score
<solnic>
it’s almost always ALWAYS some kind of an exception
<snusnu>
hah ok, yeah, altho it rarely happens with flog ime
<solnic>
like for instance those get/set methods
<solnic>
or I’ve got one build method that’s much more complex than rest of the stuff but I think it’s totally fine cause 1) it’s not really that complex 2) it doesn’t hurt at all
<solnic>
I may extract it into a builder object once it becomes really complex, now it’s not