Rollback all migrations with ragtime - clojure

I'm dealing with some code that assumed that the rollback function below would roll back all migrations. However, it only seems to roll back the latest migration.
(defn create-migrator
[spec]
{:datastore (ragtime.jdbc/sql-database spec)
:migrations (ragtime.jdbc/load-resources "migrations")})
(defn rollback
[env]
(-> (create-db-spec env)
(create-migrator)
(ragtime.repl/rollback)))
How can I alter rollback to roll back all migrations?

Ragtime rollback function accepts multiple options. Among them there is number of migrations to rollback or ID of migration you want to rollback to (amount-or-id).
As ragtime.jdbc/load-resources returns a seq of all migrations sorted by their names (and by convention they will be sorted by their order of application) you can query the first one and get its ID:
(-> (ragtime.jdbc/load-resources "migrations")
(first)
(:id))
If your database is at the latest migrations I guess using count of your migrations seq as amount should also work.
For the given example:
(defn rollback-all
[env]
(let [spec (create-db-spec env)
migrator (create-migrator spec)
count-migrations (-> migrator :migrations count)]
(ragtime.repl/rollback migrator count-migrations)))

Related

How To Dispatch Two Related Events In a Re-frame Application?

I'm working on a game with inventory system. Right now, the characters equipment and inventory are separate values in my DB. My problem, is when a user equips an item I need to both remove the item from the inventory and add it to the character.
I have something like this right now:
(defn equip-item [item idx]
(re-frame/dispatch [:equip-item {:position :off-hand :item item}])
(re-frame/dispatch [:remove-item-from-inventory idx]))
(re-frame/reg-event-db
:equip-item
(fn [db [_ itemObj]]
(update-in db [:character :equipment] merge {(:position itemObj) (:item itemObj)})))
(re-frame/reg-event-db
:remove-item-from-inventory
(fn [db [_ idx]]
(update-in db [:inventory :main] handle-remove idx)))
This works perfectly fine so far, but I'm wondering if there's a better way to handle dispatching multiple events like this? I know there's an ability to create an effect with a :dispatch-n key, but I'm not sure if that's appropriate here.
In any case, I'm also concerned about one event failing with the other succeeding. These should behave sort of like a transaction in that if one fails they both should fail.
The best approach would be to extract those update-in to separate regular functions, create the necessary third event, and call those functions in there via e.g. ->. Of course, you would call those functions in the original events instead of update-in as well.

Should I run checks outside or inside my swap! function?

When I'm going to swap! the value of an atom conditionally, should the condition wrap the swap! or should it be part of the function swap! calls?
(import '(java.time Instant))
(def not-nil? (comp not nil?))
(defonce users (atom {
"example user 1" {:ts (Instant/now)}
"example user 2" {:ts (Instant/now)}
}))
(defn update-ts [id]
(if (not-nil? (get #users id))
(swap! users assoc-in [id :ts] (Instant/now))))
In the above example, I'm doing the existence check for the user before doing the swap!. But couldn't the user be deleted from users after the check but before the swap!? So, then, is it safer to put the check inside the function run by swap!?
(defn update-ts [id]
(swap! users (fn [users]
(if (not-nil? (get users id))
(assoc-in users [id :ts] (Instant/now))
users))))
But couldn't the user be deleted from users after the check but before the swap!? So, then, is it safer to put the check inside the function run by swap!?
Yes, exactly right. You should never make a decision about how to mutate an atom from anywhere but inside of a swap! on that atom. Since swap! is the only operation guaranteed to be atomic, every time you do otherwise (ie, make a decision about an atom from outside of a swap! on it), you introduce a race condition.
But couldn't the user be deleted from users after the check but
before the swap!? So, then, is it safer to put the check inside the
function run by swap!?
As amalloy said, if you need it to be bulletproot you must put the not-null? check inside the swap function.
However, please keep in mind that you & your team are writing the rest of the program. Thus, you have a lot of outside information that may simplify your decision:
If you only ever have one thread (like most programs), you never need to worry about a race condition.
If you have 2 or more threads, maybe you never remove entries from the map (it only accumulates :ts values). Then you also don't need to worry about a conflict.
If your function is more complicated than the simple example above, you may wish to use a (dosync ...) form to wrap multiple steps instead of shoehorning everything in to a single swap function.
In the third case, replace the atom with a ref. An example is:
(defonce users (ref {...} )) ; ***** must us a ref here *****
(dosync
(if (not-nil? (get #users id))
<lots of other stuff...>
(alter users assoc-in [id :ts] (Instant/now)))))

Why monger only update one record instead all the records in the list

I have a function that takes in list of entry and save it to mongo using monger.
What is strange is that only the one record will be updated and the rest ignored unless I specify multi:true.
I don't understand why the multi flag is necessary for monger to persist all the updates to mongodb.
(defn update-entries
[entries]
(let [conn (mg/connect)
db (mg/get-db conn "database")]
(for [e entries] (mc/update db "posts" {"id" (:id e)} {$set {:data (:data e)}} {:multi true}))))
The multi flag is necessary for multi updates, since that's what mongo itself uses. Take a look at documentation for update. Granted, that's mongo shell, but most drivers try to follow when it comes to operation semantics.
Note that if "id" is unique, then you're updating one record at a time so having :multi set to true shouldn't matter.
There is, however, another issue with your code.
You use a for comprehension, which in turn iterates a collection lazily, i.e. calls to mc/update won't be made until you force the realization of the collection returned by for.
Since mc/update is a call made for it's side-effects (update a record in the db), using doseq would be more apropriate, unless you need the results.
If that's the case, wrap for in doall to force realization:
(doall
(for [e entries]
(mc/update db "posts" {"id" (:id e)} {$set {:data (:data e)}} {:multi true})))))

Clojure style / idiom: creating maps and adding them to other maps

I'm writing a Clojure programme to help me perform a security risk assessment (finally gotten fed-up with Excel).
I have a question on Clojure idiom and style.
To create a new record about an asset in a risk assessment I pass in the risk-assessment I'm currently working with (a map) and a bunch of information about the asset and my make-asset function creates the asset, adds it to the R-A and returns the new R-A.
(defn make-asset
"Makes a new asset, adds it to the given risk assessment
and returns the new risk assessment."
[risk-assessment name description owner categories
& {:keys [author notes confidentiality integrity availability]
:or {author "" notes "" confidentiality 3 integrity 3 availability 3}}]
(let [ia-ref (inc (risk-assessment :current-ia-ref))]
(assoc risk-assessment
:current-ia-ref ia-ref
:assets (conj (risk-assessment :assets)
{:ia-ref ia-ref
:name name
:desc description
:owner owner
:categories categories
:author author
:notes notes
:confidentiality confidentiality
:integrity integrity
:availability availability
:vulns []}))))
Does this look like a sensible way of going about it?
Could I make it more idiomatic, shorter, simpler?
Particular things I am thinking about are:
should make-asset add the asset to the risk-assessment? (An asset is meaningless outside of a risk assessment).
is there a simpler way of creating the asset; and
adding it to the risk-assessment?
Thank you
A few suggestions, which may or may not apply.
The Clojure idiom for no value is nil. Use it.
Present the asset as a flat map. The mixture of position and
keyword arguments is confusing and vulnerable to changes in what
makes an asset valid.
As #Symfrog suggests, separate the validation of the asset from its
association with a risk assessment.
Don't bother to keep :current-ia-ref as an entry in a risk
assessment. It is just an asset count.
Pull out the default entries for an asset into a map in plain sight.
You can change your assumed defaults as you wish.
This gives us something like the following (untested):
(def asset-defaults {:confidentiality 3, :integrity 3, :availability 3})
(defn asset-valid? [asset] (every? asset [:name :description :owner]))
(defn add-asset [risk-assessment asset]
(if (asset-valid? asset)
(update-in
risk-assessment
[:assets]
conj (assoc
(merge asset asset-defaults)
:ia-ref (inc (count (:assets risk-assessment)))
:vulns []))))
Responses to Comments
:current-ia-ref isn't a count. If an asset is deleted it shouldn't reduce :current-is-ref.
Then (4) does not apply.
I'm not sure of the pertinence of your statement that the Clojure idiom for no value is nil. Could explain further in this context please?
Quoting Differences with other Lisps: In Clojure nil means 'nothing'. It signifies the absence of a value, of any type, and is not specific to lists or sequences.
In this case, we needn't give :author or :notes empty string values.
'Flat map': are you talking about the arguments into the function, if so then I agree.
Yes.
I'm not sure why you define an asset-valid? function. That seems to exceed the original need somewhat: and personally I prefer ensuring only valid assets can be created rather than checking after the fact.
Your add-asset function uses the structure of its argument list to make sure that risk-assessment, name, description, owner, and categories are present (I forgot to check for categories). If you move to presenting the data as a map - whether as a single argument or by destructuring - you lose this constraint. So you have to check the data explicitly (whether to do so in a separate function is moot). But there are benefits:
You can check for more than the presence of certain arguments.
You don't have to remember what order the arguments are in.
Wouldn't your version mean that if I decided to make asset a record in future I'd have to change all the code that called add-asset?
No. A record behaves as a map - it implements IPersistentMap. You'd have to change make-asset, obviously.
... whereas my approach the details of what an asset is is hidden?
In what sense are the contents of an asset hidden? An asset is a map required to have particular keys, and likely to have several other particular keys. Whether the asset is 'really' a record doesn't matter.
A core principle of Clojure (and any other Lisp dialect) is to create small composable functions.
It is not a problem if an asset is created outside of a risk assessment as long as the asset is not exposed to code that is expecting a fully formed asset before it has been added to a risk assessment.
So I would suggest the following (untested):
(defn add-asset-ra
[{:keys [current-ia-ref] :as risk-assessment} asset]
(let [ia-ref (if current-ia-ref
(inc current-ia-ref)
1)]
(-> risk-assessment
(assoc :current-ia-ref ia-ref)
(update-in [:assets] #(conj % (assoc asset :ia-ref ia-ref))))))
(defn make-asset
[name description owner categories
& {:keys [author notes confidentiality integrity availability]
:or {author "" notes "" confidentiality 3 integrity 3 availability 3}}]
{:name name
:desc description
:owner owner
:categories categories
:author author
:notes notes
:confidentiality confidentiality
:integrity integrity
:availability availability
:vulns []})
You may also find the Schema library useful to validate the shape of function arguments.

Alter tables with lobos migrations

Anybody knows how to alter table (add column) with lobos migrations?
There is no documentation on this that I can find -- any help would be greatly appreciated.
Lobos will run all migrations pending according to the lobos_migrations table on your database.
The syntax for modifying an existent table is using alter, you can find some docs here.
For adding a new column this is an example migration:
(defmigration add-column-test
(up []
(alter :add (table :your_table (integer :test_column))))
(down []))
You can also find other alter options such as column rename in the tests source code