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

josevalim
Yes, yet another parser combinator library! Most of the parser combinators in the ecosystem are either compile-time, often using AST tra...
159 19417 141
New
pkrawat1
Presenting Aviacommerce, open source e-commerce platform in Elixir Aviacommerce is an open source e-commerce platform in Elixir. We at...
New
brainlid
LangChain is short for Language Chain. An LLM, or Large Language Model, is the “Language” part. This library makes it easier for Elixir a...
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
bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
384 14136 119
New
wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
mindok
What is ContEx? A pure Elixir server-side data plotting/charting library outputting SVG. It has nice barcharts in particular and works g...
New
Hal9000
Here is my first stab at this. README pasted below. https://github.com/Hal9000/elixir_random Comments and critiques are welcome. Thank...
New
OvermindDL1
Been making an MLElixir thing (not released yet…) for fun in spare time in the past day. I’m just trying to see how much I can get an ML...
132 14057 106
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

Other popular topics Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 43487 311
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43757 214
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
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31265 143
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement