tfwright

tfwright

Spur - (Very) Simple Activity streams for Ecto

I’ve been working on a hobby app as a way to learn Elixir and Phoenix (one of approaching Rails horde, I imagine), and the need arose for an activity feed describing actions of various users. I searched around a bit and it didn’t seem there was anything currently existing, so I rolled my own: Spur. It’s a partial extraction of a solution that’s very simple but it’s been good enough for my needs so far. I learned a lot writing it, so hopefully it can at least serve as a learning tool for others.

The functionality is loosely based on this pretty common Rails gem, and although it’s also very simple it relies on two features of Rails that Phoenix/Ecto very consciously omits: callbacks and association polymorphism. The first issue does not pose much of a challenge since Multi provides a very nice and explicit (if a bit bulky to my Rails trained eyes) API for grouping related operations. The core of this lib is extracting the main actions that activities usually need to track: inserts, updates, and deletes:

%Context.Resource{}
|> Context.Resource.changeset(params)
|> Spur.update

Polymorphism is another story. Initially I tried to follow the recommendations in the Ecto docs, and that worked alright in my own app, but they proved to be pretty abstraction proof because they require the Activity model to be aware of context specific modules that would be tracked. I briefly considered trying to solve that through configuration, but that would involve an amount of Elixir meta-programming that’s still a bit daunting for me at the moment, and more importantly, I had the thought that activity tracking actually shouldn’t rely on associations, because by its very nature describes an ephemeral relationship. For exactly this reason I usually use some sort of revision tracking in conjunction with it in order to support things like undo, etc. So instead I kept the schema as simple as possible, basically just specifying some required and recommended fields, along with the usual catch all meta field. Along with wrapping the activity and the object change in a transaction, the main sugar it sprinkles is the ability to pass a function that it executes and save the result automatically on the activity, which is useful for things like storing changes.

%Context.Resource{}
|> Context.Resource.changeset(params)
|> Spur.update(fn resource, params -> add_resource_data_to_meta(params) end)

Anyway, it is a good example of how getting acquainted with the Elixir way has made me more thoughtful about how I approach domain modeling, instead of relying on Rails “cheats” like association polymorphism. Of course, I’d greatly appreciate any tips on how to improve my Elixir idioms and suggestions for directions to take this. One idea I’ve been playing with, inspired by revisionair, which I’m using for revisions, is abstracting the underlying storage logic, but I’m not sure how useful that would be since most of the value here is pretty Ecto specific.

Most Liked

tfwright

tfwright

Release 0.2 includes support for associating an activity with an audience.

tfwright

tfwright

Release 0.4 includes some minor optimizations and (first feature request) the ability to use a custom name for the activities table.

bruteforcecat

bruteforcecat

Awesome work. Do you know about ex_audit?
It looks like both of library work by “hijacking” standard Repo and use Ecto.Multi to append an operation for inserting the change.

I generally don’t fee very comfortable using this approach(wrapping the original db operation with this additional trace record db operation into 1 DB transaction). I was wondering what if instead of wrapping them in a Ecto.Multi. Could we only instead send msg to a dedicated GenServer and this genserver role is to do diffing and persist them in db? We can also do batching as well.

The pro of it is we are completing separating the original db operation and the trace record db operation(In fact we can even swap storage engine)

Where Next?

Popular in Announcing Top

wmnnd
Hi there, for my project DBLSQD, I needed a file storage solution that is a bit more flexible than Arc. Because I thought others might f...
New
mathieuprog
Hello :waving_hand: Allow me to introduce you to Tz, an alternative time zone database support to Tzdata. Why another library? First a...
New
alisinabh
Hey everyone i’ve developed a library for Jalaali calendar for elixir which supports converting Gregorian dates to Jalaali and vice vers...
New
blatyo
The best overview for how things are tied together is this presentation. Modules and functions are pretty well documented at this point, ...
New
kip
Image is an image processing library for Elixir. It is based upon the fabulous vix library that provides a libvips wrapper for Elixir. I...
622 18474 194
New
versilov
Could not wait for the missing Elixir ML libraries to appear, so, I wrote one myself, taking https://github.com/sdwolfz/exlearn as a foun...
New
markmark206
simple_feature_flags is a tiny package that lets you turn features on or off based on which environment (e.g. localhost, staging, product...
New
scohen
Lexical Lexical is a next-generation language server for the Elixir programming language. Features Context aware code completion As-you...
New
zoltanszogyenyi
Hey everyone :waving_hand: Excited to join this forum - I am one of the founders and current project maintainers of a popular and open-s...
New
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement