<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: RITER.CO</title>
    <description>The latest articles on Forem by RITER.CO (@riter).</description>
    <link>https://forem.com/riter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F299%2F39d34617-ee4d-497a-871a-eaa510c08ed1.jpg</url>
      <title>Forem: RITER.CO</title>
      <link>https://forem.com/riter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/riter"/>
    <language>en</language>
    <item>
      <title>Chat as a project management system</title>
      <dc:creator>Alexey Osipenko</dc:creator>
      <pubDate>Tue, 15 Nov 2022 08:56:55 +0000</pubDate>
      <link>https://forem.com/riter/chat-as-a-project-management-system-20k2</link>
      <guid>https://forem.com/riter/chat-as-a-project-management-system-20k2</guid>
      <description>&lt;p&gt;The easiest way to manage tasks is the one that isn't. Or the one you don't notice.&lt;/p&gt;

&lt;p&gt;Let's be honest. Each task or issue in your project can be agreed upon or even solved in a chat. Well, some of them may be discussed not in the chat, but via the calls, but this thing highlights the main point. Any project management tool, like the monstrous Jira or the fancy Notion, has always been secondary in the management process. The such system only tries to keep up with the real world. To keep the management process actual company hires a separate employee who should be responsible for this. Also, they may create additional rules, for example, demanding an issue prefix in the branch name or issue number in the brackets before each commits message. Such things are not simplified debug porocess or bring order to the process. It allows us to be sure that the user creates the task before writing the code. As a consequence, a user creates the task, estimate it, and have a discussion with the managers and colleagues. That's it.&lt;/p&gt;

&lt;p&gt;Next to the tasks are relentless time-tracking systems, multiple layers of decision makers, bug trackers, release notes, release flags, and many more things specific to a particular industry, but these are even further away from the actual process. Even if you imagine a perfectly spherical situation in a sterile project. For example, the bug tracker automatically got a new report, after that, an internal system created a new task by a trigger, picked the author of commits, and automatically assigned a responsible developer. Will that be the end of it? Of course not. The system should have feedback from the participants. Whether it's the developer, the tester, or the manager, they will ask in a chat channel, "Hey guys, look what happened. Who's to blame and what to do?". Then there's bound to be a discussion about the nature of the bug, free developers, and assigning responsibility. And when all these questions are solved, a developer can go to the tracker and fix the problem. Well, or create the new one. And there will be something rather dry in the title and formulaic in the task body. And all of that heated discussion, all those attempts to determine what is missing and transfer the folklore knowledge about this particular module will remain in the depths of chat and then disappear after 90 days in the free plan.&lt;/p&gt;

&lt;p&gt;Chat is primary. Chat is the place where the most important and key things for a given task happen. Chat is where the decision maker makes the important decisions, where the person who will do the task points out potential problems and points out the shortcomings of the current solution. I will stipulate again that sometimes this happens in the voice chat, but it certainly does not lure in the comments to the task.&lt;/p&gt;

&lt;p&gt;You just have to stop lying to yourself that the task tracker is the central place of the project, where all the data threads converge. Chat is where everything happens. There are no chance to change it except the way when the any chat in a team will be denied. &lt;/p&gt;

&lt;p&gt;Some people objected to this and said that in their project, everything happens within the tasks of the project tracker. The arguments usually pointed to the remoteness of the team in space and the different time zones of various chat participants. Someone is sleeping or eating, and someone is working. It's better to read the comments, not the chats. But the slightest study showed that when it was really necessary to decide something, not just to write something formal, the discussion quickly moved to the chat room. In most cases in private chat room, which is worse. Then it was possible to discuss the details, so that then in the comments formal language to write not explaining anything, but only declaring the decision.&lt;/p&gt;

&lt;p&gt;We choose the other way. We're trying to figure out what we needed that wouldn't fit into a regular thread in Slack. We came up with only three points.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any message in a chat room can become a task. And correspondence in that message's thread becomes a discussion of that task.&lt;/li&gt;
&lt;li&gt;The responsible person for the task is not always the one who writes or the one who gets written to. The person responsible can be tagged or simply tagged via the nickname.&lt;/li&gt;
&lt;li&gt;It is obvious, but it is important to have a task performer. Sometimes (not in all cases) it is important to set deadlines.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It turned out that this is the minimum functionality that will not only meet our needs but will also suit the vast majority of project teams. At the very least, this is the right place to start any project, when you want some order to tasks that were chaotically solved simply in Slack.&lt;/p&gt;

&lt;p&gt;As a result, we created the &lt;a href="http://slack.riter.co"&gt;application in Slack&lt;/a&gt; with the telling title "Task for Slack" first for ourselves, and then we opened it for everyone who wants to use it. Use it, it's completely free without any restrictions, additional registrations, integrations, or external services. Everything is done without leaving Slack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uETo5qoN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dhl8hkssta917zy4lup5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uETo5qoN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dhl8hkssta917zy4lup5.png" alt="slack.riter.co screenshot" width="880" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would like to hear in the comments what is missing in the tool, and what should be added to your opinion.&lt;/p&gt;

</description>
      <category>management</category>
      <category>slack</category>
      <category>productivity</category>
      <category>startup</category>
    </item>
    <item>
      <title>Software Developer’s Efficiency: How to Evaluate and Improve?</title>
      <dc:creator>katglin</dc:creator>
      <pubDate>Sun, 19 May 2019 14:17:36 +0000</pubDate>
      <link>https://forem.com/riter/software-developer-s-efficiency-how-to-evaluate-and-improve-5332</link>
      <guid>https://forem.com/riter/software-developer-s-efficiency-how-to-evaluate-and-improve-5332</guid>
      <description>&lt;p&gt;I know I’m preaching to the choir now, but a good programmer is distinguished from a bad one by efficiency. And the first thing that comes to mind when we hear the word “efficiency” is the time spent on implementing a particular feature. For example, when one developer does something in three days, and another one — in six days, then a small amount of arithmetic calculations reveals that the first developer is twice as effective as the second one, isn’t he?&lt;/p&gt;

&lt;p&gt;No! This criterion will be correct only in the short run and totally wrong during long-term development.&lt;/p&gt;

&lt;p&gt;The right way to test a developer’s performance is to see how he handles several typical (repetitive) tasks. Each next task of the same type must take less and less time. You can try to derive a certain formula of reducing the execution time of a next task and use it to evaluate and compare the effectiveness of developers in your team. The so-called developer’s efficiency ratio. Well, for example, each next task of the same type should take two times less time than the previous one. Until a certain point, of course.&lt;/p&gt;

&lt;h2&gt;
  
  
  Becoming more efficient
&lt;/h2&gt;

&lt;p&gt;We could talk a lot about how to achieve efficiency gains and speed up the development process. This time we will offer you a couple of analogies for reflection.&lt;/p&gt;

&lt;p&gt;Do you remember the idiom about the sharpening of the ax and deforestation? The one where an abstract developer has no time to sharpen an ax, because he’s too busy chopping wood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GQtfdHax--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/d0f8fb2805334878bdf46c33b3d8.jpg/autoorient%2Cresize_fit-1920-1080/too%2520busy.jpg%3Fp%3D0aa930d138b25b338ee16fe%26s%3Dcda1b9c175b18aa4a7b76e71c5cfdd35bc75483c" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GQtfdHax--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/d0f8fb2805334878bdf46c33b3d8.jpg/autoorient%2Cresize_fit-1920-1080/too%2520busy.jpg%3Fp%3D0aa930d138b25b338ee16fe%26s%3Dcda1b9c175b18aa4a7b76e71c5cfdd35bc75483c" alt="im1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem is obvious, but the solution is not always so straightforward. There is a flipside here — premature ax sharpening. The ax needs to be sharpened when it is clear that the costs of sharpening the ax will pay off by the speed of deforestation.&lt;/p&gt;

&lt;p&gt;And to check it out, you need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Find out the speed of deforestation with a blunt ax. To this end, you need to do the task at least once before any optimizations. No need to implement a separate library and introduce extra abstractions till then.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understand the characteristics of the ax, know what will happen after sharpening. The initially written inline solution, built within the current architecture, will fully make it clear what and where you can later replace and refactor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Know the next time you need to sharpen an ax. It’s impossible to make all the code completely polished at once, but it is never necessary. It is much better to take care only of a specific task each time when refactoring. Your goal is to shorten the development time of this particular task the next time, say, twice.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is another similar popular idiom. It says something like: “It’s better to spend the whole day building an airplane to fly to your destination in five minutes, than to spend the whole day trying to run there.” But the “ax” and the “plane” idioms describe different cases. The “ax idiom” tells about different tasks of the same type, while the “airplane idiom” — about routine processes within the same task.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://riter.co/blog"&gt;Riter Agile Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Diving into programming: where not to begin?</title>
      <dc:creator>katglin</dc:creator>
      <pubDate>Mon, 13 May 2019 08:56:45 +0000</pubDate>
      <link>https://forem.com/riter/diving-into-programming-where-not-to-begin-8p5</link>
      <guid>https://forem.com/riter/diving-into-programming-where-not-to-begin-8p5</guid>
      <description>&lt;p&gt;I have already raised the issue in one of my &lt;a href="https://riter.co/blog/diving-into-programming-things-to-avoid" rel="noopener noreferrer"&gt;previous posts&lt;/a&gt;, however, daily practice shows that the problem is still relevant. This time, I’ll also talk about paid programming courses which are so popular today. Lots of courses like “Learn PHP/Java/C++/... in a month/2 weeks/...” and high demand for them don’t allow me to just keep silent. Moreover, an enormous number of their advertising, exceeding reasonable limits, also do not allow to ignore this topic. So I will try to save a couple hundred dollars for those who want to dive into programming through courses.&lt;/p&gt;

&lt;p&gt;For starters, here are some statements to think about.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The overwhelming majority of programmers’ daily tasks consist of finding and assessing the necessary information and building new mental abstractions. So it is just impossible to learn everything you need for work in advance. First of all, it is better to study the mechanism for obtaining new knowledge. It is about “learning to learn”. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2F38503f7054094ab8b90c848f8e29.png%2Fautoorient%2Cresize_fit-1920-1080%2Flearn.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D230822bce83d986bd1428205a140e51817282bdd" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2F38503f7054094ab8b90c848f8e29.png%2Fautoorient%2Cresize_fit-1920-1080%2Flearn.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D230822bce83d986bd1428205a140e51817282bdd" alt="im1"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The most topical technology turns into an ancient enterprise in six months or a year. The process of learning technologies should be continuous and never stop. The material which is taught to you on courses right now was prepared a couple of months ago and mastered by the teacher themself six months ago or earlier. The relevance of such knowledge is extremely small.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The learning process is always based on drawing analogies with already known things. And for each person, the process of making such analogies is strongly individual. All that a teacher is able to do is to impose their own analogies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A certain basic set of knowledge can still be obtained during courses, but it is so fundamental that you can easily learn it independently. Git, the basics of Unix, algorithms and data structures, a bit of math. In addition, some basis for a specific profession. For example, in web programming you need to understand HTML, CSS, HTTP and stuff.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2Fdf2a2baaa5634a4e951eefc26757.png%2Fautoorient%2Cresize_fit-1920-1080%2Finstant-study.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3Da91d2b1c9edf6acdf006424f377ec4b025574888" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2Fdf2a2baaa5634a4e951eefc26757.png%2Fautoorient%2Cresize_fit-1920-1080%2Finstant-study.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3Da91d2b1c9edf6acdf006424f377ec4b025574888" alt="im2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, in practice, it can be difficult to master both the fundamentals and a certain programming language on your own, especially without any technical background, since you don’t have an overall picture. In this case, it is still important to distinguish good courses from bad ones.&lt;/p&gt;

&lt;p&gt;Good courses give you new challenges, tasks which you learn to solve. Sometimes these are typical tasks and often typical approaches. Good courses do not impose approaches, but explain which ones are better and why. And the tool for solving the problem — say, ruby or python — you can choose depending on the situation. &lt;strong&gt;Bad courses teach you a language. Good courses teach you to meet the challenges and solve tasks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And finally, even if you succeed to choose the best and the most rigorous courses, it’ll still be very hard to stand out from the increasing number of other similar students, not to say about thousands of computer science graduates.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And what is your take on this issue? Have you had your own experience with such courses or their graduates? How successful was it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://riter.co/blog/diving-into-programming-2-where-not-tobegin" rel="noopener noreferrer"&gt;Riter Agile Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Sharing and Concealing Startup Ideas</title>
      <dc:creator>katglin</dc:creator>
      <pubDate>Wed, 08 May 2019 07:54:51 +0000</pubDate>
      <link>https://forem.com/riter/sharing-and-concealing-startup-ideas-4fg6</link>
      <guid>https://forem.com/riter/sharing-and-concealing-startup-ideas-4fg6</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RkMRE_lo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cpr43i4zel284k5aoxv8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RkMRE_lo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cpr43i4zel284k5aoxv8.jpg" alt="im"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;— What are you working on now?&lt;/em&gt;&lt;br&gt;
&lt;em&gt;— I’m doing a project.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;— What kind? Tell me, I’m curious.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;— I can’t tell you anything now, you will see it later.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Probably every programmer has an idée fixe which will one day allow them to take over the world together with nearby planets. It is different for everybody, with varying degrees of ambitions, forethought, and planning. Gamers want to write a game. Home life optimizers want to create culinary apps, home bookkeeping tools, and smart home software. Holiday fans want social networks for drunks and mobile apps for camping and picnics organizing.&lt;/p&gt;

&lt;p&gt;Some are lucky to find like-minded people to work together on the “wunderwaffe” which will save millions of lives, revive the Ussuri tigers, and make them billions of dollars. Only a few begin to implement at least something and only a handful of them are able to bring the idea to at least some workable stage of a product. This is all well-known, logical, and this is the way it should be.&lt;/p&gt;

&lt;p&gt;But what is really incomprehensible and illogical is the desire to hide what you are working on now. Well, or, at least, what you are planning to work on.&lt;/p&gt;

&lt;p&gt;On the one hand, it is clear that you don’t want to be unfounded and talk about an idea that you haven’t even begun to do in a too pretentious way. On the other hand, any idea that you share with people is strongly criticized. And this criticism is worth listening to. Correctly posed questions and answers to them help to develop in the right direction. The same &lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging"&gt;“rubber duck”&lt;/a&gt; method applies here, but only at the stage of system requirements analysis and software design. First, you train to answer awkward questions and hone that very &lt;a href="//ttps://en.wikipedia.org/wiki/Elevator_pitch"&gt;“elevator pitch”&lt;/a&gt;. Secondly, you begin to see the obvious weaknesses of the project still at the idea stage. This is not touching the fact that like-minded people can be found only if you talk about your project.&lt;/p&gt;

&lt;p&gt;Of course, this does not mean that you need to talk about your projects on every corner. We are talking only about the places where it is relevant, such as thematic conferences and meetings, and when someone is really interested in what you are working on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AgbIf9yS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/eadf94b519224431bc3d313ba69c.png/autoorient%2Cresize_fit-1920-1080/pr.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D0cd118d5af941cfd50d3fba0a3522d0379ef1503" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AgbIf9yS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/eadf94b519224431bc3d313ba69c.png/autoorient%2Cresize_fit-1920-1080/pr.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D0cd118d5af941cfd50d3fba0a3522d0379ef1503" alt="im1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In some cases, developers refer to a risk that a good idea can be stolen. However, in the modern world it is rather pointless to be afraid of this. More likely you need to be afraid that someone else will come up with the same idea and its implementation will turn out to be a million times cooler than you could imagine on your own. Or that this idea is already being developed by someone else, or even has already been implemented. The chances that you will miss this will be much lower if you do not keep the idea secret, but discuss it with friends and colleagues.&lt;/p&gt;

&lt;p&gt;Sometimes, on the contrary, such a reluctance to tell about the project is connected with the author’s uncertainty that it is worth the discussion. High competition, slow development of the project in comparison with similar apps, thoughts like “well, if it starts to go well, then I’ll tell them” often prevent startups from turning into something more than a hobby or even force an author to drop out of it over time. However, &lt;a href="https://riter.co/blog/startups-3-ways-of-development"&gt;as mentioned earlier&lt;/a&gt;, the presence of competition does not mean that the idea is not destined to grow into something significant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“If you are afraid to create or always compare your content with others’, fearing to produce something ‘worse than others’, think about the theory of two cakes”&lt;/em&gt; (@Charodei_Ent)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IBh3Vtq6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/f3b4dc5f081949188e3fd2f72bd2.png/autoorient%2Cresize_fit-1920-1080/2.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3Dae512fdb443f4af99217c338c6ed83faf90ae4e8" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IBh3Vtq6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/f3b4dc5f081949188e3fd2f72bd2.png/autoorient%2Cresize_fit-1920-1080/2.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3Dae512fdb443f4af99217c338c6ed83faf90ae4e8" alt="im2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you can hear a more reasoned opinion as an alternative to ours “tell everyone about your idea.” For example, one of our subscribers shared the following statement: “When a developer tells someone about his plans, it may be a feeling that he has already achieved something. As a result, false satisfaction appears and motivation gets low.”&lt;/p&gt;

&lt;p&gt;And what is your opinion in this regard? At what stage do you begin to discuss and share your project ideas and success?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://riter.co/blog/sharing-and-concealing-startup-ideas"&gt;Riter.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>discuss</category>
      <category>networking</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Project Management Digest, March 2019</title>
      <dc:creator>katglin</dc:creator>
      <pubDate>Sun, 31 Mar 2019 14:38:34 +0000</pubDate>
      <link>https://forem.com/riter/project-management-digest-march-2019-6op</link>
      <guid>https://forem.com/riter/project-management-digest-march-2019-6op</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://riter.co/blog/project-management-digest--march-2019"&gt;Riter.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the third release of our monthly digest of the most discussed, popular, and entertainment articles focused primarily on product owners and project manager. However, everybody working in the IT field can find something useful here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Most popular articles
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Startups, product management, and related fields&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projecttimes.com/articles/how-ai-will-change-the-future-of-project-management.html"&gt;How AI Will Change The Future Of Project Management&lt;/a&gt; — the topic of AI is becoming more popular each year and many experts have their own opinion on its implementation in project management. The author tells about task prioritization, project re-planning, better and efficient analysis, insights and prediction, and much more. Some assumptions are already being implemented in practice by existing &lt;a href="https://riter.co/blog/artificial-intelligence-in-today-s-project-management"&gt;project management software&lt;/a&gt;, but some are quite new and deserve attention.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.arraspeople.co.uk/camel-blog/pm-careers-advice/future-project-management-post-brexit-apprenticeships-ai/"&gt;The Future of Project Management – Post Brexit, Apprenticeships and AI&lt;/a&gt; — one more view on the future of project management and the most possible changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/articles/529992/Applying-Project-Management-Skills-to-Guide-Production-of-an-Off-Road-Triathlon-Film"&gt;Applying Project Management Skills to Guide Production of an Off-Road Triathlon Film&lt;/a&gt;. Project management skills are applicable in many industries. The author tells about changing his roles from an IT professional to a principal in a video-content firm. Can you share your own life situations where your project management experience and skills came in handy?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/gits-apps-insight/why-agile-and-how-to-be-good-at-it-as-ux-designer-dbc0f50e0073"&gt;Why Agile and How to be Good at It As UX Designer&lt;/a&gt;. Having worked on various agile projects for clients in different industries, the author is sharing what he learned from these agile projects as a UX designer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/gijovarghese/how-my-side-project-became-my-full-time-startup-934"&gt;How my side project became my full-time Startup&lt;/a&gt;. Learn how to turn your side project into a promising startup, how to develop, scale, and promote it properly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Career and leadership&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://leadershipfreak.blog/2019/03/22/how-to-navigate-turmoil-without-sinking-the-boat/"&gt;How To Navigate Turmoil Without Sinking The Boat&lt;/a&gt;. Unresolved turmoil weakens your team, leads to frustration, helplessness, and regret. Find out what can cause turmoil and how to manage it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://brodzinski.com/2019/03/cultural-fit.html"&gt;Cultural Fit versus Cultural Fit&lt;/a&gt; — a little about hiring developers, organizational culture, team management, good and bad approaches to recruitment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://leadershipfreak.blog/2019/03/26/10-ways-to-help-reluctant-people-make-commitments/"&gt;10 Ways To Help Reluctant People Make Commitments&lt;/a&gt;. Leaders spend much time trying to get performance from people who aren’t committed. Spend more time helping people commit and less time pressuring people to conform. As a team leader, how do you help people make commitments?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/articles/534221/3-Ways-to-Perfect-Your-Strategic-Skills"&gt;3 Ways to Perfect Your Strategic Skills&lt;/a&gt;. What creates long-term success in a project management career? In the author’s opinion, developing strategic skills is the best path to that destination. And the article explains how you can improve them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Agile and Scrum&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/blog-post/52404/Free-Your-Team-With-Liberating-Structures-"&gt;Free Your Team With Liberating Structures&lt;/a&gt;. The author tells about &lt;a href="http://www.liberatingstructures.com/"&gt;“liberating structures”&lt;/a&gt; and different ways to use them to organize your work. If you are a fan of liberating structures, feel free to share which ones you used, in what context, and how was the result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/serious-scrum/about-that-scrum-master-agile-project-manager-role-8200a801f253"&gt;About that Scrum Master/Agile Project manager role...&lt;/a&gt;. The article is going to explain why you can’t be a Scrum Master &amp;amp; Project Manager at the same time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/articles/532707/All-for-One-Backlog--One-Backlog-for-All"&gt;All for One Backlog, One Backlog for All&lt;/a&gt;. It’s well-known that Scrum supposes the only backlog used by the team. However, in practice, some organizations try to split the backlog between “product” and “tech” and give each their own product owner. The author tells about problems which can cause such a behavior, why this is a wrong approach, and how teams could avoid it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/serious-scrum/a-daily-scrum-in-the-cave-78d8d7ac6f72"&gt;A Daily Scrum in the Cave&lt;/a&gt;. The author uses Plato’s Allegory of the Cave to talk about Scrum and tremendous opportunities outside “the cave”.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Productivity and teamwork&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kbondale.wordpress.com/2019/03/17/are-you-being-responsibly-transparent/"&gt;Are you being (responsibly) transparent?&lt;/a&gt; — the author describes the need for team members to act with responsible transparency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kbondale.wordpress.com/2019/03/24/how-hidden-are-your-hurdles/"&gt;How hidden are your hurdles?&lt;/a&gt; How does your team cope with team’s blockers and how bold they are? In the post, the author tells about possible ways to deal with such blockers — issues preventing developers from completing their work items — without the need for broader communication or escalation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanager.com/training/how-to-disagree-with-your-boss"&gt;How to Disagree with Your Boss&lt;/a&gt;. Your boss isn’t always right, and sometimes you need to show them the error of their ways, however difficult that may be.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://leadershipfreak.blog/2019/03/01/how-to-overcome-the-frustrations-of-collaboration/"&gt;How To Overcome The Frustrations Of Collaboration&lt;/a&gt;. The truth is, the fastest way to get simple work done is clear direction and obedient compliance. But you’re on your own when people feel excluded and disrespected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/the-new-york-times/how-to-build-a-successful-team-d6d2168f5d3b"&gt;How to Build a Successful Team&lt;/a&gt; — “every leader should take the ‘12 minutes’ out of their day to read and internalize this”.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Most discussed articles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/blog-post/52363/3-Ways-To-Set-Yourself-Apart"&gt;3 Ways To Set Yourself Apart&lt;/a&gt;. Not all project managers are created equal. How do you differentiate yourself as a project manager? The post suggests a few ideas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/emmawedekind/how-to-boost-your-productivity--get-sht-done-3h5n"&gt;How To Boost Your Productivity &amp;amp; Get Sh*t Done&lt;/a&gt;. Productivity is a skill that can be learned. The post includes some of the tips &amp;amp; tricks which the author uses to prioritize her commitments and tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/articles/534250/8-Common-Project-Manager-Mistakes"&gt;8 Common Project Manager Mistakes&lt;/a&gt; — some common weaknesses of many project managers and suggestions how we could deal with them. Familiarizing yourself with common mistakes can help you prevent a project disaster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/articles/532481/Reducing-Waste-in-Project-Management"&gt;Reducing Waste in Project Management&lt;/a&gt;. When a company grows, the complexity of project management methodologies used also grows. As a result, project managers suddenly find themselves inundated with a wealth of templates, processes and tools, but project success rate only falls. The author tells about scaling your project management processes and some common mistakes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/helenanders26/do-you-have-a-type-4jp1"&gt;What’s your type?&lt;/a&gt; Join the discussion of Scott Hanselmans interview with Camille Fournier on Hanselminutes — this is an episode relating to management and what behaviors managers should and shouldn’t exhibit to motivate engineering teams.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Entertainment content
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.reddit.com/r/agile/comments/b4erew/tell_me_your_best_agile_joke/"&gt;The best Agile jokes&lt;/a&gt; from reddit community. Read and don’t forget to share your own examples.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.reddit.com/r/ProgrammerHumor/comments/b0rd8k/the_car/"&gt;A manager, a mechanical engineer, and software analyst are driving a car...&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.reddit.com/r/ProgrammerHumor/comments/ay26df/estimates/"&gt;Estimates&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/blogs/332098/Geplanus--The-PM"&gt;Geplanus: The PM&lt;/a&gt; — a collection of funny comics on project management and Agile.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources for project managers
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;General resources and blogs&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://project-management.com/project-management-huts/"&gt;Project Management Huts&lt;/a&gt; — a collection of articles oriented on project managers with varying levels of skill and experience. Also, several new articles appear almost every day at &lt;a href="https://project-management.com/2019/"&gt;their website&lt;/a&gt;. In addition, you can find book and software reviews, knowledge base, and other related materials covering specific subjects for project managers there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.girlsguidetopm.com/"&gt;Girl’s Guide to PM&lt;/a&gt; — a blog for project, program and portfolio managers and project delivery professionals. It includes templates, mentoring programs, training tutorials and guides, blog posts and some paid content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://tynerblain.com/blog/"&gt;Tyner Blain Blog&lt;/a&gt; — the author shares lessons he’s learned from his own project management experiences as well as from what he’s observed as a consultant for companies both large and small.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://riter.co/blog"&gt;Agile blog&lt;/a&gt;, the original source of this monthly digest. In addition to the posts on general project management topics (time management, risks, AI in project management, productivity, collaboration, etc.), you will find there many non-standard views and thoughts on the best coding practices, team working, managing startups, and a few articles on technologies and achievements in software development based on our own experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://bobsutton.typepad.com/"&gt;Bob Sutton’s Blog&lt;/a&gt; — not a new or active blog indeed, however, its content still remains actual and can be useful for project managers regardless of their experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://blog.proofhub.com/"&gt;ProofHub Blog&lt;/a&gt; — a space of posts on common issues and tasks in project management.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Books and educational resources&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://professor.ufabc.edu.br/~nelson.faustino/Ensino/IPE2016/Livros/Peter%20L.%20Bernstein-Against%20the%20Gods_%20The%20Remarkable%20Story%20of%20Risk-Wiley%20(1998)%20(1).pdf"&gt;“Against the Gods, The remarkable story of Risk” by Peter L. Bernstein&lt;/a&gt; — perhaps the best general history of risk — and presentation of the major concepts of risk — that is understandable by all practitioners at any level.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://icproject.com/en/blog/10-books-about-project-management-worth-reading/"&gt;10 books about project management worth reading&lt;/a&gt; — if you are looking for a way to improve your project management skills, this is a rather fair March collection of books worth reading.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.knowledgetrain.co.uk/resources"&gt;Knowledge Train&lt;/a&gt; — free resources for project management grouped by categories and types. Their materials are mainly categorized as Careers, Practice, Qualifications, Training and Exams, News. The content appears in different formats like videos with transcriptions, PDF downloads, infographics, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.girlsguidetopm.com/free-project-status-report-template"&gt;Free Project Status Report Template&lt;/a&gt; — a free PDF report template along with a recent post which explains what a report template is and how you can use it for your needs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Podcasts&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://art19.com/shows/leap-with-tina-seelig"&gt;LEAP! with Tina Seelig&lt;/a&gt; — a podcast series hosted by Tina Seelig, Professor of the Practice in Stanford’s Department of Management Science and Engineering. Each episode takes about 20 minutes. 4 new episodes were released in March. You can find more articles and podcasts &lt;a href="https://ecorner.stanford.edu/series/leap/"&gt;on the website&lt;/a&gt; and on the &lt;a href="https://medium.com/@tseelig"&gt;Medium blog&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://burnupmedia.com/"&gt;The Burn Up&lt;/a&gt; — a new podcast that may be of interest to project managers and product owners. Authors share their combined 40 years of experience in software design and delivery. The podcast was released in January 2019 and has 10 episodes for now. They tell about team management, user stories, business analyst, and product management and take about 20 minutes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Slack communities&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://join.slack.com/t/serious-scrum/shared_invite/enQtMzcxNDQ4NTM4MzI1LTI0ZGU0NjFkZGM2ZDM2MTlhMDQyMjVlMTJkZjk5OTZlZDhkNDczZTIzOTUxYjMyYTk4ZGNhOTNjM2EwZWIyMTc"&gt;Serious Scrum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thank you for reading. We would be glad to know which topics and resources are the most interesting for you. Also, feel free to share your favorite articles and other materials for the next digests. We always take into account all suggestions. &lt;a href="https://twitter.com/RiterApp"&gt;Follow us&lt;/a&gt; on social media or &lt;a href="https://riter.co/blog"&gt;subscribe&lt;/a&gt; to our blog not miss them.&lt;/p&gt;

&lt;p&gt;The previous releases:&lt;br&gt;
&lt;a href="https://riter.co/blog/project-management-digest--february-2019"&gt;Project Management Digest, February 2019&lt;/a&gt;&lt;br&gt;
&lt;a href="https://riter.co/blog/project-management-digest--january-2019"&gt;Project Management Digest, January 2019&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agile</category>
      <category>projectmanagement</category>
      <category>leadership</category>
      <category>scrum</category>
    </item>
    <item>
      <title>Serverless Approach to Integrate Gitlab Webhooks with GraphQL API</title>
      <dc:creator>katglin</dc:creator>
      <pubDate>Sat, 16 Mar 2019 08:12:48 +0000</pubDate>
      <link>https://forem.com/riter/serverless-approach-to-integrate-gitlab-webhooks-with-graphql-api-4kgi</link>
      <guid>https://forem.com/riter/serverless-approach-to-integrate-gitlab-webhooks-with-graphql-api-4kgi</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://riter.co/blog/serverless-approach-tointegrate-gitlab-webhooks-with-riter-graphql-api" rel="noopener noreferrer"&gt;Riter.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this post, we describe how to integrate Gitlab webhooks with Riter API and deploy the service on the Google Cloud Platform using &lt;a href="https://cloud.google.com/functions/" rel="noopener noreferrer"&gt;Cloud Functions&lt;/a&gt; — a serverless compute solution for creating event-driven applications. Cloud Functions let you reduce the infrastructure costs and provide a relatively convenient way to build and deploy services. The whole code is available on &lt;a href="https://github.com/K-S-A/gcloud-riter-gitlab-integration" rel="noopener noreferrer"&gt;Github&lt;/a&gt; along with unit and performance tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service purpose
&lt;/h2&gt;

&lt;p&gt;As you may know, Riter is a project management tool used (and developed) by our team. Each pull request created in Gitlab is always associated with a certain task (a story, as we call it) in Riter. So, up until now, we had to insert a link to the task in the pull request description, and after that — add a corresponding link to pull request inside the task comments. As a result, it was easy to see which particular code changes a specific task includes, and which task a certain pull request relates to. On the other hand, such an approach resulted in some “double work” which we’d like to avoid.&lt;/p&gt;

&lt;p&gt;To this end, we’ve written a simple service that uses Gitlab webhooks to track and process merge request events and Riter API — to report these events in appropriate tasks. In our case, we suggest that each Gitlab repository corresponds to a particular Riter project. We also suggest that either a pull request description or a source branch name contain a story slug. If so, then each time when somebody performs a merge command, the service figures out a task to which it belongs and adds a link to this pull request (with its id, title, and action performed) in the task annotations. As a result, we’ve got an automatically generated history of changes in our project management tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements to the initial data
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generate Riter access token&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All Riter users have full access to its &lt;a href="https://riter.co/docs/graphql-api" rel="noopener noreferrer"&gt;GraphQL API&lt;/a&gt; which covers all existing functionality. Go to “My Profile” settings and open “Access tokens” tab. Here you can generate a personal access token to all your projects at once. However, in our example, we generate several separate access tokens for different projects and save them in appropriate environment variables (see below).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2F80425b7add7447b5af56e98f885b.png%2Fautoorient%2Cresize_fit-1920-1080%2Fimage.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D64e4ce430eabb5fa542aa7d688762a5ec6a2aff6" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2F80425b7add7447b5af56e98f885b.png%2Fautoorient%2Cresize_fit-1920-1080%2Fimage.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D64e4ce430eabb5fa542aa7d688762a5ec6a2aff6" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, we could create a separate user identity (a bot) to generate annotations on its behalf. However, for ease, let’s suppose that we use just a regular Riter user account to report all events.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Set up Gitlab webhooks&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Gitlab, go to the project “Settings -&amp;gt; Integration” page to add a webhook. We chose “Merge request events” trigger to process only events when a merge request is created, updated, and merged. In the same way, we could process any other events in the repository.&lt;/p&gt;

&lt;p&gt;Here you also need to specify the &lt;code&gt;URL&lt;/code&gt; which is unique for each function and appears in the console after deploy. It can be found in the Google Cloud Admin panel as well.&lt;/p&gt;

&lt;p&gt;In our case, we call the same function for several Gitlab repositories. However, it would be better to deploy separate instances with different environment variables for each repository.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment variables&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use several environment variables. We’re going to track events only from specific users, so &lt;code&gt;USERS&lt;/code&gt; include a space-separated list of Gitlab user nicknames.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USERS: nickname1 nickname2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, we create environment variables (“ProjectName_GraphQL_API_URL”) for different project access tokens along with the full API endpoint. For example, for the &lt;a href="https://demo.riter.co" rel="noopener noreferrer"&gt;Demo company&lt;/a&gt;, the “The night’s watch” project, the &lt;code&gt;env.yml&lt;/code&gt; file could include the variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THE_NIGHTS_WATCH_GRAPHQL_API_URL: https://demo.riter.co/the-night_s-watch/graphql?api_token=18dc7...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our example, we use similar variables defined as &lt;code&gt;TRACKER_GRAPHQL_API_URL&lt;/code&gt;, &lt;code&gt;CLIENT_GRAPHQL_API_URL&lt;/code&gt;, and &lt;code&gt;WEBHOOK_TEST_GRAPHQL_API_URL&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The entire code takes 84 lines and is written in pure JavaScript. An example of the same code written in Go is also available.&lt;/p&gt;

&lt;p&gt;We get all users (whose pull requests we’d like to track) separated with a space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const users = process.env.USERS.split(' ');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In GraphQL, we use mutations to perform POST requests. So, we describe a mutation to create a new annotation (&lt;code&gt;input&lt;/code&gt;) to a particular story (specified by &lt;code&gt;slug&lt;/code&gt;) in Riter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const query = `
  mutation($input: AnnotationTypeCreateInput!) {
    createAnnotation(input: $input) {
      resource {
        slug
      }
    }
  }
`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we list endpoints for different project names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const projects = {
  'tracker': {
    endpoint: process.env.TRACKER_GRAPHQL_API_URL
  },
  'client': {
    endpoint: process.env.CLIENT_GRAPHQL_API_URL
  },
  'webhook-test': {
    endpoint: process.env.WEBHOOK_TEST_GRAPHQL_API_URL
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, compose and send a request. We respond immediately to the webhook to speed up request processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  response.status(200).send();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the endpoint specified for a current project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if(req.body.project) {
    project = projects[req.body.project.name];
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only calls from specific users and for certain projects are allowed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if(!project || !req.body.user || req.body.user &amp;amp;&amp;amp; !users.includes(req.body.user.username)) {
    return 1;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we validate the presence of content: URL to the pull request, pull request id, title, and action (for example, “open” or “update”):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const attributes = req.body.object_attributes;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And try to define story slug from the pull request description or the related source branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  let storySlug;

  if(attributes.description) {
    [, storySlug] = attributes.description.match(/stories\/([\w-]+)/) || [];
  }

  if(!storySlug &amp;amp;&amp;amp; attributes.source_branch) {
    [storySlug] = attributes.source_branch.match(/^[\w-]+/) || [];
  }

  if(!storySlug) {
    return 3;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, if all the data is correct, we perform the API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const { url, iid, title, action } = attributes;
  const { endpoint } = project;

  const variables = {
    input: {
      body: `[Merge request !${iid} - "${title}" (${action})](${url})`,
      storySlug: storySlug
    }
  };

  request(endpoint, query, variables).catch(() =&amp;gt; {});
  return 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy and run
&lt;/h2&gt;

&lt;p&gt;All commands required to deploy the service on Google Cloud Platform are provided in the README file. For example, here’s how we can deploy the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gcloud functions deploy http --env-vars-file .env.yml --trigger-http --runtime nodejs8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve used &lt;a href="https://cloud.google.com/functions/docs/emulator" rel="noopener noreferrer"&gt;Cloud Functions Node.js Emulator&lt;/a&gt; to deploy, run, and debug the app on the local machine before deploying them to the production. We’ve used the following &lt;a href="https://rominirani.com/google-cloud-functions-tutorial-setting-up-a-local-development-environment-8acd394a8b76" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; to set up Local Functions Emulator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Event processing speed appeared to be quite high. Memory usage, on the contrary, was rather low, as we had expected. That’s well since you are charged based on resources consumption (and on the number of requests to your function). While deploying Cloud Functions, you only have to specify the amount of memory your function needs and CPU resource is allocated proportionally. Here’re some screenshots from Google Cloud statistics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2F658ce416c081452ca1d07167b87e.png%2Fautoorient%2Cresize_fit-1920-1080%2Fmemory_usage.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D49b456489f2bb8ae4daf8f854998f79437193475" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2F658ce416c081452ca1d07167b87e.png%2Fautoorient%2Cresize_fit-1920-1080%2Fmemory_usage.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3D49b456489f2bb8ae4daf8f854998f79437193475" alt="img2"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Usage of memory&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2Ffde4d3ab5b204240b73df1533498.png%2Fautoorient%2Cresize_fit-1920-1080%2Fexecution_time.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3Db6310f28425d492413efddaedcf7d00fa950c68b" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fvault8.io%2Ffde4d3ab5b204240b73df1533498.png%2Fautoorient%2Cresize_fit-1920-1080%2Fexecution_time.png%3Fp%3D0aa930d138b25b338ee16fe%26s%3Db6310f28425d492413efddaedcf7d00fa950c68b" alt="im3"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Request processing speed&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In such a simple way, Riter can be quickly integrated with any third-party service, and its functionality can be extended as far as you need to meet all the specific requirements of your team and workflow.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Project Management Digest, January 2019</title>
      <dc:creator>katglin</dc:creator>
      <pubDate>Sun, 03 Feb 2019 09:31:44 +0000</pubDate>
      <link>https://forem.com/riter/project-management-digest-january-2019-km1</link>
      <guid>https://forem.com/riter/project-management-digest-january-2019-km1</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://riter.co/blog/project-management-digest--january-2019"&gt;Riter.co&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Having a hard time managing teams, projects, or the whole company lately? You’re not alone. This is a pilot release of our monthly project management digest designed to spread experience of many teams and companies among project/product managers. However, we also believe that it will be useful for everybody working in the IT field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Topical articles in accordance with your goals
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Most popular content&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Look thought the most popular articles on project management just to keep up with the latest news in your field of activity.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the post &lt;a href="https://medium.freecodecamp.org/epics-are-dead-heres-what-we-should-do-instead-279bada1e644"&gt;Epics are dead. Here’s what we should do instead&lt;/a&gt; (6 min read) the author makes us think again about epics and describes how he is destroying epics in his Agile practice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find out &lt;a href="https://medium.com/@johnpcutler/15-things-you-should-know-about-product-managers-f488513d246"&gt;15 things you should know about product managers&lt;/a&gt; (12 min read) to be sure you are ready to interact with them properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the post &lt;a href="https://towardsdatascience.com/data-science-project-flow-for-startups-282a93d4508d"&gt;Data science project flow for startups&lt;/a&gt; (20 min read) the author presents and structures the flow of data science projects (mostly for small teams/startups), describes scope &amp;amp; KPIs validation process, shares his experience in running, leading or managing data science projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some tips on managing millennials on project team are provided in the post &lt;a href="https://www.projectmanagement.com/blog-post/36111/Millennials--Your-Best-Resources-for-Project-Management-in-the-World-of-NGOs"&gt;Millennials: your best resources for project management in the world of NGOs&lt;/a&gt; (3 min read).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Most discussed articles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Looking for communication with colleagues instead of dry facts? Join the most intense discussions on topics of interest to you.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/blog-post/50332/3-Levels-of-Project-Quality--Infographic-"&gt;3 levels of project quality (Infographic)&lt;/a&gt; — the infographic, inspired by a book on project management called “Project-Driven Creation” by Jo Bos, Ernst Harting, and Marlet Hesslelink, sets out the three levels of project quality that you can reach for your deliverables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the post &lt;a href="https://www.projectmanagement.com/blog-post/51112/Good-project-managers-wear-many-hats"&gt;Good project managers wear many hats&lt;/a&gt; the author describes several roles of project managers. Can you add any?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/blog-post/51084/Monty-Hall-for-Projects--To-Switch-or-Not-to-Switch-"&gt;Monty Hall for projects: to switch or not to switch?&lt;/a&gt;. And how would you deal with the Monty Hall Problem in the project management environment? Join the discussion to share your ideas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read about project budgeting and contracts in the post &lt;a href="https://dev.to/bertilmuth/project-budgeting-an-anti-pattern-2jn9"&gt;Project budgeting: an anti-pattern&lt;/a&gt;. An author, a scrum master and agile coach, describes anti-patterns which he meets in many companies over and over again.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Most controversial content&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The following group of articles seems to be the most controversial topics on project management and related issues for now. Share your experience and thoughts on them.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The post &lt;a href="https://www.projectmanagement.com/blog-post/50659/It-s-Time-for-a-Long--Hard-Look-at-Processes"&gt;It’s time for a long, hard look at processes&lt;/a&gt; describes the evolution of process as a concept and raises a question: “What should the next edition of the PMBOK Guide look like?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read about &lt;a href="https://dev.to/waterlink/4-rules-of-effective-development-team-meetings-25hn"&gt;9 rules of effective development team meetings&lt;/a&gt; and suggest your own opinion and convictions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some thoughts on Stand-Up meetings and how we could change them: &lt;a href="https://dev.to/justinctlam/time-to-change-how-we-do-stand-ups-503"&gt;Time to change how we do Stand-Ups&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Entertainment materials&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feeling tired after a long day of work? Leave the news and controversial articles for tomorrow. Just view the following entertainment materials for fun and relaxation.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Friday humor, no so funny when it really happens: &lt;a href="https://www.projectmanagement.com/blog-post/50681/Friday"&gt;Friday&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The post &lt;a href="https://medium.com/swlh/your-app-will-be-finished-on-tuesday-which-tuesday-bffaa90d20ca"&gt;“Your app will be finished on Tuesday.” — which Tuesday?!&lt;/a&gt;(7 min read) describes 10 reasons why software development projects fail in a humorous manner and with fun life examples.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A short comix about well-known situation: &lt;a href="https://www.projectmanagement.com/blog-post/50875/As-soon-as-possible--ASAP-"&gt;As soon as possible (ASAP)&lt;/a&gt; and &lt;a href="https://www.projectmanagement.com/blogs/332098/Geplanus--The-PM?userTagIDSort=7850&amp;amp;"&gt;some more&lt;/a&gt; similar stories.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choose the most interesting articles by topics
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Agile, Scrum, and the best PM practices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The post &lt;a href="https://medium.com/xplor8/design-thinking-lean-startup-and-agile-what-is-the-difference-1eed3594b121"&gt;Design thinking, lean startup and Agile: what is the difference?&lt;/a&gt;(3 min read) explains what the terms “Design Thinking”, “Lean Startup” and “Agile” relate to, and how they can be integrated with each other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read about &lt;a href="https://www.trainingzone.co.uk/community/blogs/tenoshahblogger/agile-development-training-tips-to-follow-in-2019"&gt;Agile development training tips to follow in 2019&lt;/a&gt; to improve your business processes, management, and teamwork.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find out how to hire right people for your Scrum team in the post &lt;a href="https://it.toolbox.com/blogs/dennisstevenson/4-things-to-check-out-before-hiring-for-your-scrum-team-012519"&gt;4 things to check out before hiring for your Scrum team&lt;/a&gt;. What kind of person do you want?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time management, KPIs, and productivity&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Know whether you’re using story points properly with the post &lt;a href="https://www.projectmanagement.com/blog-post/51012/Debunking-4-Misconceptions-About-Story-Points"&gt;Debunking 4 misconceptions about story points&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Several tips and links to tell you &lt;a href="https://ceoworld.biz/2019/01/30/how-to-double-your-teams-productivity/"&gt;How to double your team’s productivity&lt;/a&gt; from Adrian Shepherd, the creator of “The One-Bite Time Management System” (TMS).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are challenges most companies come across while implementing project management KPIs. In the post &lt;a href="https://www.projectmanagement.com/blog-post/51106/5-Challenges-in-Implementing-Project-Management-KPIs"&gt;5 challenges in implementing project management KPIs&lt;/a&gt; the author explains what these challenges are and how to overcome them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/blog-post/50773/Do-You-Know-The-3-Drivers-Of-Project-Success--"&gt;Do you know the 3 drivers of project Success?&lt;/a&gt; Read the post for details or join the discussion to add something.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Teamwork and collaboration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A short article about &lt;a href="https://www.trainingzone.co.uk/community/blogs/questld/training-for-flexibility-and-teamwork"&gt;Training for flexibility and teamwork&lt;/a&gt; written by Helen Green, a collaborator, a deadline demon and a diplomat.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/nimmo/cant-we-just-56g"&gt;“Can’t we just...?”&lt;/a&gt; — about teamwork, collaboration, and using the terminology by your teammates.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Managing teams and companies&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Still working at office? Make sure you understand all the reasons and advantages of remote work (and know how to manage remote teams) described in the post &lt;a href="https://ceoworld.biz/2019/01/29/top-5-benefits-of-remote-working/"&gt;Top 5 benefits of remote working&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For company owners and team leads, it will be useful to know &lt;a href="https://www.weforum.org/agenda/2019/01/jobs-of-next-20-years-how-to-prepare/"&gt;What the next 20 years will mean for jobs — and how to prepare&lt;/a&gt; — make sure you’re looking for the right future workforce.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the post &lt;a href="https://it.toolbox.com/blogs/peterkowalke/6-tips-for-cutting-it-costs-013119"&gt;6 tips for cutting IT costs&lt;/a&gt; the author provides some ways to reduce IT costs for business. “Every business is now a digital business. This doesn’t mean that IT costs must keep rising, however”, he says.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The best resources for project managers
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A huge number of novice project managers often ask about good resources and appropriate communities for obtaining experience and sharing knowledge with colleagues. We have compiled such a selection that will be equally interesting to both professionals and novice specialists, and whoever just interested in this area.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.projectmanagement.com/"&gt;Projectmanagement.com&lt;/a&gt; — maybe one of the most large-scale resources with discussions, blogs, huge knowledge base, events, templates, webinars, tools, and all the rest for project managers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.manager-tools.com/"&gt;Manager Tools&lt;/a&gt; — this website offers free weekly podcasts, contains a forum, and some informational content (events, tools, reviews, links) for project managers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.agilealliance.org/"&gt;Agile Alliance&lt;/a&gt; — a nonprofit community focused on exploring and applying Agile values, principles, and practices of software development. The website contains a great number of resources (reports, articles, educational materials), upcoming events, news, and a list of community groups around the world.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Currently active PM podcasts&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://www.project-management-podcast.com"&gt;PM Podcast&lt;/a&gt; — hosted by Cornelius Fichtner, the podcast already includes 427 (mostly free) episodes for project managers, both beginners and experts. Episodes are usually released weekly (sometimes even more often) and last up to 30 minutes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ricardo-vargas.com/podcasts/"&gt;5 Minutes Project Management Podcast&lt;/a&gt; — 455 episodes (organized into 15 playlists by topics) published by Ricardo Vargas and focused on project, portfolio, and risk management (each 5 to 10 minutes long). Episodes are released at intervals of 1 to 4 weeks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://pmforthemasses.com/"&gt;PM for the Masses Podcast&lt;/a&gt; — hosted by Cesar Abeid, the podcast already includes about 118 episodes about general project management concepts, based mostly on interviews with project management and productivity gurus, personal stories of success or failures, and own author’s tips and experience. Episodes last 20-60 minute and are released about once a week.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Author blogs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.joelonsoftware.com/"&gt;Joel on Software&lt;/a&gt; — the blog includes about 1114 articles about software development, management, and business written by Joel Spolsky, CEO and co-founder of Stack Overflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.mountaingoatsoftware.com/"&gt;Mountain Goat Software&lt;/a&gt; — a lot of posts since 2005 about estimating, main Agile and Scrum concepts, planning, leadership, etc., prepared for product owners, managers, and scrum masters. In addition to blog posts, the website includes presentations, videos, and much more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://theaccidentalpm.com/"&gt;The Accidental Product Manager&lt;/a&gt; hosted by Dr. Jim Anderson, the blog tells how to develop, launch, and manage products. The author analyzes and interprets examples of project management success and failures from companies around the world, shares own 25+ years experience and practices of applying project management skills to products at different stages of development.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Telegram groups/channels&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://telegram.me/PrMaB"&gt;PrMaB&lt;/a&gt; — the channel publishes a great number of actual project management materials (books, articles, reports, events, etc.), mostly in PDF format, as a rule, several posts daily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reddit communities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.reddit.com/r/projectmanagement/"&gt;Projectmanagement&lt;/a&gt; — the biggest Reddit community dedicated to project management with a focus on the software development area with 17,000+ subscribers and no commercial promotion allowed. The community includes all about PM: discussion, success and failures stories, educational materials, news, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.reddit.com/r/productivity/"&gt;Productivity&lt;/a&gt; — a more general community, however, with a lot of useful content for managers: planning and time tracking, communication, co-working, team building, meetings, deadlines, and so on. Open to sharing your views and experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.reddit.com/r/agile/"&gt;Agile&lt;/a&gt; — one more community for project managers, scrum masters, and most software development teams in general following Agile principles. A great number of subscribers makes it a good place to ask questions and share your experience in Agile, Scrum, Extreme Programming, Lean, Kanban, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dev.to communities&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At Dev.to posts are written by many different authors around the world that makes them quite diverse and frequent.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For example, the &lt;a href="https://dev.to/t/productivity"&gt;Productivity&lt;/a&gt; content includes posts about holding effective meetings, ways of improving team productivity, open source time tracking software, and much more things not only for project managers, but also for the whole software development team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Posts about &lt;a href="https://dev.to/t/agile"&gt;Agile&lt;/a&gt;, &lt;a href="https://dev.to/t/startup"&gt;Startups&lt;/a&gt;, &lt;a href="https://dev.to/t/leadership"&gt;Leadership&lt;/a&gt;, and &lt;a href="https://dev.to/t/management"&gt;Management&lt;/a&gt; will be interesting for project and product managers, team leads, product owners, and entrepreneurs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://dev.to/t/softskills"&gt;Softskills&lt;/a&gt;, &lt;a href="https://dev.to/t/teamwork"&gt;Teamwork&lt;/a&gt; and &lt;a href="https://dev.to/t/team"&gt;Team&lt;/a&gt; topics will be useful for a wider audience, including articles about time estimation, communication with the team, managers and customers, problems with deadlines, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Medium publications&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Take a look at the &lt;a href="https://medium.com/swlh"&gt;Startup&lt;/a&gt; publication designed for product owners, managers, team leads, and all other software development enthusiasts. A wide range of topics includes collaboration, time management, communication with customers, software development, and much more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://productcoalition.com/"&gt;ProductCoalition&lt;/a&gt; — the world’s largest free product management community with 2,000+ articles, 300+ writers, and 2,700+ members in their &lt;a href="http://productcoalitionslack.herokuapp.com/"&gt;Slack community&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://entrepreneurshandbook.co/"&gt;Entrepreneurs Handbook&lt;/a&gt; — another popular Medium publication about startups, entrepreneurship processes, tech innovations, and management with frequent updates and many authors.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Have something to share?
&lt;/h2&gt;

&lt;p&gt;Are there any particular challenges you’ve faced on your own? Or opinions you’d like to address to the community? Feel free to share your own blog posts with us and we’ll add the most valuable articles in the next digest. Or you can do it directly in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s make this digest better
&lt;/h2&gt;

&lt;p&gt;What other content and resources you’d like to see here? Please share your favorite communities and thoughts in the comments or write to us &lt;a href="//mailto:kate@cimon.io"&gt;directly&lt;/a&gt;. We’d like to make this digest better with your help. Do not be limited to typical project management topics and feel free to suggest any new ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who we are and why we’re writing here
&lt;/h2&gt;

&lt;p&gt;We’re a software development team with own, mostly successful, experience in project and remote team management. However, this is only a drop in the ocean compared with the experience of thousands of teams and companies around the world. Therefore, we prefer to collect and summarize existing examples and mistakes instead of imposing our own views on you. We definitely won’t tell you about our projects, and much less promote something here. However, you can share your own projects with us and own readers as examples of your success stories, instructive or fun experiences, and innovative ideas.&lt;/p&gt;




&lt;p&gt;We hope you’ve found something interesting here. If so, we will appreciate if you subscribe and share the post with your colleagues. Wait for more resources and articles in the next digest in a month.&lt;/p&gt;

</description>
      <category>projectmanagement</category>
      <category>agile</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Cursed custom selects</title>
      <dc:creator>Alexey Osipenko</dc:creator>
      <pubDate>Tue, 18 Sep 2018 11:32:46 +0000</pubDate>
      <link>https://forem.com/riter/cursed-selects-1ckn</link>
      <guid>https://forem.com/riter/cursed-selects-1ckn</guid>
      <description>&lt;p&gt;When a design requires something special from inputs, layout designers crouch in the twine and do pretty crazy things, like an image inside the input, but still leave the tag &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; on the page. However, if suddenly it comes to styles of a drop-down list, the tag &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; along with a set of &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; tags go to the dump, and then &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and javascript appear to take their place. Well, you are not allowed to customize selects in browsers even with the latest html/css, and that's very sad!&lt;/p&gt;

&lt;p&gt;A traditional select is awesome, it is opened with "Cmd+down", closed with "Esc", it supports search (just open a select and start typing), and nothing of this, as a rule, cannot be done by all your custom selects. Just because a design developer, working on this component, has not gotten around to that yet.&lt;/p&gt;

&lt;p&gt;Of course, there are some successful solutions, for example, custom selects from bootstrap, jQuery, and similar and famous React.js component. But even in these cases the number of leaky abstractions is not zero, but simply less than in other analogs. If you think that you know an example that proves the opposite, where a set of divs behaves exactly the same as an original select and no abstraction leaks, then you should immediately remember about autocomplete of forms in browsers or about long drop-down lists and low browser windows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rlqj-6CL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/f95b20ebe15c40f9bd6658aacf91.jpg/autoorient%2Cresize_fit-1920-1080/1.jpg%3Fp%3D0aa930d138b25b338ee16fe%26s%3Dfe3f4586fd85ea1e9884b07a5be69b8c77f7795f" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rlqj-6CL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://vault8.io/f95b20ebe15c40f9bd6658aacf91.jpg/autoorient%2Cresize_fit-1920-1080/1.jpg%3Fp%3D0aa930d138b25b338ee16fe%26s%3Dfe3f4586fd85ea1e9884b07a5be69b8c77f7795f" alt="1.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the way, when browsers were young Internet Explorer only dreamed of the seventh version and Chrome did not exist at all, the selects were even more independent. Some browsers, apparently due to some internal limitations, refused to implement drop-down lists in standard ways and tricks were used instead. Such clumsy unpolished heavy kludges. There could be no question of additional styles for selects at all, there were much more serious problems. For example, no div with an absolute position and an increased z-index could have select-input components inside just because a drop-down list was not a part of the document. All selects were rendered separately from the whole document and, in fact, atop of the document. If you suddenly wanted to create something like a modal window, then with help of an extra javascript code you could apply such a ninja trick: while opening any modal div-element, all drop-down lists on the page were updated with &lt;code&gt;visibility: hidden&lt;/code&gt;. You could also notice on slow computers that calculation of the select's position were lagged a little behind when scrolling a page. Select calculated its position a bit later than a page itself did that, and moved with a slight delay.&lt;/p&gt;

&lt;p&gt;Currently we're working on custom selects.&lt;/p&gt;

&lt;p&gt;In that one, which is "multi", I still tried to implement a proper work with a keyboard, but I dumped it when it came to a usual select. If you try to copy the behavior of a native element, you can waste a week, while nobody ever estimates a div with a text and another div with a drop-down list at a whole week.&lt;/p&gt;

&lt;p&gt;And you should not forget about custom selects on mobile devices. This is a separate pain for the user, and native selects are completely different anything else, take, at least, those iOS "drums". And, surely, the user won't be delighted with custom designer's garbage.&lt;/p&gt;

&lt;p&gt;Another interesting idea is of the evolution of native controls. Take, for example, the scrollbars. In the past, we had bags and bags of libraries implementing custom scrollbars. And if at that time a designer could not resist the temptation to add a little beauty to his creation, now his scroll, no matter how cool it was before, will look pretty dull against the backdrop of neat, sometimes even self-removing in passive state native scrolls. Of course, no design lives for so long, but it's great to always keep in mind the possibility of browser evolution when working on a design.&lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>design</category>
      <category>webdev</category>
    </item>
    <item>
      <title>SQL tests in your smart framework</title>
      <dc:creator>Alexey Osipenko</dc:creator>
      <pubDate>Mon, 10 Sep 2018 06:55:09 +0000</pubDate>
      <link>https://forem.com/riter/sql-tests-in-your-smart-framework-48ob</link>
      <guid>https://forem.com/riter/sql-tests-in-your-smart-framework-48ob</guid>
      <description>&lt;p&gt;Many complain that sql-code, in one form or another, becomes unsupported very quickly and, in fact, it is not even worth starting to write any logic in sql. "Use sql only as tables, and implement all the logic inside the code", they say not entirely without some slyness at the same time.&lt;/p&gt;

&lt;p&gt;Well, tell me, some specific data types are nevertheless permissible in sql, aren't they? For example, is it considered shameful to use enum instead of string, or it is still acceptably? It seems to be permitted even in the eyes of ardent defenders of code purity. And what about using of a floating point numbers of certain accuracy (precision and scale) - is it still eligible? Or reasonable default values like &lt;code&gt;created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP&lt;/code&gt;, we are allowed to use them so far, right?&lt;/p&gt;

&lt;p&gt;If answers for all questions above are positive, then most likely we can also add data validation to be sure that the format of, say, a string is met. We mean, for example, checking the format of the email address, absence of dangerous characters in fields-identifiers of the address bar (&lt;code&gt;slug&lt;/code&gt; or &lt;code&gt;uid&lt;/code&gt;), or even validation of uniqueness in a specific subset of records (&lt;code&gt;scope&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;And then it's a short road to stored procedures. Let's say, you have a 'name' field, and you want to make sure that the value of this field is unique without matching case. It's a typical task, right? Traditional MVP-frameworks say that verification should be done in advance, like &lt;code&gt;SELECT 1 FROM table_name WHERE lower(name) = $1&lt;/code&gt;, and if a record is present, then saving process mustn't even be started. A database, as a concept, tells us that it should be checked at the time of recording. In some cases constraints on tables come to our rescue, in others we can apply rules and triggers. In general, it would be much more convenient to do that with a database, except, support of the written code becomes more complicated.&lt;/p&gt;

&lt;p&gt;In our project &lt;a href="https://riter.co"&gt;riter.co&lt;/a&gt; we came up with some simple rules, adhering to which you can worry about sql-code no more than about some other piece of your project, for example javascript-code or css-rules.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It is necessary to clearly &lt;strong&gt;distinguish cases&lt;/strong&gt; when you create a rule, a trigger, and a constraint. Rules should be better defined on your own in a separate project, but anyway you will certainly follow some priority of creation. Like, if it is possible to create a constraint, then create a constraint. If not, you should create a rule. As a last resort, if even a rule is not suitable in your case, that use a trigger.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standards for naming&lt;/strong&gt; triggers and rules. If you want to create some sort of a trigger on the table, the name must be uniquely defined. I propose to begin thinking about this naming rule with &lt;code&gt;"#{table_name}_#{affected_colum_name}_#{verb}_on_#{action}"&lt;/code&gt;. You will get something like &lt;code&gt;"companies_subdomain_lowercase_on_insert"&lt;/code&gt;. In addition, then it will be much easier to find existing rules with the mask:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;routine_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routines&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="k"&gt;routine_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'companies_%_on_insert'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
    &lt;span class="n"&gt;routine_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'FUNCTION'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
    &lt;span class="n"&gt;specific_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;KISS. All procedures in your database &lt;strong&gt;must be simple&lt;/strong&gt;. Complex things result in complex bugs, and bugs at the level of the database are quite scary. Let's keep them simple. It is better to leave complicated logic to high-level programming languages with their frameworks. For example, we can convert everything to a lowercase or generate a unique identifier inside the database, but when it is about checking the intersection of time intervals, it is better, probably, to do that in the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tests&lt;/strong&gt;. Actually, the entire post is written for the sake of this point, and all that is said above is more like a preamble.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests should be presented in three ways, as always: integration, unit and acceptance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit-tests&lt;/strong&gt; have much less sense and in the overwhelming majority they must be written in a high-level code. It's a good practice to write unit tests for some single procedures independent of the current state of the database. For example, we have such a procedure that looks very nice in the context of unit tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;character&lt;/span&gt; &lt;span class="nb"&gt;varying&lt;/span&gt;
    &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;
    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
      &lt;span class="k"&gt;DECLARE&lt;/span&gt;
        &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;found&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;BEGIN&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SELECT slug FROM '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;quote_ident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' WHERE slug = '&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;quote_literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;found&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="n"&gt;WHILE&lt;/span&gt; &lt;span class="k"&gt;found&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;
            &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_random_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'base64'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;quote_literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;found&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By and large, it's impossible to implement a full mock and stub, so real unit-tests can not be written either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration tests&lt;/strong&gt; should be written in the language of the framework chosen for the project, and they must check that all your sql-triggers' actions are reflected in the objects and structures of your program. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'CamelCase'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;its&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'camelcase'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firstly, it will check the properly working &lt;code&gt;RETURNING name&lt;/code&gt;. Secondly, it will test work of your framework and its willingness to cooperate with the database. For example, if your framework is ready to monitor the correctness of data in some fields, then such tests can reveal a conflict and it can be resolved in time.&lt;/p&gt;

&lt;p&gt;What about &lt;strong&gt;acceptance-tests&lt;/strong&gt;, that's where things get interesting. After all, it really does not matter what exactly makes all letters from the name field lowercase, whether triggers, or rules. It is much more important to be convinced of the results.&lt;/p&gt;

&lt;p&gt;First, you need to agree on the structure. We have considered several options and reached the most reasonable one - for each individual test we create a separate function, then we call it and check the return value. Of course, we want these tests to run along with all the other tests, so we use a small wrapper with our frameworks.&lt;/p&gt;

&lt;p&gt;Here's the test itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;()"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ok'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's terribly simple, isn't it? Certainly, before running the test, you need to create this function. We do this right before the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CREATE OR REPLACE FUNCTION &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;() RETURNS varchar LANGUAGE plpgsql AS $$ &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; $$;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after the test we immediately delete the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DROP FUNCTION IF EXISTS &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;()"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StatementInvalid&lt;/span&gt;
  &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now about content of this function. The most convenient solution is to keep the content in separate files with the &lt;code&gt;.sql&lt;/code&gt; extension and read it before creating the sql-function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;    &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'**/*_spec.sql'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
      &lt;span class="n"&gt;basename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_path_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'spec'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sql'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
                         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'__'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_spec.sql\z/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;OpenStruct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;empty?: &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;basename: &lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;function_name: &lt;/span&gt;&lt;span class="s2"&gt;"rspec_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A name of the function, that is &lt;code&gt;function_name&lt;/code&gt;, can be generated from the file name with some authentic prefix, so that you could distinguish test-function from all other functions. Then you will be able to make sure that there is nothing superfluous left in the database once more before and after running all the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
  &lt;span class="k"&gt;routine&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;routine&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;routine_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routines&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt;
      &lt;span class="k"&gt;routine_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'rspec_%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
      &lt;span class="n"&gt;routine_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'FUNCTION'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
      &lt;span class="n"&gt;specific_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="n"&gt;LOOP&lt;/span&gt;
    &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="s1"&gt;'DROP FUNCTION '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;quote_ident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;routine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the test itself is a matter of technique. Here is, for example, a separately saved &lt;code&gt;company_name_spec.sql&lt;/code&gt;, which will check the uniqueness of the &lt;code&gt;name&lt;/code&gt; field in the &lt;code&gt;companies&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt;
  &lt;span class="n"&gt;company_name&lt;/span&gt; &lt;span class="n"&gt;companies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;

  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;companies&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'qwe1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;ASSERT&lt;/span&gt; &lt;span class="n"&gt;company_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'qwe1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'companies.name is allowed to be a string'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;companies&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'qwe1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ASSERT&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'companies.name should raise unique violation'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;EXCEPTION&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;unique_violation&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
      &lt;span class="n"&gt;ASSERT&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="s1"&gt;'ok'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of conclusions, I would like to say that an intelligent database is the very place that must be thoroughly covered with tests. And leave your excuses in the past, now you know how to write them.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>tests</category>
    </item>
    <item>
      <title>SQL on Rails concept</title>
      <dc:creator>Alexey Osipenko</dc:creator>
      <pubDate>Mon, 30 Jul 2018 08:05:58 +0000</pubDate>
      <link>https://forem.com/riter/sql-on-rails-concept-1f5m</link>
      <guid>https://forem.com/riter/sql-on-rails-concept-1f5m</guid>
      <description>&lt;p&gt;First of all, this article is not about how much I love Rails. And secondly, it is not about how I hate it. We can treat Rails quite differently and it will become better only if we change something. But also, Rails can get worse just if we try to make corrections to it. Well, anyway, you've been warned and I hope you got me.&lt;/p&gt;

&lt;p&gt;One of the basic concepts of ActiveRecord is that the database is quite utilitarian in nature and can be easily modified. So, there you are sitting and writing your models for MySQL, and suddenly you find out somewhere that it's that simple to just up and replace MySQL with MongoDB. Well, not so drastically, but, say, you may have reasons to replace MySQL with PostgreSQL. Or vice versa, I have nothing against MySQL.&lt;/p&gt;

&lt;p&gt;And here ActiveRecord claims to come in handy, since supposedly it's a piece of cake for it. After all, scopes, before/after filters and associations are abstract enough not to worry about generating queries on databases and focus just on the logic of the application. Instead of writing &lt;code&gt;WHERE is_archived = TRUE&lt;/code&gt;, you can easily write &lt;code&gt;where(is_archived: true)&lt;/code&gt; and ActiveRecord will do the rest for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.pinimg.com%2Foriginals%2Fa1%2Fc1%2F9c%2Fa1c19cc429092ebb57ee218f34d7a525.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.pinimg.com%2Foriginals%2Fa1%2Fc1%2F9c%2Fa1c19cc429092ebb57ee218f34d7a525.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it's not really as simple as all that! In practice, it turns out that this layer of abstraction is full of gaps, like a broken trough from the tale of the Golden Fish. And that many basic features can not be used, like comparing dates or working with arrays. And we have got scopes with forced &lt;code&gt;where("#{quoted_table_name}.finished_at &amp;gt;= ?", Date.current)&lt;/code&gt; or &lt;code&gt;where("#{quoted_table_name}.other_ids &amp;lt;@ ARRAY[?]", ids)&lt;/code&gt;. To which ActiveRecord gives a completely conscious and expected answer: do not use this. Instead of arrays, use habtm-association, and if you need to compare dates, accept this. And God forbid, you miss &lt;code&gt;quoted_table_name&lt;/code&gt; in such a scope - the first &lt;code&gt;includes&lt;/code&gt; or &lt;code&gt;joins&lt;/code&gt; will put everything in its place. It's easier to add them wherever and whenever possible, so that you do not lose the skill.&lt;/p&gt;

&lt;p&gt;And, of course, once you decide on such an interference in the work of ActiveRecord, there's no going back. Not only chances, but also a vague hope for a painless transition to another database will be gone. It will be much better to print this code out and burn it. And surely, there is no other reason not to use extra-database capabilities in your application. You're welcome to use and make everybody do!&lt;/p&gt;

&lt;p&gt;And when it turns out that use of extra-opportunities is more than half of your scopes in the models folder, it will be quite obvious that ActiveRecord is just a convenient wrapper for integrating one labeled piece of code with another piece of code. And scopes like &lt;code&gt;where(is_archived: true).joins(:sprint).merge(Sprint.archived)&lt;/code&gt; will work fine and their combining won't be much more difficult than cooking eggs, will it?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fgifs.com.ua%2Fuploads%2Fgifs-com-ua-736459401.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fgifs.com.ua%2Fuploads%2Fgifs-com-ua-736459401.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next stage is denormalization. Of course, denormalization has always been and has not disappeared anywhere, but taking care of it was placed on mighty shoulders of Rails and ActiveRecord, and you know, that these two guys don't suffer from excessive lightness and ascetic in their resource requirements. Let's say, &lt;code&gt;counter_cache: true&lt;/code&gt; is the first step to denormalization, after all, ActiveRecord won't let you just do &lt;code&gt;COUNT(*) AS sprints_count&lt;/code&gt; so easily (I mean, you're not going to use &lt;code&gt;select&lt;/code&gt; method, are you?). And &lt;code&gt;counter_cache&lt;/code&gt; is far from ideal and in some cases there may be a desynchronization of the real quantity from the cached one. Not critical, of course, but unpleasant. And this is only the first candidate to settle in the database and not load the already burden head of the Ruby machine. Just a couple of triggers and it's done! When deleting and adding an entry to the A-table, you need to recalculate the number of records in the B-table and that's all, right? And, of course, you do the same when editing an entry if &lt;code&gt;foreign_key&lt;/code&gt; has been changed, so as the request &lt;code&gt;UPDATE B SET a_id = $1 WHERE id = $2&lt;/code&gt; will break &lt;code&gt;counter_cache&lt;/code&gt; down both for the old and new tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;update_&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parent_table&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;child_table&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;_counter_on_insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parent_table&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;counter_column&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;child_table&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;foreign_column&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;foreign_column&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;foreign_column&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parent_table&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;foreign_column&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next piece of work with a database will be related to the date-time. First, let the &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; fields be serviced in the database itself, fortunately, it's much simpler. Primarily, let's set default values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;change_column_default&lt;/span&gt; &lt;span class="ss"&gt;:table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'CURRENT_TIMESTAMP'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;change_column_default&lt;/span&gt; &lt;span class="ss"&gt;:table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:updated_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'CURRENT_TIMESTAMP'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to do this everywhere at once, we can organize a cycle throughout all tables, where these fields are present. Except of the &lt;code&gt;schema_migrations&lt;/code&gt; and &lt;code&gt;ar_internal_metadata&lt;/code&gt; tables, certainly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sx"&gt;%w(schema_migrations ar_internal_metadata)&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all, now the default values for these tables will be exactly the same as we need. And now it's time to  make sure that Rails won't touch these fields. There is an option in the configuration of the framework, which is responsible for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_timestamps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, the next step is to change the &lt;code&gt;updated_at&lt;/code&gt; field when a record is changed. That's simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;touch_for_&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;_on_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to completely get rid of &lt;code&gt;touch: true&lt;/code&gt; in models. This thing is very similar to the target in the shooting gallery - it's also completely leaky. And I won't even explain why, because you already know all these cases. This is not much more difficult, you just need to update &lt;code&gt;updated_at&lt;/code&gt; where necessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;touch_for_&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;_on_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;foreign_table_name&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foreign_column_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, the sequence of calls of such triggers will cause extra actions, but Postgres doesn't provide any sane mechanism to call triggers without changing the record itself. You could try to write &lt;code&gt;SET title = title&lt;/code&gt; but this is hardly ever better than &lt;code&gt;SET updated_at = CURRENT_TIMESTAMP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Exactly the same trigger serves for insertion, just &lt;code&gt;update_at&lt;/code&gt; is not necessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;touch_for_&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;_on_insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;foreign_table_name&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foreign_column_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Certainly, you could try to write this with one function by adding a check on the current event directly into the trigger like &lt;code&gt;IF TG_OP = 'UPDATE' THEN&lt;/code&gt;, but it is preferable to make all triggers as simple as possible, in order to reduce the probability of error.&lt;/p&gt;

&lt;p&gt;You might want to somehow automate the generation of such triggers, and then you will most likely need to find all foreign relations between the current table and the rest ones. Here you can easily do that with this request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ccu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;foreign_table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kcu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;column_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;column_name&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_constraints&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;
    &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key_column_usage&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;kcu&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kcu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt;
    &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;constraint_column_usage&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ccu&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ccu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;constraint_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'FOREIGN KEY'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'#{table_name}'&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ccu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One more very useful tip. Name all triggers from a template to be able to verify the presence or absence of a required trigger with a single request. For example, this query will find all touch-insert triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;routine_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routines&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="k"&gt;routine_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'touch_for_%_on_insert'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
    &lt;span class="n"&gt;routine_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'FUNCTION'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
    &lt;span class="n"&gt;specific_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the last thing left is the most terrible. The fact is that Rails is not designed for at least a marginally smart database and it definitely doesn't care that something can be changed there, except ID-field, and even then only when inserting data into the table. Therefore, there is no sane way to add &lt;code&gt;RETURNING id, updated_at&lt;/code&gt; to update-queries, you will need to dive headlong into Rails thicket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Frd55kvyvbl4bicpgi0e6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Frd55kvyvbl4bicpgi0e6.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monkey patch turned out not so much neat, but primarily the goal was to minimize contravention of the current work of the framework as much as possible. Here's how it looks in the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ActiveRecord&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Persistence&lt;/span&gt;
    &lt;span class="c1"&gt;# https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L729-L741&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_create_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attribute_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;attribute_names&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column_names&lt;/span&gt;
      &lt;span class="n"&gt;attributes_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attributes_with_values_for_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;an_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;affected_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_insert_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes_values&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;an_id&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;primary_key&lt;/span&gt;
      &lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom_returning_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quoted_table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attribute_types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="vi"&gt;@new_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;

      &lt;span class="nb"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="ss"&gt;:_create_record&lt;/span&gt;

    &lt;span class="c1"&gt;# https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L710-L725&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_update_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attribute_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;attribute_names&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column_names&lt;/span&gt;
      &lt;span class="n"&gt;attribute_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attributes_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attribute_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
        &lt;span class="n"&gt;affected_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="vi"&gt;@_trigger_update_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;affected_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_update_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@_trigger_update_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom_returning_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quoted_table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attribute_types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;

      &lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt; &lt;span class="ss"&gt;:_update_record&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ConnectionAdapters&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;PostgreSQL&lt;/span&gt;
      &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;DatabaseStatements&lt;/span&gt;
        &lt;span class="c1"&gt;# https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb#L93-L96&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
          &lt;span class="n"&gt;execute_and_clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql_with_returning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb#L147-L152&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
          &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_sql_and_binds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;exec_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence_name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;insert&lt;/span&gt;

        &lt;span class="c1"&gt;# https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb#L98-L111&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sql_for_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# :nodoc:&lt;/span&gt;
          &lt;span class="n"&gt;table_ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_table_ref_from_insert_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
            &lt;span class="c1"&gt;# Extract the table from the insert sql. Yuck.&lt;/span&gt;
            &lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;table_ref&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="n"&gt;returning_columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quote_returning_column_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;returning_columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
            &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; RETURNING &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;returning_columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

          &lt;span class="k"&gt;super&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# No source in original repo&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;quote_returning_column_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;returning_columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
          &lt;span class="n"&gt;returning_columns&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suppress_composite_primary_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;returning_columns&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom_returning_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;returning_columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;quote_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# No source in original repo&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sql_with_returning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;table_ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_table_ref_from_update_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="n"&gt;returning_columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quote_returning_column_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;returning_columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; RETURNING &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;returning_columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# No source in original repo&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_table_ref_from_update_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/update\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*set/im&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="no"&gt;Regexp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important thing is that here we turn to &lt;code&gt;ApplicationRecord.custom_returning_columns&lt;/code&gt; to find out which columns, besides id, we are interested in. And this method looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;custom_returning_columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'"schema_migrations"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'"ar_internal_metadata"'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;
      &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ss"&gt;:updated_at&lt;/span&gt;

      &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;table_ref&lt;/span&gt;
             &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'"user_applications"'&lt;/span&gt;
               &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:api_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
             &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s1"&gt;'"users"'&lt;/span&gt;
               &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:session_salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password_changed_at&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
             &lt;span class="c1"&gt;# ...&lt;/span&gt;
             &lt;span class="k"&gt;else&lt;/span&gt;
               &lt;span class="p"&gt;[]&lt;/span&gt;
             &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;res&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All examples are given for PostgreSQL, not for MySQL, so MySQL followers will have to reinvent their own wheel.&lt;/p&gt;

&lt;p&gt;Instead of conclusions, we could say that now the aching Rails's head will hurt a little less. Such routine processes as &lt;code&gt;counter_cache&lt;/code&gt; and &lt;code&gt;touch&lt;/code&gt; are going to sink into oblivion, and the next article will be focused on something more global, like removing hanging spaces, validating data, cascade data deletion, or paranoid removal and rspec for sql. If this article proves to be interesting, of course.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>sql</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
