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

OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
tmbb
PhoenixWS - Websockets over Phoenix Channels Source code on Github here: GitHub - tmbb/phoenix_ws: Websockets implemented over Phoenix Ch...
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
tmbb
I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps: bureaucrat - which contains a bunch ...
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
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36352 110
New
Azolo
Hey everyone, I just released WebSockex which is a Elixir WebSocket client. WebSockex strives to work as a OTP special process, be RFC6...
New
achempion
Hi, I would like to tell about my initiative to further maintain and develop Waffle project which is the fork of Arc library. The progre...
New
zachdaniel
Ash Framework What is Ash? Ash Framework is a declarative, resource-oriented application development framework for Elixir. A resource can...
New
wfgilman
I’ve cleaned up and open sourced three financial libraries I was using for my company. They are bindings for the APIs of these three comp...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
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
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
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
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 127089 1222
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement