├── bin ├── publish_recent_changes_production.sh ├── andrzej_blog.sh └── new_post ├── package.json ├── posts ├── 2020-04-27-autonomy---the-best-friend-of-async-and-remote.md ├── 2021-04-20-post-to-check-markdown.md ├── 2021-09-28-application-level-locks.md ├── 2021-03-25-stuff-youll-miss-outside-ruby.md ├── 2021-09-27-processing-webhooks:-from-yolo-to-reliability.md ├── 2019-07-10-ruby-refactoring-tennis-game-kata.md ├── 2021-01-29-implementing-crm-with-rails-and-rails-event-store.md ├── 2021-03-17-async-processing-of-events-in-order---the-holy-grail-of-sql-backed-event-store.md ├── 2020-07-27-against-dry-dot-challenging-rails-dogmas-dot.md ├── 2014-07-09-rubymine-basic-navigation-features.md ├── 2014-10-03-behind-the-scenes.md ├── 2019-06-07-how-many-ruby-programmers-are-there-in-the-world.md ├── 2021-02-01-x-ways-to-improve-transparency-of-your-work.md ├── 2021-01-18-10x-developer-destructured.md ├── 2017-08-31-database-url-examples-for-rails-db-connection-strings.md ├── 2015-10-28-slack-driven-blogposts.md ├── 2020-07-17-faces-of-tech-debt.md ├── 2021-10-28-ways-of-communication-that-are-killing-your-team.md ├── 2020-05-05-overcome-10k-rows-database-limit-on-heroku-by-upgrading-the-plan.md ├── 2016-11-24-educate-about-ddd-slash-cqrs-slash-event-sourcing-at-the-facebook-group.md ├── 2017-07-17-non-coding-activites-in-a-software-project.md ├── 2022-06-10-rails-integration-tests-without-service-objects-and-commands.md ├── 2021-03-16-comparison-of-event-serialization-methods.md ├── 2023-01-04-summary-event-pattern.md ├── 2017-06-01-handling-svg-images-with-refile.md ├── 2021-10-05-setting-up-mutant-on-a-huge-test-suite.md ├── 2016-11-21-dealing-with-randomly-failing-tests-from-a-team-perspective.md ├── 2014-11-15-rails-refactoring-dot-com-podcast-number-1.md ├── 2016-02-09-from-legacy-to-ddd-what-are-those-events-anyway.md ├── 2023-01-02-effortless-debugging-with-those-4-linking-classes-from-railseventstore.md ├── 2015-02-27-the-reasons-why-programmers-dont-blog.md ├── 2015-05-03-from-rails-to-haskell-and-yesod.md ├── 2016-11-06-rails-and-adapter-objects-different-implementations-in-production-and-tests.md ├── 2017-02-06-a-potential-problem-with-pstore-and-rails.md ├── 2016-06-24-rails-refactoring-podcast-6-frontend-friendly-rails.md ├── 2022-11-14-be-careful-with-turbo-and-view-components.md ├── 2016-02-22-private-classes-in-ruby.md ├── 2017-05-25-passive-aggresive-events-code-smell.md ├── 2021-01-11-how-well-rails-developers-actually-test-their-apps.md ├── 2021-04-28-rails-console-trick-i-had-no-idea-about.md ├── 2014-08-19-how-we-structure-our-front-end-rails-apps-with-react-dot-js.md ├── 2016-10-24-running-bash-command-from-ruby-with-your-bash-profile.md ├── 2017-05-24-self-hosting-event-store-on-digital-ocean.md ├── 2021-04-14-how-to-delete-jobs-from-sidekiq-retries.md ├── 2015-07-06-am-i-ignored-in-my-async-team.md ├── 2016-01-22-drop-this-before-validation-and-use-method.md ├── 2016-07-08-always-present-association.md ├── 2017-09-29-which-ruby-version-am-i-using-how-to-check.md ├── 2017-10-06-rails-how-to-find-records-where-column-is-not-null-or-empty.md ├── 2016-11-09-ruby-exceptions-are-4400-times-faster-than-activerecord-base-number-create.md ├── 2020-04-02-the-truth-about-rails-convention-over-configuration.md ├── 2013-05-13-the-a-team.md ├── 2017-01-02-on-upcoming-immutable-string-literals-in-ruby.md ├── 2023-11-20-who-calls-who-a-simple-events-heuristic.md ├── 2017-10-29-a-bug-that-only-appears-once-a-year.md ├── 2016-06-10-see-how-we-create-books-live.md ├── 2021-04-28-decorate-your-runner-session-like-a-pro.md ├── 2025-04-03-rails-when-nothing-changed-is-the-best-feature.md ├── 2012-12-03-why-we-dont-use-orm.md ├── 2013-11-21-chronos-and-kairos.md ├── 2014-06-16-async-standups.md ├── 2015-03-06-you-get-feature-toggle-for-free-in-event-driven-systems.md ├── 2017-10-07-how-to-add-a-default-value-to-an-existing-column-in-a-rails-migration.md ├── 2015-12-08-how-to-start-a-new-rails-app-and-not-end-in-legacy.md ├── 2021-03-10-how-to-make-idempotent-create-requests-against-a-3rd-party-http-api.md ├── 2016-06-17-cover-all-test-cases-with-permutation.md ├── 2012-11-29-not-rails.md ├── 2018-02-14-building-custom-search-is-hard-and-boring.md ├── 2016-11-30-recovering-unbootable-nixos-instance-using-hetzner-rescue-mode.md ├── 2025-02-06-improve-your-ux-with-turbo-frames.md ├── 2017-10-06-how-to-overwrite-to-json-as-json-in-active-record-models-in-rails.md ├── 2025-08-01-watch-out-for-this-one-deprecation-warning-when-upgrading-from-rails-7-dot-1-to-7-dot-2-on-heroku.md ├── 2020-02-24-legacy-rails-ddd-migration-strategy-from-read-models-through-events-to-aggregates.md ├── 2020-04-21-most-controversial-rules-in-arkency.md ├── 2016-05-19-70-percent-off-the-rails-slash-tdd-slash-ddd-slash-mutant-video-class-until-11pm-19-dot-05-dot-2016-cest.md ├── 2023-01-20-offloading-write-side-with-a-read-model.md ├── 2022-09-28-simplify-your-system-debugging-by-introducing-event-store-linking.md ├── 2021-01-12-gradual-automation-in-ruby.md ├── 2017-06-23-tracking-dead-code-with-metrics.md ├── 2016-12-23-why-would-you-even-want-to-listen-about-ddd.md ├── 2015-03-09-fast-introduction-to-event-sourcing-for-ruby-programmers.md ├── 2016-07-10-respond-to-format-is-useful-even-without-multiple-formats.md ├── 2015-03-24-how-to-store-large-files-on-mongodb.md ├── 2016-07-10-domain-events-schema-definitions.md ├── 2017-07-14-the-easiest-posts-to-write-for-a-programming-blog.md ├── 2018-03-28-what-ive-learned-at-arkency-and-why-i-am-leaving.md ├── 2021-03-19-use-activeadmin-like-a-boss.md ├── 2015-01-03-fearless-refactoring-1-dot-1-validations.md ├── 2021-04-16-render-is-not-your-final-word-in-your-rails-controllers-action.md ├── 2014-01-07-you-win-by-being-remote.md ├── 2023-04-28-few-static-analysis-tricks-to-bulletproof-your-application.md ├── 2022-10-07-the-final-trick-when-moving-from-crud-to-event-sourcing.md ├── 2016-10-27-the-typical-ruby-bugs-with-changing-the-last-line-in-a-method.md ├── 2017-02-05-how-to-unit-test-classes-which-depend-on-rails-models.md └── 2021-10-23-less-known-capability-of-rubys-json-dot-parse.md ├── .github └── workflows │ └── build.yml └── README.md /bin/publish_recent_changes_production.sh: -------------------------------------------------------------------------------- 1 | git ci -a -m "draft updated [GENERATED]" 2 | git push -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@prettier/plugin-ruby": "^4.0.2", 4 | "prettier": "^3.1.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /bin/andrzej_blog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -x 5 | 6 | ./bin/new_post -b -c \ 7 | -t "$1" \ 8 | -a "Andrzej Krzywda" \ 9 | -e /Applications/iA\ Writer.app/Contents/MacOS/iA\ Writer 10 | -------------------------------------------------------------------------------- /posts/2020-04-27-autonomy---the-best-friend-of-async-and-remote.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2020-04-27T10:52:41.840Z 3 | author: Tomasz Wróbel 4 | tags: [] 5 | publish: false 6 | --- 7 | 8 | # Autonomy - the best friend of Async and Remote -------------------------------------------------------------------------------- /posts/2021-04-20-post-to-check-markdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post to check markdown 3 | created_at: 2021-04-20T14:53:51.409Z 4 | author: Paweł Pacana 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | # Post to check markdown 10 | There 11 | 12 | ## How about now 13 | Here 14 | ```ruby 15 | 1 + 1 16 | ``` 17 | Is it working? 18 | -------------------------------------------------------------------------------- /posts/2021-09-28-application-level-locks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Application level locks 3 | created_at: 2021-09-28T14:53:08.580Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | ## PG transaction level 10 | 11 | ## PG session level 12 | 13 | ## DB records 14 | 15 | ## Sidekiq Ent `unique_for` 16 | 17 | 18 | -------------------------------------------------------------------------------- /posts/2021-03-25-stuff-youll-miss-outside-ruby.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Stuff you'll miss outside Ruby 3 | created_at: 2021-03-25T10:28:46.615Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | ## build times 10 | 11 | https://twitter.com/adamgordonbell/status/1374728566068416518 12 | 13 | ## community 14 | 15 | ## occasional magic / metaprogramming 16 | 17 | ## beautiful language 18 | 19 | ## little punctuation 20 | 21 | ## so many good ideas started in Ruby x Rails community 22 | 23 | -------------------------------------------------------------------------------- /posts/2021-09-27-processing-webhooks:-from-yolo-to-reliability.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Processing Webhooks - from YOLO to reliability 3 | created_at: 2021-09-27T11:35:19.867Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | ## Webhooks and in-request handling 10 | 11 | * maybe the webhook caller will care about the order when retrying webhooks 12 | 13 | ## Webhooks and background handling 14 | 15 | * retries can mess up order 16 | 17 | ## A queue/log 18 | 19 | ## Streams 20 | 21 | ## `/events` 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: trigger 2 | on: 3 | push: 4 | branches: [master] 5 | jobs: 6 | trigger: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - run: | 10 | curl -s -f -L \ 11 | -X POST \ 12 | -H "Accept: application/vnd.github+json" \ 13 | -H "Authorization: Bearer ${{ secrets.PERSONAL_ACCESS_TOKEN }}" \ 14 | -H "X-GitHub-Api-Version: 2022-11-28" \ 15 | https://api.github.com/repos/arkency/sites/dispatches \ 16 | -d "{\"event_type\": \"trigger\" }" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Posts 2 | 3 | Blogposts featured on [blog.arkency.com](https://blog.arkency.com). This content is fetched with [nanoc-github](https://github.com/pawelpacana/nanoc-github). 4 | 5 | ## Improving existing content 6 | 7 | Found a typo? Code not working? Submit a pull-request. 8 | 9 | ## Creating new post 10 | 11 | ``` 12 | ./bin/new_post -t "How to tell a compelling story" 13 | ``` 14 | 15 | ## ERB code blocks 16 | If you want to use ERB code blocks in your post, remember to use double-percent marks because the content is run through eRuby on compilation. 17 | 18 | ``` 19 | <%%= tags_for(item, none_text: "", base_url: "#") %> 20 | ``` 21 | -------------------------------------------------------------------------------- /posts/2019-07-10-ruby-refactoring-tennis-game-kata.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2019-07-10 12:01:51 +0200 3 | publish: false 4 | author: Andrzej Krzywda 5 | --- 6 | 7 | # Ruby Refactoring Tennis Game Kata 8 | 9 | Ruby Refactoring preview 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /posts/2021-01-29-implementing-crm-with-rails-and-rails-event-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2021-01-29 22:54:06 +0100 3 | author: Paweł Pacana 4 | tags: ['rails event store', 'rails'] 5 | publish: false 6 | --- 7 | 8 | 9 | # Implementing CRM with Rails and Rails Event Store 10 | 11 | * there are some dated Rails open source CRM solutions, meh 12 | * many solutions in the market, all of them having 90% of not needed functionality 13 | * still integrating with data sources either way 14 | * fleshing out concepts via event storming 15 | * fairly simple use case: listing, details and search 16 | * mailchimp mailing list since the beginning of product at Arkency, good place to start mining 17 | * structure of member in API 18 | * how to transform this into events 19 | * on valid_at and bi-temporal features in RES 2.0 20 | * RES Browser feature demo 21 | * next episode teaser: read models 22 | 23 | 24 | -------------------------------------------------------------------------------- /posts/2021-03-17-async-processing-of-events-in-order---the-holy-grail-of-sql-backed-event-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Async Processing of Events in Order - the Holy Grail of SQL backed Event Store 3 | created_at: 2021-03-17T21:42:46.712Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | # Async Processing of Events in Order - the Holy Grail of SQL backed Event Store 10 | 11 | problem: holes in id sequence 12 | 13 | where needed: 14 | 15 | * process events from one app in another app 16 | * "persistent projections" 17 | 18 | 19 | issue 106 20 | 21 | how to make it without relying on exotic db features (vendor specific or unavailable in cloud) 22 | 23 | * ~sync handlers~ 24 | * out of order processing 25 | * always reading the whole stream from the beginning 26 | * linearized writes 27 | * max tx time 28 | * auto increment order (vs tx order) — waits for transaction to finish 29 | -------------------------------------------------------------------------------- /posts/2020-07-27-against-dry-dot-challenging-rails-dogmas-dot.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2020-07-27T08:39:15.913Z 3 | author: Tomasz Wróbel 4 | tags: [] 5 | publish: false 6 | --- 7 | 8 | # Against DRY. Challenging Rails dogmas. 9 | 10 | We've been taught to make our code DRY. It seems so obvious. Almost like the fact that earth orbits the sun. 11 | 12 | Well, agressive dryification can lead to so many problems. Over time people started to realize you should not always try to dry up your code at all costs. 13 | 14 | Let's see an example 15 | 16 | if statements 17 | wrong abstraction 18 | accidental vs actual 19 | 20 | People have come up with new interesting acronyms: [WET](https://dev.to/wuz/stop-trying-to-be-so-dry-instead-write-everything-twice-wet-5g33), [AHA](https://kentcdodds.com/blog/aha-programming). 21 | 22 | Read models and bounded contexts help you make your code less dry. Let's look at some examples. 23 | -------------------------------------------------------------------------------- /posts/2014-07-09-rubymine-basic-navigation-features.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2014-07-09 10:10:42 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | newsletter: fearless_refactoring_1 6 | tags: [ 'rubymine' ] 7 | --- 8 | 9 | # RubyMine basic navigation features (that make you move around code fast) 10 | 11 |

12 | 13 |

14 | " width="100%"> 15 |
16 | 17 |

18 | 19 | * jump to callback definition 20 | * jump to field from schema 21 | * jump to accessor definition 22 | * jump to association declaration 23 | * select jump destination from multiple implementations of methods 24 | * jump to method defined in ruby gem 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /posts/2014-10-03-behind-the-scenes.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2014-10-03 09:34:23 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'lifestyle' ] 6 | newsletter: aar_newsletter 7 | --- 8 | 9 | # Behind the scenes 10 | 11 | We work from wherever we want and we ship it. 12 | 13 | 14 | 15 | ## Where did we work from this week? 16 | 17 | " width="45%"> 18 | " width="45%"> 19 | " width="45%"> 20 | " width="45%"> 21 | " width="45%"> 22 | " width="45%"> 23 | 24 | ## Andrzej preparing to his lectures at Wrocław University 25 | 26 | " width="100%"> 27 | -------------------------------------------------------------------------------- /posts/2019-06-07-how-many-ruby-programmers-are-there-in-the-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2019-06-07 12:01:51 +0200 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['ruby', 'rubymine', 'video'] 6 | --- 7 | 8 | # How many Ruby programmers are there in the world? 9 | 10 | According to JetBrains research (as of 2019), there is 300.000 professional Ruby programmers, while there is over a 1.000.000 programmers who use Ruby. 11 | 12 | 13 | 14 | A few days ago, I've had a chance to sit with Artem Sarkisov, a product marketing manager from JetBrains, responsible for the RubyMine IDE. 15 | 16 | Artem's perspective on the Ruby community is quite unique, as he looks at it from the marketing perspective. 17 | 18 | Here is this part of the conversation where we talk about the size of the Ruby community: 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /posts/2021-02-01-x-ways-to-improve-transparency-of-your-work.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: X ways to improve transparency of your work 3 | created_at: 2021-02-01T15:10:27.430Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | # X ways to improve transparency of your work 10 | 11 | Problem: you're doing something, but no one knows what. You're being asked on standup or pinged. 12 | 13 | Traditional, naive ways: 14 | 15 | * Standup 16 | * Ticket updates 17 | * Private message: "How it's going with PR-xxx?" 18 | 19 | Why they're not cool? 20 | 21 | * distracting 22 | * ineffective 23 | 24 | New cool ways: 25 | 26 | * `--verbose` 27 | * async standup 28 | * frequent, meaningful elaborate commit messages 29 | * live google doc 30 | * discord voice channles, screen sharing 31 | * snaps 32 | * pairing, intermittent pairing 33 | * cut the project into small storiess, deliver daily 34 | 35 | Each of these has nice side effects: 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /posts/2021-01-18-10x-developer-destructured.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 10x Developer Destructured 3 | created_at: 2021-01-18T11:21:35.374Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | # 10x Developer Destructured 10 | 11 | 12 | 13 | 14 | Everyone has heard of mythical _10x developers_. Do they exist? Recruiters want to hire them. Developers mostly think it's not them. But: 15 | 16 | 10x = x + x + x + x + x + x + x + x + x + x 17 | 18 | Are all the `x`'s about coding? What makes someone averagely 10x efficient at work? Here are some of my ideas: 19 | 20 | **TODO: it's just a brain dump so far, need to adjust according to actual relevance.** 21 | 22 | ## Communication 23 | 24 | ## Being kind 25 | 26 | ## Not taking offense 27 | 28 | ## Timeless programming knowledge 29 | 30 | We had a blogpost about that... 31 | 32 | ## Architectural patters 33 | 34 | ## Mental models 35 | 36 | ## Actual programming 37 | 38 | ## Command line Fu 39 | -------------------------------------------------------------------------------- /posts/2017-08-31-database-url-examples-for-rails-db-connection-strings.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-08-31 10:04:49 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'travis', 'rails', 'database_url', 'connection string' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # DATABASE_URL examples for Rails DB connection strings 10 | 11 | Recently I've been configuring [RailsEventStore](https://github.com/RailsEventStore/rails_event_store) to run tests on many 12 | databases on the Travis CI. We do it using `DATABASE_URL` environment variable 13 | but I couldn't find good examples easily. So here they are. 14 | 15 | 16 | 17 | ## PostgreSQL 18 | 19 | ``` 20 | DATABASE_URL=postgres://localhost/rails_event_store_active_record?pool=5 21 | ``` 22 | 23 | ## MySQL 24 | 25 | ``` 26 | DATABASE_URL=mysql2://root:@127.0.0.1/rails_event_store_active_record?pool=5 27 | ``` 28 | 29 | ## Sqlite in memory 30 | 31 | ``` 32 | DATABASE_URL=sqlite3::memory: 33 | ``` 34 | 35 | ## Code 36 | 37 | ``` 38 | ENV['DATABASE_URL'] ||= "postgres://localhost/rails_event_store_active_record?pool=5" 39 | 40 | RSpec.configure do |config| 41 | config.around(:each) do |example| 42 | ActiveRecord::Base.establish_connection(ENV['DATABASE_URL']) 43 | end 44 | end 45 | ``` 46 | -------------------------------------------------------------------------------- /posts/2015-10-28-slack-driven-blogposts.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-10-28 15:45:05 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: [ 'blog', 'blogging'] 6 | --- 7 | 8 | # Slack-driven blogposts 9 | 10 | Slack (or any other team communication tool) may be a good source for your blogposts. 11 | 12 | You may have explained something to your team on the Slack channel. You already verbalised your thoughts in a written manner. That's the biggest part of writing a blog post! Use it to your benefit now. Turn it into a blogpost. 13 | 14 | 15 | 16 | 17 | As developers we often share this feeling that what we just wrote or explained is not really that useful to anybody. 18 | 19 | Here are some techniques which may help you in turning the Slack conversations into a blog post: 20 | 21 | - if your colleague wrote something interesting on Slack, encourage them to wrap it as a blogpost - suggest the title, focus on how useful it may be for others 22 | - look at what you wrote today on Slack - does it have some interesting value? Ask your team if this could make a blogpost. 23 | - prepare a blogpost based on what others wrote. If they feel blocked to blog on their own, show them how easy it is. Grab their messages, create a blogpost, send them the draft if they're OK with it. Publish. 24 | -------------------------------------------------------------------------------- /posts/2020-07-17-faces-of-tech-debt.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2020-07-17T11:34:07.875Z 3 | author: Tomasz Wróbel 4 | tags: [] 5 | publish: false 6 | --- 7 | 8 | # Faces of tech debt 9 | 10 | Most IT projects fail. Reasons are many, but tech debt is one of them. Tech debt has many faces. Know your enemy. 11 | 12 | 13 | ## Spaghetti code 14 | 15 | ## Coupling everywhere 16 | 17 | ## Implicit side effects 18 | 19 | ## Global state 20 | 21 | ## Too many dependencies 22 | 23 | ## Long-overdue upgrades 24 | 25 | ## No tests 26 | 27 | ## Tests running a loooong time 28 | 29 | ## Failing tests 30 | 31 | ## Tests difficult to modify 32 | 33 | ## No CI/CD 34 | 35 | ## var 36 | 37 | From weekly 38 | 39 | * you wanna change 1 thing, but you need to do it in 10 places 40 | * conversely, you wanna change 1 thing, but you cannot do it because it would affect 10 other places you don't want to change 41 | * hard to express invariants in code 42 | * web app - syncing data 43 | * inconsistent data, old data that changed, new code breaks invariants 44 | * organizational tech debt 45 | * coupled read and write 46 | * conditionals 47 | * callbacks (-> implicit...) 48 | * complexity 49 | * customer doesn't understand "tech debt" 50 | 51 | rails debt cleaners, fixing + education 52 | 53 | 54 | 55 | Which one you've got? 56 | 57 | Which ones can we help with. 58 | -------------------------------------------------------------------------------- /posts/2021-10-28-ways-of-communication-that-are-killing-your-team.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ways of communication that are killing your team 3 | created_at: 2021-10-28T08:14:09.190Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | ## `@here`-ing too 10 | 11 | What are you going to do when there's a real DEFCON? There's no bigger way of grabbing people's attention. 12 | 13 | ## ping inflation 14 | 15 | ping not enough, DM needed 16 | 17 | ## meeting to solve everything 18 | 19 | ## video on 20 | 21 | ## meetings scheduled without consideration for context switching 22 | 23 | ## meetings as a default mode of communication 24 | 25 | go for text instead 26 | 27 | easier to reread, don't have to repeat, don't have connectivity problems 28 | 29 | focus problems 30 | 31 | # Secretive conversations 32 | 33 | - private channels 34 | - group DMs 35 | 36 | Why? 37 | 38 | 39 | 56 | 57 | -------------------------------------------------------------------------------- /posts/2020-05-05-overcome-10k-rows-database-limit-on-heroku-by-upgrading-the-plan.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2020-05-05T11:20:37.601Z 3 | author: Andrzej Krzywda 4 | tags: [heroku, postgres] 5 | publish: true 6 | --- 7 | 8 | # Overcome 10k rows database limit on Heroku by upgrading the database plan 9 | 10 | Over time I have collected quite a number of small Rails heroku apps. They usually start small, but overtime they hit the limit of 10k rows and it's time to upgrade the database plan. Every time I do it, I hit the heroku documentation just to realize that their way of explaining doesn't fit me well. 11 | 12 | This usually means that I then google a lot and only after 10 minutes I find what I look for. 13 | 14 | This blogpost is my attempt to make myself a quick summary of what needs to be done: 15 | 16 | And yes, Andrzej, if you read it in the future - you do need to provision a new database to overcome the 10k limit. There's no way of just upgrading this limit on the UI with one button. 17 | 18 | 19 | Before I start I make sure that I don't need to append the name of the app to each command, by: 20 | 21 | `heroku git:remote -a myapp` 22 | 23 | * create new postgres add-on (choose Hobby Basic) in UI 24 | * `heroku maintenance:on` 25 | * `heroku pg:copy HEROKU_POSTGRESQL_COPPER_URL HEROKU_POSTGRESQL_CHARCOAL` 26 | * `heroku pg:promote HEROKU_POSTGRESQL_CHARCOAL` 27 | * `heroku maintenance:off` 28 | * remove old Postgres add-on 29 | 30 | -------------------------------------------------------------------------------- /posts/2016-11-24-educate-about-ddd-slash-cqrs-slash-event-sourcing-at-the-facebook-group.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-11-24 15:55:30 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['ddd'] 6 | --- 7 | 8 | # Educate about DDD/CQRS/Event Sourcing at the Facebook group 9 | 10 | There's more and more places where people interested in DDD can learn more. One of those places is the [DDD/CQRS google group](https://groups.google.com/forum/#!forum/dddcqrs), from which I learnt a lot! 11 | 12 | 13 | 14 | 15 | I was wondering if a more light-weight place to learn about DDD would make sense and as part of an experiment, I've started a Facebook group. I know that not all people use Facebook, but I know several thriving programming communities on Facebook, so why not? 16 | 17 | Feel free [to join the new Facebook group](https://www.facebook.com/groups/1232045823501220/) and learn more. The place is meant to be technology-agnostic. Ruby, Java, .Net, JavaScript, you name it. What's great is that the DDD/CQRS/Event Sourcing patterns look almost the same in all the languages. They all make sense. Why not learn from other communities too? 18 | 19 | Feel free to just lurk but I also encourage you to post DDD-related blogposts and all kind of questions you may have! Even if you're a total newbie, it's OK to ask questions. 20 | 21 | See you on the [DDD CQRS Event Sourcing Facebook group](https://www.facebook.com/groups/1232045823501220/) :) 22 | -------------------------------------------------------------------------------- /posts/2017-07-17-non-coding-activites-in-a-software-project.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-07-17 15:25:30 +0200 3 | publish: true 4 | tags: [ 'async remote' ] 5 | author: Andrzej Krzywda 6 | --- 7 | 8 | # Non-coding activities in a software project 9 | 10 | Recently in our project, we came up with a list of non-coding activities. Those are the tasks that need be done quite regularly and might be easy to be forgotten. 11 | 12 | 13 | 14 | If we tend to forget them, then there's a risk that someone else will introduce a process around those activities. Sometimes it may mean new people will be brought so that they "manage" those activities. In my opinion, the more can be done by a developer the better, because we don't introduce non-technical people to the communication loop. 15 | 16 | * read communication on pivotal (and optionally reply) 17 | * read communication on slack (and optionally reply) 18 | * build/monitoring failures 19 | * review commits from others 20 | * work on our tickets 21 | * look at exception notifications 22 | * create new tickets based on build failures or exceptions 23 | * challenge the prios in backlog 24 | * check security updates for gems 25 | * document higher level concepts (architecture, tracking, technical debt) 26 | * remove dead code, unused feature toggles 27 | 28 | I will probably keep updating this list, as this may serve our team in the longer run. If you feel that we miss something important here, feel free to comment, thanks! 29 | -------------------------------------------------------------------------------- /posts/2022-06-10-rails-integration-tests-without-service-objects-and-commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2022-06-10 12:11:31 +0200 3 | author: Andrzej Krzywda 4 | tags: [] 5 | publish: false 6 | --- 7 | 8 | # Rails integration tests without service objects and commands 9 | 10 | Today I worked on the Arkency Ecommerce project. 11 | 12 | I have noticed that we're still relying on commands in the integration tests. 13 | If you don't use commands, you can think of them as similar stuff to service objects for this scope of the problem. 14 | 15 | In this post I 16 | 17 | 18 | 19 | This is what the tests were like: 20 | 21 | 22 | ```ruby 23 | ``` 24 | 25 | 26 | This is how it was changed. 27 | 28 | 29 | ```ruby 30 | ``` 31 | 32 | 33 | 34 | As you see it's mostly relying on testing via the HTTP Api (which uses Rails views so it returns html). 35 | 36 | What is the problem of having service objects in the integration tests? 37 | 38 | The main one is that we skip the controllers from being tested. 39 | Testing them in isolation is usually an overkill with mocking but some kind of tests are needed. 40 | 41 | How did this happen that we had them in the first place? 42 | 43 | In this project we've had an UI for showing/using products and customers but we didn't have any UI to create them. 44 | This means that temporarily we had to rely on commands in those tests. 45 | 46 | However, once the UI was created, we could replace the commands with real http calls. 47 | 48 | 49 | -------------------------------------------------------------------------------- /posts/2021-03-16-comparison-of-event-serialization-methods.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Comparison of event serialization methods 3 | created_at: 2021-03-16T20:52:15.122Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | 18 | 19 | 20 | ## YAML 21 | 22 | * you can just throw whatever at it and have it serialized 23 | * self-contained schema 24 | 25 | ``` 26 | example payload 27 | ``` 28 | 29 | ## JSON 30 | 31 | * you need schema definition for anything beyond json types 32 | * dry 33 | 34 | ## JSONB 35 | 36 | * you can query 37 | 38 | 39 | ## Marshal 40 | 41 | * almost like yaml - you can throw anything ruby at it 42 | * but unreadable 43 | * and dangerous? 44 | 45 | ## Protobuf 46 | -------------------------------------------------------------------------------- /posts/2023-01-04-summary-event-pattern.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2023-01-04 11:19:39 +0100 3 | author: Piotr Jurewicz 4 | tags: [] 5 | publish: false 6 | --- 7 | 8 | # Reduce the communication between bounded contexts with a Summary Event pattern 9 | 10 | ## Granularity or redundancy? 11 | 12 | I can see no point for Invoicing Bounded Context to know about an order that is not submitted yet. 13 | However, when the order is finally submitted, Invoicing needs to know all its details. 14 | All the operations of adding and removing items from the order are not relevant for Invoicing. 15 | It is only the final state of the order that matters. 16 | In this case, we will gain by reducing the communication between the bounded contexts. Code will be simpler and more maintainable. 17 | Dumping all the order details into OrderSubmitted event looks pretty tempting. 18 | 19 | 20 | On the other hand, if your business is for example a fast food restaurant, you might have some bounded contexts that want to know about order item being added to the cart even it is not submitted yet. 21 | In this kind of business, fast fulfillment may be more important than some loses when the order is altered or abandoned before it is submitted. 22 | In this case, you would rather to stick to granular events. 23 | 24 | 25 | ## Why not both? 26 | 27 | This is where the Summary Event pattern comes in. 28 | 29 | 30 | 31 | 32 | FIXME: Place post body here. 33 | 34 | ```ruby 35 | Person.new.show_secret 36 | # => 1234vW74X& 37 | ``` 38 | -------------------------------------------------------------------------------- /posts/2017-06-01-handling-svg-images-with-refile.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-06-01 14:00:39 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'rails', 'svg', 'refile', 'imgix' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # Handling SVG images with Refile and Imgix 10 | 11 | My colleague Tomek today was responsible for changing a bit how we 12 | handle file uploads in a project so that it can support SVG logos. 13 | 14 | For handling uploads this Rails app uses `Refile` library. And 15 | for serving images there is `Imgix` which helps you save bandwith 16 | and apply transformations (using Imgix servers instead of yours). 17 | 18 | 19 | 20 | The normal approach didn't work because it did not recognize SVGs 21 | as images. 22 | 23 | ```ruby 24 | attachment :logo, type: :image 25 | ``` 26 | 27 | So instead we had to list supported content types manually. 28 | 29 | ```ruby 30 | attachment :logo, 31 | content_type: %w(image/jpeg image/png image/gif image/svg+xml) 32 | ``` 33 | 34 | There is also a bit of logic involved in building proper URL for 35 | the browser. 36 | 37 | ```ruby 38 | = link_to image_tag(imgix_url("/shop/#{shop.logo_id}", 39 | { auto: "compress,format",w: 300,h: 300,fit: "crop" }), 40 | filename: shop.logo_filename) 41 | ``` 42 | 43 | ```ruby 44 | def imgix_url(path, **options) 45 | options[:lossless] = true if options[:lossless].nil? 46 | host = options.delete(:host) || S3_IMGIX_PRODUCTION_HOST) 47 | Imgix::Client.new(host: host).path(path).to_url(options) 48 | end 49 | ``` 50 | -------------------------------------------------------------------------------- /posts/2021-10-05-setting-up-mutant-on-a-huge-test-suite.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Setting up Mutant on a huge test suite 3 | created_at: 2021-10-05T11:27:06.776Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | 10 | 11 | require tracing 12 | 13 | 14 | bumpnąć gemiki capybara i reek żeby odblokować dependency na parser i regexp dla mutanta 15 | 16 | usunąć obsolete database_cleaner i jego copypaste truncation które psuje współbieżność, standard railsowy jest szybszy i lepszy 17 | 18 | dodać mutanta z require na config/environment oraz na pliku robiącym Rails.configuration.eager_load = true 19 | 20 | na CI używać bundle exec mutant subscription test && env RAILS_ENV=test bundle exec mutant run --since master aby odsiać tych co nie mają licencji (i będzie wisiało 40s) oraz mutować dodany kodzik 21 | 22 | env RAILS_ENV=test bundle exec mutant run --since master 23 | 24 | j1 25 | 26 | Rails.configuration.eager_load = true 27 | 28 | require 'mutant/integration/rspec' 29 | 30 | Mutant::Integration::Rspec.send(:remove_const,:CLI_OPTIONS) 31 | Mutant::Integration::Rspec::CLI_OPTIONS = %w[spec/modules/booking_financials --fail-fast].freeze 32 | 33 | before(:suite) do 34 | # czy find_or_create jest tutaj w transakcji czy nie, przy wspólżbieżności jest kupa 35 | end 36 | 37 | 38 | 39 | https://github.com/mbj/mutant/blob/main/docs/mutant-rspec.md#test-selection 40 | https://github.com/mbj/mutant/blob/main/docs/configuration.md 41 | 42 | https://github.com/RailsEventStore/ecommerce/blob/master/rails_application/.mutant.yml — example 43 | -------------------------------------------------------------------------------- /posts/2016-11-21-dealing-with-randomly-failing-tests-from-a-team-perspective.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-11-21 00:12:37 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['testing'] 6 | --- 7 | 8 | # Dealing with randomly failing tests from a team perspective 9 | 10 | One of the things that can negatively impact a team morale is random builds - builds where sometimes some tests are failing. 11 | Inspired by Martin Fowler's article on [Quarantine](http://martinfowler.com/articles/nonDeterminism.html), in some of our projects we came up with a guideline how we can fix the problem as a team. 12 | 13 | 14 | 15 | 16 | 1. If a test fails randomly for more than 1 time, add to it to quarantine (consult the list of existing failures) 17 | 2. never kick the build without doing some action (quarantine, test fix) 18 | 3. if the build is red after your session of work, it's your responsibility to fix it (feel free to ask for help if you have no time, but the initiative is yours). Whenever we say 'you are responsible', we mean that the whole team is responsible, but you're the tracker, you take the initiative. It's not your fault, but we need someone to track it and that seems to make most sense. 19 | 4. don't push into the repo if you have no time to handle the build problems 20 | 5. never leave a red build after your session of work 21 | 6. if a build fails for not clear reason, find the reason and fix it 22 | 7. don't push the code if the build is red 23 | 8. if you start your working session and the build is red, talk to others and fix it first, then start your task 24 | 9. if there's really no other way to fix the build and no one to help, then at least kick the build 25 | -------------------------------------------------------------------------------- /posts/2014-11-15-rails-refactoring-dot-com-podcast-number-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2014-11-15 11:33:09 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'ruby2.1', 'service objects', 'form objects' ] 6 | newsletter: arkency_form 7 | enclosure: 8 | identifier: "/assets/sounds/rails-refactoring1.ogg" 9 | type: "audio/ogg" 10 | --- 11 | 12 | # rails-refactoring.com podcast #1 13 | 14 | Andrzej Krzywda & Robert Pankowecki in first episode of 15 | [rails-refactoring.com](http://rails-refactoring.com) podcast. We are talking about migrating 16 | applications from ruby 1.9 to 2.1 and using gems for form 17 | and service objects. 18 | 19 | 20 | 21 | 22 | 23 | ## Links 24 | 25 | * [Download episode](<%= url_for(items.find{|i| i.identifier == item.attributes[:enclosure][:identifier] }) %>) 26 | * [YAML: Syck vs Psych](http://devblog.arnebrasseur.net/2014-02-yaml-syck-vs-psych) 27 | * [Ruby 2.0 in Detail](http://globaldev.co.uk/2013/03/ruby-2-0-0-in-detail/) 28 | * [Ruby 2.1 in Detail](http://globaldev.co.uk/2014/05/ruby-2-1-in-detail/) 29 | * [Ruby 2.1 GC settings](http://www.reddit.com/r/ruby/comments/2m663d/ruby_21_gc_settings/) 30 | * [Audience questions](https://twitter.com/andrzejkrzywda/status/533231444622319617) 31 | * [Trailblazer gem](https://github.com/apotonick/trailblazer) 32 | * [Interactor gem](https://github.com/collectiveidea/interactor) 33 | * [Fearless refactoring: Rails controllers](http://rails-refactoring.com/) 34 | -------------------------------------------------------------------------------- /posts/2016-02-09-from-legacy-to-ddd-what-are-those-events-anyway.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-02-09 23:48:04 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['ddd'] 6 | --- 7 | 8 | # From legacy to DDD: What are those events anyway? 9 | 10 | [In one of my previous posts](http://blog.arkency.com/2016/01/from-legacy-to-ddd-start-with-publishing-events/), I've suggested to start with publishing events. It sounds easy in theory, but in practice it's not always clear what is an event. 11 | The problem is even bigger, as the term event is used in different places with different meaning. In here, I'm focusing on explaining events and commands, with their DDD-related meaning. 12 | 13 | 14 | 15 | **Events are facts**. 16 | 17 | They happened. There's no arguing about it. That's why we name them in past tense: 18 | 19 | ``` 20 | UserRegistered 21 | OrganizationAllowedToUseTheApp 22 | OrderConfirmed 23 | ``` 24 | 25 | If those are only facts, then what is the thing which is the request to make the fact happen? 26 | 27 | Enter commands. 28 | 29 | Commands are the objects which represent the intention of the outside world (usually users). A command is like a request: 30 | 31 | ``` 32 | RegisterUser 33 | AllowOrganizationToUseTheApp 34 | ConfirmOrder 35 | ``` 36 | 37 | It's like someone saying "Please do it" to our system. 38 | 39 | Usually handling commands in the system, causes some new events to be published. 40 | 41 | **Commands are the input**. 42 | 43 | **Events are the output**. 44 | 45 | Both commands and events are almost like only data structures. They contain some "params". 46 | 47 | It's important to note, they're not responsible for "handling" any action. 48 | 49 | For now, just remember: 50 | 51 | **commands are requests** 52 | 53 | **events are facts** 54 | -------------------------------------------------------------------------------- /posts/2023-01-02-effortless-debugging-with-those-4-linking-classes-from-railseventstore.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2023-01-02 16:53:30 +0100 3 | author: Łukasz Reszke 4 | tags: ['event store', 'event sourcing', 'rails event store'] 5 | publish: true 6 | --- 7 | 8 | # Effortless debugging with those 4 linking classes from RailsEventStore 9 | 10 | Some time ago I wrote a [short article](https://blog.arkency.com/simplify-your-system-debugging-by-introducing-event-store-linking/) about simplifying your system debugging by using the linking feature of RailsEventStore. The post is describing custom built linking class. 11 | 12 | However, I forgot to mention that RailsEventStore provides a few linking classes out of the box! 13 | 14 | Currently, there are [4 linking classes](https://railseventstore.org/docs/v2/link/#available-linking-classes): 15 | 16 | 17 | 18 | - `RailsEventStore::LinkByMetadata` - links events to stream built on specified metadata key and value, 19 | - `RailsEventStore::LinkByCorrelationId` - links events to stream by event's correlation id, 20 | - `RailsEventStore::LinkByCausationId` - links events to stream by event's causation id, 21 | - `RailsEventStore::LinkByEventType` - links events to stream by event's type 22 | It's easy to use. All you have to do is to add the following line to your subscriptions: 23 | 24 | ```ruby 25 | event_store.subscribe_to_all_events(RailsEventStore::LinkByEventType.new) 26 | event_store.subscribe_to_all_events(RailsEventStore::LinkByCorrelationId.new) 27 | event_store.subscribe_to_all_events(RailsEventStore::LinkByCausationId.new) 28 | ``` 29 | And from now on, you can go to RailsEventStore Browser and browse your events by an event type, causation, and correlation ids. 30 | 31 | You can read more about linking in the [docs](https://railseventstore.org/docs/v2/link/). 32 | -------------------------------------------------------------------------------- /posts/2015-02-27-the-reasons-why-programmers-dont-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-02-27 10:05:53 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | newsletter: blogging_book 6 | tags: [ 'blog', 'blogging'] 7 | --- 8 | 9 | # The reasons why programmers don't blog 10 | 11 | I was wondering why only few programmers actively blog, so I asked for reasons on Twitter. Here are the responses. 12 | 13 | 14 | 15 |

If you're a programmer, could you reply to me - why are you not blogging? If you *are*, then please reply, why are you not blogging more?

— Andrzej Krzywda (@andrzejkrzywda) February 25, 2015
16 | 17 | Click here to see all the responses as they appeared in original. 18 | 19 | Here's a short summary of the reasons: 20 | 21 | * Time 22 | * "What I do is not interesting to others" 23 | * Perfectionism 24 | * Coding is better than writing 25 | * I'm not doing anything innovative 26 | * "I don't do side projects" 27 | * "I don't know what to write about" 28 | * shame 29 | * "what I want to write about is too obvious" 30 | * writing is harder than coding 31 | * everyone knows that already 32 | * longer feedback loop, as compared to coding 33 | 34 | Is there any other reason you'd like to add? 35 | 36 | It's worth noting that this survey may not be a good sample - those are from programmers who don't blog, but they do tweet (which on its own is quite interesting). 37 | 38 | Do you think the reasons can be addressed in any way? Does it make sense for programmers to blog more? I'll leave you with those questions here :) 39 | -------------------------------------------------------------------------------- /posts/2015-05-03-from-rails-to-haskell-and-yesod.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-05-03 12:28:14 +0200 3 | publish: false 4 | author: Andrzej Krzywda 5 | newsletter: arkency_form 6 | img: "haskell-helloworld.png" 7 | --- 8 | 9 | # From Rails to Haskell and Yesod 10 | 11 |

12 |

13 | "> 14 |
15 |

16 | 17 | I learnt Ruby by using Rails. I remember that over 10 years ago, I was typing `has_many :items` without understanding that `has_many` is a class method. I didn't event know at that time, that a Ruby class is an object. 18 | 19 | I'm trying to apply the same pattern of learning to Haskell and other languages. I don't know Haskell. I couldn't type a hello world application in Haskell without. 20 | 21 | This blog post is about my first steps in learning Yesod (the Rails equivalent for Haskell) and my thoughts about it. 22 | 23 | 24 | 25 | I think it was about 17 years ago (yes, I'm that old) when I first learnt about the Functional Programming concepts and some foundations of type systems. FP is not totally foreign to me. 17 years ago I was told by some super smart people from the University of Wroclaw that FP would be the future "soon". They may have been a bit optimistic in this prediction, however the trends are indeed showing that FP is becoming mainstream. 26 | 27 | **The skills of selling FP** 28 | 29 | As developers we don't feel comfortable with the concept of selling. We don't trust when someone is trying to sell something to us. But sometimes it's different. 30 | If you're reading this, the chances are that you're a Ruby developer. 31 | 32 | Would you be a Ruby developer if DHH didn't have his selling skills over 10 years ago? 33 | 34 |

35 |

36 | "> 37 |
38 |

39 | 40 | -------------------------------------------------------------------------------- /posts/2016-11-06-rails-and-adapter-objects-different-implementations-in-production-and-tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-11-06 18:07:14 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['testing'] 6 | --- 7 | 8 | # Rails and adapter objects: different implementations in production and tests 9 | 10 | If you work with service objects in Rails apps, very quickly you need to have the dependency being passed to the service object constructor. Which usually means, that the Rails controller needs to do it. This blogpost describes how to have a different implementation being passed in the production environment and an in-memory one in the tests. 11 | 12 | 13 | 14 | There are several possible solutions, but one could be closer to the hearts of many Rails developers. Let's just use the built-in Rails environments and configure them appropriately: 15 | 16 | ``` 17 | $ ag foo_adapter config/environments/ 18 | config/environments/development.rb 19 | 187: config.foo_adapter = FooAdapter.new 20 | 21 | config/environments/production.rb 22 | 164: config.foo_adapter = FooAdapter.new 23 | 24 | config/environments/test.rb 25 | 184: config.foo_adapter = InMemoryFooAdapter.new 26 | ``` 27 | 28 | As you see, we have the same implementations in the production/dev environments, but a different one in the tests. The tests use an in-memory implementation which probably doesn't really send the requests to the Foo API. 29 | 30 | In the Rails controller, you can then initialize the service object with: 31 | 32 | ```ruby 33 | 34 | def create 35 | RegisterNewUser.new(Rails.application.config.foo_adapter).call 36 | #... 37 | end 38 | ``` 39 | 40 | while in the service object you stay unaware of the difference: 41 | 42 | ```ruby 43 | 44 | class RegisterNewUser 45 | def initialize(foo_adapter) 46 | @foo_adapter = foo_adapter 47 | end 48 | end 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /posts/2017-02-06-a-potential-problem-with-pstore-and-rails.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-02-06 16:40:01 +0100 3 | publish: true 4 | tags: [ 'rails' ] 5 | author: Andrzej Krzywda 6 | --- 7 | 8 | # A potential problem with PStore and Rails 9 | 10 | Today, I've noticed an interesting [post about PStore in Ruby](http://blog.redpanthers.co/pstore-ruby-standard-library/). This reminded me a recent story we've had with PStore. 11 | 12 | Let me start by saying that I like PStore. It's a simple solution which can definitely work. 13 | 14 | 15 | 16 | I like to think about persistence as something that can be easily replaced if needed. At least in theory ;) In our projects , we often use the pattern of repository. As long as you provide another repository implementation which has the same API, persistence should still work. 17 | 18 | In one of our >5.years projects, we've been migrating servers to a better machine. As part of this, we've made one app to work on several nodes instead of 1 as it was so far. The database node was already separated, all cool. 19 | 20 | During the migration, the developer followed the Capistrano file (code never lies, that's the beauty of Capistrano!) and noticed that as part of the deployment we link to the pstore file. After quick investigation, he noticed that one module of the app doesn't use the relational database, but used PStore. It's a very rarily used module and a very small one (as in 2 "tables"). 21 | 22 | This made the server migration a bit more complex. We either need to have a network-based file system now, so that 2 nodes can use it, or we need to refactor the module (write a new repo object) to use database. Refactoring sounds easier. 23 | 24 | The story here is that while PStore was a nice experiment here, there were infrastructural consequences of this decision :) Accessing the filesystem is not the thing that cloud providers like to give us nowadays ;) 25 | -------------------------------------------------------------------------------- /posts/2016-06-24-rails-refactoring-podcast-6-frontend-friendly-rails.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-06-24 03:05:00 +0200 3 | publish: true 4 | author: Wiktor Mociun 5 | tags: [ "rails", "refactoring", "podcast"] 6 | --- 7 | 8 | # Rails Refactoring Podcast #6 - Frontend Friendly Rails 9 | 10 | In this episode of Rails Refactoring Podcast, Wiktor and Marcin are discussing 11 | new Frontend-Friendly Rails book. It tells about improving frontend 12 | infrastructure provided by Rails. We are covering few topics from the book: 13 | 14 | - UUID and Rails 15 | - JSON API 16 | - Modern frontend infrastructure with Node.js 17 | - ESLint 18 | 19 | 20 | 21 | 24 | 25 | [Download MP3 file](https://rails-refactoring.com/podcast/rails-refactoring.com_06.mp3). 26 | 27 | You can subscribe to _Rails Refactoring Podcast_ on [RSS](http://rails-refactoring.com/podcast/rss.xml) or on [iTunes](https://itunes.apple.com/en/podcast/rails-refactoring-podcast/id943212549). 28 | 29 | ## Show notes 30 | 31 | - [Frontend-Friendly Rails book](http://blog.arkency.com/frontend-friendly-rails/) 32 | - [JSON API](http://jsonapi.org/) 33 | - [JSONAPI::Serializers Gem](https://github.com/fotinakis/jsonapi-serializers) 34 | - [Creating new content types in Rails 4.2](http://blog.arkency.com/2016/03/creating-new-content-types-in-rails-4-dot-2/) 35 | - [The Hitchhiker’s Guide to Modern JavaScript Tooling](http://reactkungfu.com/2015/07/the-hitchhikers-guide-to-modern-javascript-tooling/) 36 | - [Setting up ESLint](https://medium.com/planet-arkency/catch-mistakes-before-you-run-you-javascript-code-6e524c36f0c8) 37 | - 40% off coupon: **PODCAST_FRONTEND_FRIENDLY** 38 | -------------------------------------------------------------------------------- /posts/2022-11-14-be-careful-with-turbo-and-view-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2022-11-14 13:50:10 +0100 3 | author: Tomasz Patrzek 4 | tags: ["hotwire", "rails"] 5 | publish: true 6 | --- 7 | 8 | # Be careful with turbo and view components 9 | 10 | In our project we are using view components to unify button styles in view templates. 11 | Something similar to: 12 | 13 | ```ruby 14 | class Forms::Button < ViewComponent::Base 15 | def initialize(type:, text:, options: {}, data: {}, disabled: false) 16 | @type = type 17 | @text = text 18 | @options = options 19 | @data = data 20 | @disabled = disabled 21 | end 22 | 23 | def call 24 | case @type 25 | #... 26 | when "link" 27 | link_to( 28 | @options[:href], 29 | data: @data, 30 | class: "#{@options[:text_color]} #{@options[:bg_color]} #{css_styles} app-btn", 31 | ) do 32 | tag.p(@text) 33 | end 34 | end 35 | end 36 | 37 | ``` 38 | In order to make a POST request with the given component I've decided to make use of the link_to option: `method`: 39 | 40 | 41 | ```ruby 42 | #... 43 | link_to( 44 | @options[:href], 45 | method: @options[:method] || "GET", 46 | data: @data, 47 | class: "#{@options[:text_color]} #{@options[:bg_color]} #{css_styles} app-btn", 48 | ) 49 | #... 50 | ``` 51 | 52 | And it works. However it turned out there is an issue. 53 | Our other component links stopped to work. 54 | The link_to with option method will dynamically create an HTML form and immediately submit it. 55 | It uses @rails/ujs and it turns out that GET forms are not compatible with turbo stream responses. 56 | The fix was to remove the `:method` option, and use a button_tag for the `:post` request. 57 | Beside of this, since Rails 7 @rails/ujs library is no longer on by default. Also the link_to attriubute "method" is a deprecated. 58 | So, do not use it. 59 | 60 | -------------------------------------------------------------------------------- /posts/2016-02-22-private-classes-in-ruby.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-02-22 11:49:07 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'ruby', 'private', 'class' ] 6 | newsletter: clean 7 | --- 8 | 9 | # Private classes in Ruby 10 | 11 | One of the most common way to make some part of your code more understandable and explicit is to extract a class. 12 | However, many times this class is **not intended for public usage**. It's an implementation detail of a bigger 13 | unit. It should not be used be anyone else but the module in which it is defined. 14 | 15 | So how do we hide such class 16 | so that others are not tempted to use it? So that it is clear that it is an **implementation detail**? 17 | 18 | 19 | 20 | I recently noticed that many people don't know that since Ruby 1.9.3 you can make a constant private. And that's 21 | your answer to _how_. 22 | 23 | ```ruby 24 | class Person 25 | class Secret 26 | def to_s 27 | "1234vW74X&" 28 | end 29 | end 30 | private_constant :Secret 31 | 32 | def show_secret 33 | Secret.new.to_s 34 | end 35 | end 36 | ``` 37 | 38 | The `Person` class can use `Secret` freely: 39 | 40 | 41 | ```ruby 42 | Person.new.show_secret 43 | # => 1234vW74X& 44 | ``` 45 | 46 | But others cannot access it. 47 | 48 | ```ruby 49 | Person::Secret.new.to_s 50 | # NameError: private constant Person::Secret referenced 51 | ``` 52 | 53 | So `Person` is the public API that you expose to other parts of the system and `Person::Secret` is just an 54 | implementation detail. 55 | 56 | You should probably not test `Person::Secret` directly as well but rather through the public `Person` API 57 | that your clients are going to use. That way your tests won't be brittle and **depended on implementation**. 58 | 59 | ## Summary 60 | 61 | That's it. That's the entire, small lesson. If you want more, subscribe to our mailing list below or [buy Fearless Refactoring](http://rails-refactoring.com). 62 | -------------------------------------------------------------------------------- /posts/2017-05-25-passive-aggresive-events-code-smell.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-05-25 13:49:31 +0300 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['domain event', 'ddd'] 6 | --- 7 | 8 | # Passive aggresive events - code smell 9 | 10 | Today, while sitting on our [Rails/DDD workshops](http://blog.arkency.com/ddd-training/) led by Robert in Lviv, I was thinking/preparing a design of the new aggregates in my project. Robert was just explaining aggregates and how they can communicate (with events). 11 | 12 | During the break, I asked Robert what he thinks about it and he mentioned a term, that I missed somehow. The term was coined by Martin Fowler in his [What do you mean by “Event-Driven”?](https://martinfowler.com/articles/201701-event-driven.html) article. 13 | 14 | 15 | 16 | Here is the particular quote: 17 | 18 | "A simple example of this trap is when an event is used as a passive-aggressive command. This happens when the source system expects the recipient to carry out an action, and ought to use a command message to show that intention, but styles the message as an event instead." 19 | 20 | In my case, it was a situation, where I have a `Company` aggregate and when it receives an external request to "change\_some\_state" it has to delegate it to its "children" objects. Those objects are just value object in the aggregate, but they are also aggregates on their own (as separate classes). The design was split into smaller aggregates with hope of avoiding `Your Aggregate Is Too Big` problem. 21 | 22 | I agree that with the approach I have planned my events are a little bit passive-aggresive and they sound more like commands. I will either live with that (but be aware of the trap) or I will consider using the Saga concept here (events as input, command as output). 23 | 24 | BTW, the whole article by Martin Fowler is [worth a read](https://martinfowler.com/articles/201701-event-driven.html). 25 | 26 | How do you deal with such problems in your DDD apps? 27 | -------------------------------------------------------------------------------- /posts/2021-01-11-how-well-rails-developers-actually-test-their-apps.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How well Rails developers actually test their apps 3 | created_at: 2021-01-13T09:02:15.550Z 4 | author: Tomasz Wróbel 5 | tags: ["testing"] 6 | publish: true 7 | --- 8 | 9 | # How well Rails developers actually test their apps 10 | 11 | Here are the results of our _State of Testing in Rails apps_ survey results. I have distilled the most interesting numbers for your reading pleasure and efficiency. Detailed charts [here](https://twitter.com/tomasz_wro/status/1348558886295506946). I have highlighted what stands out as interesting for me. Number of surveyees: 142. Thanks for being one! 12 | 13 | * 80% favor RSpec 14 | * 79% find testing is inseparable from software development 15 | * **54% say their app is well-tested** 16 | * 93% rely on unit tests 17 | * 30% work with a project with over 100 db tables 18 | * 33% work in a team of two or three 19 | * **18% run a single test in "blink of an eye", 46% under 5s** 20 | * **19% need more than half an hour to run the full suite on a development machine** 21 | * 86% run their tests on CI 22 | * **15% wait longer than half an hour for CI result** 23 | * **60% are "pretty much" confident in their test suite** 24 | * 57% drop everything and fix the build, if it happens to fail 25 | * **57% say their biggest problem with tests is that they take ages to run** 26 | * 39% never allow skipped test cases 27 | * **83% say tests help them refactor code** 28 | * 23% say they mostly test their JavaScript code 29 | * 73% use mocks, 72% stubs, 35% fakes 30 | * 39% do not assess coverage, 56% use simplecov 31 | * 32% do not aim for a specific coverage level, 31% aim for over 90% 32 | * 17% often get frustrated by random test failures 33 | * 32% just retry the build upon encountering a random failure 34 | * 50% do TDD sometimes, 23% do often, 9% do always 35 | 36 | Want to see **detailed charts**? Jump into [this twitter thread](https://twitter.com/tomasz_wro/status/1348558886295506946). Also, it's the best place to **comment** or ask further questions. 37 | -------------------------------------------------------------------------------- /posts/2021-04-28-rails-console-trick-i-had-no-idea-about.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2021-04-28 09:20:25 +0200 3 | author: Paweł Pacana 4 | tags: ['rails'] 5 | publish: true 6 | --- 7 | 8 | # Rails console trick I had no idea about 9 | 10 | Tweaking `.irbrc` to make interactive console comfortable is a highly-rewarding activity. You gets instant boost of productivity and there are less frustrations. There were numerous posts and tips featured in Ruby Weekly on this topic recently. 11 | 12 | I've been making my `.irbrc` more useful too. The harder part was always distributing those changes to remote servers. For example to have those goodies available in Heroku console. And not only for me. There are multiple ways to achieve that, duh. The one that stick though was close to the app code: 13 | 14 | ```ruby 15 | # script/likeasir.rb 16 | 17 | # Nice things to have when entering production console 18 | # load 'script/likeasir.rb' 19 | 20 | def event_store 21 | Rails.configuration.event_store 22 | end 23 | 24 | def command_bus 25 | Rails.configuration.command_bus 26 | end 27 | 28 | # ... 29 | ``` 30 | 31 | You'd open the console first and load the helpers next to the IRB session with: 32 | 33 | ```ruby 34 | load 'script/likeasir.rb' 35 | ``` 36 | 37 | And then [Kuba](https://blog.arkency.com/authors/jakub-kosinski/) showed me a neat trick that made this load step completely obsolete: 38 | 39 | ```ruby 40 | # config/application.rb 41 | 42 | module MyApp 43 | class Application < Rails::Application 44 | # ... 45 | 46 | console do 47 | module DummyConsole 48 | def event_store 49 | Rails.configuration.event_store 50 | end 51 | 52 | def command_bus 53 | Rails.configuration.command_bus 54 | end 55 | end 56 | Rails::ConsoleMethods.include(DummyConsole) 57 | end 58 | end 59 | end 60 | ``` 61 | 62 | Now, whenever you load `bin/rails c`, the `command_bus` and `event_store` methods will be present in the IRB session. 63 | 64 | That's it. That's the trick I did not know about for years. 65 | 66 | You're welcome. 67 | 68 | -------------------------------------------------------------------------------- /posts/2014-08-19-how-we-structure-our-front-end-rails-apps-with-react-dot-js.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2014-08-19 02:28:05 +0200 3 | publish: true 4 | author: Wiktor Mociun 5 | tags: [ 'front end', 'react', 'javascript' ] 6 | newsletter: react_books 7 | --- 8 | 9 | # How we structure our front-end Rails apps with React.js 10 | 11 |

12 |

13 | " width="100%"> 14 |
15 |

16 | 17 | We've tried almost everything for our Rails frontends - typical Rails views, Backbone, Angular and others. What we settled with is React.js. In this post we're showing you, how we structure a typical React.js app when it comes to the files structure. 18 | 19 | 20 | 21 | Our file structure per a single mini-application: 22 | 23 | ```bash 24 | app_init.js.coffee 25 | --- app_directory 26 | --- app.module.js.coffee 27 | --- backend.module.js.coffee 28 | --- components 29 | --- component_file1.module.js.coffee 30 | ... 31 | --- domain.module.js.coffee 32 | --- glue.module.js.coffee 33 | ``` 34 | 35 | app_init - we got one per each application. We always keep it simple: 36 | 37 | ```coffeescript 38 | #= require_tree ./app_directory 39 | 40 | App = require('app_directory/app') 41 | 42 | $('[data-app=appFromAppDirectory]').each -> 43 | window.app = new App(@) 44 | window.app.start() 45 | ``` 46 | 47 | * **app** - starting point of application. Here we initialize and start every component of application 48 | 49 | * **backend** - here we fetch and send data to backend. It is also a place, where we create domain objects 50 | 51 | * **components** - our React.js components we use to render an application. 52 | 53 | * **domain** - definitions of domain objects used in view. Example: immutable list of single entries (which are domain objects too). 54 | 55 | * **glue** - [hexagonal.js](https://hexagonaljs.github.io) glue 56 | 57 | ## Further reading 58 | 59 | * **Hexagonal.js** - implementation of clean hexagonal architecture - https://hexagonaljs.github.io 60 | 61 | * **RxJS** - we use reactive data streams to communicate between apps - https://github.com/Reactive-Extensions/RxJS 62 | -------------------------------------------------------------------------------- /posts/2016-10-24-running-bash-command-from-ruby-with-your-bash-profile.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-10-24 13:14:46 +0200 3 | publish: true 4 | tags: [ 'ruby' ] 5 | author: Andrzej Krzywda 6 | --- 7 | 8 | # Running bash command from Ruby (with your bash_profile) 9 | 10 | Whenever I try to automate some of my daily tasks, I end up with a mix of Ruby and Bash scripts. This is the time when I look up to the differences between `system`, `exec`, `%x`, backticks and others. 11 | 12 | However, there's additional thing with actually executing a bash script not just shell script. 13 | 14 | 15 | 16 | 17 | Yesterday I've been optimizing my blogging flow. One part of it is to open my favourite editor with the current draft file. 18 | 19 | Until yesterday I did it manually. I've had this in my `bash_profile`: 20 | 21 | ``` 22 | alias ia="open $1 -a /Applications/iA\ Writer.app/Contents/MacOS/iA\ Writer" 23 | ``` 24 | 25 | so just typing `ia content/posts/a_long_path_to_the_file.md` was opening the editor. 26 | 27 | Now, I have a script which not only generates the draft file, but also git pushes it, opens the browser to preview it and opens the editor. 28 | 29 | ``` 30 | def call 31 | create_local_markdown_file_based_on_template 32 | git_add_commit_push 33 | open_browser_with_production_url 34 | open_draft_in_editor 35 | end 36 | ``` 37 | 38 | See my previous blogpost to [read more about this specific Ruby service object](http://blog.arkency.com/2016/10/the-esthetics-of-a-ruby-service-object/) 39 | 40 | The thing is, if you just use `system` it's not enough. You need to invoke `bash` in a special mode `-ilc` to actually get the `bash_profile` loaded. Otherwise, the `ia` alias is not recognized. 41 | 42 | So, I ended up with this: 43 | 44 | ```ruby 45 | def open_draft_in_editor 46 | system("bash", "-lic", "ia #{path}") 47 | end 48 | ``` 49 | 50 | Which works great so far. It helped me speeding up my blogging process and hopefully will result in more blogposts ;) 51 | 52 | Happy blogging! 53 | 54 | ----- 55 | 56 | BTW, if you want to improve your blogging skils, my "Blogging for Busy Programmers" book is now part (for a limited time) of the [Smart Income For Developers bundle](http://www.smartincomefordevelopers.com). Check it out! 57 | -------------------------------------------------------------------------------- /posts/2017-05-24-self-hosting-event-store-on-digital-ocean.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-05-24 11:46:11 +0300 3 | publish: true 4 | tags: [ 'rails event store', 'ddd' ] 5 | author: Andrzej Krzywda 6 | --- 7 | 8 | # Self-hosting Event Store on Digital Ocean 9 | 10 | Recently in one of our projects, we have decided that it would be a good idea to switch to [EventStore](https://geteventstore.com). Our current solution is based on [RailsEventStore](https://github.com/RailsEventStore/rails_event_store) (internal to each Bounded Context) and an external RabbitMQ to publish some event "globally". This approach works, but relying on EventStore sounds like a better approach. For a long time, we felt blocked, as EventStore doesn't offer a hosted solution and we were not sure if we want to self-host (in addition to the current heroku setup). 11 | 12 | 13 | 14 | Luckily, one of the Arkency developers, Paweł, was following the discussion and quickly timeboxed a solution of self-hosting Event Store on Digital Ocean. It took him super quick to deliver a working node. This enables us to experiment with partial switching to EventStore. 15 | 16 | I have asked Paweł to provide some instructions how he did it, as it seems to a very popular need among the DDD/CQRS developers. 17 | 18 | Here are some of the notes. If it lacks any important information, feel free to ping us in the comments. 19 | 20 | ``` 21 | $ apt-get update 22 | $ curl -s https://packagecloud.io/install/repositories/EventStore/EventStore-OSS/script.deb.sh | sudo bash 23 | $ apt-get install eventstore-oss 24 | ``` 25 | 26 | ``` 27 | $ ifconfig eth0 |grep addr: 28 | inet addr:XXX.XXX.XXX.NNN Bcast:XXX.XXX.XXX.255 Mask:255.255.255.0 29 | inet6 addr: fe80::36:88ff:febb:5d6d/64 Scope:Link 30 | ``` 31 | 32 | ``` 33 | $ echo "ExtIp: XXX.XXX.XXX.NNN" >> /etc/eventstore/eventstore.conf 34 | $ cat /etc/eventstore/eventstore.conf 35 | --- 36 | RunProjections: None 37 | ClusterSize: 1 38 | ExtIp: XXX.XXX.XXX.NNN 39 | ``` 40 | 41 | ``` 42 | $ service eventstore start 43 | ``` 44 | 45 | Those are the instructions for the basic setup/installation. You can now start experimenting with EventStore. For production use though you'd need to invest in reliability (clustering, process supervision and monitoring) as well as in security. 46 | -------------------------------------------------------------------------------- /posts/2021-04-14-how-to-delete-jobs-from-sidekiq-retries.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to delete jobs from Sidekiq Retries 3 | created_at: 2021-04-14T19:33:34.618Z 4 | author: Tomasz Wróbel 5 | tags: [ 'sidekiq', 'background job' ] 6 | publish: true 7 | --- 8 | 9 | Hi future-me! This is just a list of snippets I might be looking for next time I suddenly have to deal with a huge list of failing Sidekiq jobs being retried over and over. 10 | 11 | ### List job classes sitting in the retries list 12 | 13 | ```ruby 14 | Sidekiq::RetrySet.new 15 | .map(&:display_class) 16 | .uniq 17 | ``` 18 | 19 | ### Number of jobs being retried for a specific class 20 | 21 | ```ruby 22 | Sidekiq::RetrySet.new 23 | .select { |j| j.display_class == "AJob" } 24 | .count 25 | ``` 26 | 27 | ### Delete all jobs for a class from the retries list 28 | 29 | ```ruby 30 | Sidekiq::RetrySet.new 31 | .select { |j| j.display_class == "AJob" } 32 | .map(&:delete) 33 | ``` 34 | 35 | (similarly, there's `&:kill`, `&:retry`) 36 | 37 | (similarly, there's `Sidekiq::DeadSet`, `Sidekiq::ScheduledSet`) 38 | 39 | ### If the jobs are RES async handlers, list the events: 40 | 41 | ```ruby 42 | Sidekiq::RetrySet.new 43 | .select { |j| j.display_class == "MyAsyncHandler" } 44 | .collect { |j| j.args[0]["arguments"][0]["event_id"] } 45 | .collect { |id| Rails.configuration.event_store.read.event(id) } 46 | ``` 47 | 48 | (warning: the details depend on your [RES](https://railseventstore.org/docs/v2/install/) async scheduler implementation) 49 | 50 | ### Unique error messages for a class of jobs 51 | 52 | ```ruby 53 | Sidekiq::RetrySet.new 54 | .select { _1.display_class == "AJob" } 55 | .map { _1.item["error_message"] } 56 | .uniq 57 | ``` 58 | 59 | ## More 60 | 61 | * https://github.com/mperham/sidekiq/wiki/API#retries 62 | * https://gist.github.com/wbotelhos/fb865fba2b4f3518c8e533c7487d5354 63 | * https://www.mikeperham.com/2021/04/20/a-tour-of-the-sidekiq-api/ — Fun fact: Mike Perham (Sidekiq's author) wrote this post after [stumbling upon my piece and deeming it incomplete](https://twitter.com/getajobmike/status/1382482181725900801), quite understandably. I never intended this article to a comprehensive walk-through of Sidekiq's API, just a list of snippets I use most often. Now we have the author expanding on the topic. Everyone benefits :) 64 | -------------------------------------------------------------------------------- /posts/2015-07-06-am-i-ignored-in-my-async-team.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-07-06 09:19:01 +0200 3 | publish: true 4 | tags: [ 'async remote' ] 5 | author: Andrzej Krzywda 6 | newsletter: async_remote_main 7 | --- 8 | 9 | # Am I ignored in my async team? 10 | 11 | When we work in async teams, we have many benefits from it - people not blocked on each other, freedom of place, freedom of time. 12 | We also have some drawbacks. You can't expect an instant reply to whatever you ask for. In the nature of async, you can't even be sure when you get the reply. What's interesting, you can't even be sure that all (any?) people actually received your message. 13 | 14 | 15 | 16 | In distributed systems we have the same problems (is the other server down?). We're not machines, but some techniques may be worth applying. Which ones come to your mind? 17 | 18 | In some cases, you may think that you are being ignored. That's a bad feeling (been there). What I do in such case is that I remind myself that I also didn't reply to every conversation that is happening. Sometimes I'm the one who ignores. It's never intended. Often, it's because I'm super busy and I don't want to switch the context immediately. I try to be organized and when I see an important conversation to participate I add a task to my GTD (recently it's Apple Reminders). It's not always the case, though. Sometimes I fuck up and don't remember to come back to that conversation. If there's more people like me who don't answer - the asking person may feel terrible and that's sad. 19 | 20 | I know that this may be expecting too much, but I expect the asking person to "internally sell" the conversation/question/problem in better ways. First of all, friendly-ping the topic again after some time. Consider using another medium - slack/hackpad/discourse/mumble/trello/github/email/standup. 21 | 22 | Async teams are great but require some special skills - overcommunication, proactiveness, trust (always assume the best intentions), selling skills, good writing. 23 | 24 | BTW, As Arkency, we add anarchy to the async environment. What does it mean in practice? You don't need to ask for agreement on something. It's good to overcommunicate what you're doing and make your idea happen. In the worst case, me or someone else will jump and say - "don't do that please" and this can start a discussion. 25 | -------------------------------------------------------------------------------- /posts/2016-01-22-drop-this-before-validation-and-use-method.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-01-29 10:02:10 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'validations', 'aop', 'ruby', 'rails' ] 6 | newsletter: clean 7 | --- 8 | 9 | # Drop this before validation and just use a setter method 10 | 11 | In many projects you can see code such as: 12 | 13 | ```ruby 14 | 15 | class Something 16 | before_validation :strip_title 17 | 18 | def strip_title 19 | self.title = title.strip 20 | end 21 | end 22 | ``` 23 | 24 | However there is **different way** to write this requirement. 25 | 26 | 27 | 28 | ```ruby 29 | 30 | class Something 31 | def title=(val) 32 | @title = val.strip 33 | end 34 | end 35 | ``` 36 | 37 | ...or... 38 | 39 | ```ruby 40 | class Something 41 | def title=(val) 42 | self['title'] = val.strip 43 | end 44 | end 45 | ``` 46 | 47 | ...or... 48 | 49 | ```ruby 50 | class Something 51 | def title=(val) 52 | super(val.strip) 53 | end 54 | end 55 | ``` 56 | 57 | ...depending on the way you keep the data inside the class. Various gems use various ways. 58 | 59 | Here is why I like it that way: 60 | 61 | * it **explodes** when `val` is `nil`. Yes, I consider it to be **a good thing**. Rarely my frontend can send `nil` as title 62 | so when it happens most likely something would be broken and exception is OK. It won't happen anyway. It's just my 63 | programmer lizard brain telling me all corner cases. I like this part of the brain. But sometimes it deceives us and 64 | makes us focus on cases which won't happen. 65 | * It's **less magic**. Rails validation callbacks are cool and I've used them many times. That said, I don't need them to 66 | strip fuckin' spaces. 67 | * It **works** in more cases. It works when you read the field after setting it, without doing save in between. Or if you 68 | save without running the validations (for whatever reasons). 69 | 70 | ```ruby 71 | something.code = " 123 " 72 | something.code 73 | # => 123 74 | 75 | something.save(validate: false) 76 | ``` 77 | 78 | I especially like to impose such cleaning rules on objects used for crossing boundaries such as **Command** or **Form objects**. 79 | 80 | ## Summary 81 | 82 | That's it. That's the entire, small lesson. If you want more, subscribe to our mailing list below or [buy Fearless Refactoring](http://rails-refactoring.com). -------------------------------------------------------------------------------- /posts/2016-07-08-always-present-association.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-07-12 17:42:41 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'rails', 'active record' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # Always present association 10 | 11 | Recently my colleague showed my a little trick 12 | that I found to be very useful in some situations. 13 | It's nothing fancy or mind-blowing or unusual 14 | in terms of using Ruby. It's just applied in a way 15 | that I haven't seen before. It kind of even seems 16 | obvious after seeing it :) 17 | 18 | 19 | 20 | ## The trick 21 | 22 | ```ruby 23 | class Order < ActiveRecord::Base 24 | has_one :meta_data, dependent: :destroy, autosave: true 25 | 26 | def meta_data 27 | super || build_meta_data 28 | end 29 | 30 | delegate :ip_address, :ip_address= 31 | :user_agent, :user_agent= 32 | to: :meta_data, 33 | prefix: false 34 | end 35 | ``` 36 | 37 | ## Nice 38 | 39 | Now you can just do: 40 | 41 | ```ruby 42 | order.ip_address = request.remote_ip 43 | order.save! 44 | ``` 45 | 46 | without wondering if `order.meta_data` is `nil` because 47 | if this associated record was never saved then 48 | `build_meta_data` will create a new one for you. 49 | 50 | Same goes with reading such attributes. You can get `nil` 51 | but you won't get `NoMethodError` from calling `ip_address` 52 | on an empty association (`nil`). 53 | 54 | ## Not so nice 55 | 56 | It has some downsides, however. Reading (event an empty) `ip_address` 57 | can trigger a side-effect in saving the `meta_data`. 58 | 59 | ```ruby 60 | ip = order.ip_address 61 | order.save! 62 | ``` 63 | 64 | `MetaData` can not have non-null columns unless you set all of them 65 | at the same time. Otherwise, when 66 | `ip_address` can be null but `user_agent` cannot, setting only 67 | one of them will cause troubles. 68 | 69 | ```ruby 70 | order.ip_address = request.remote_ip 71 | order.save! # Exception 72 | ``` 73 | 74 | The same problem can occur with validations on `MetaData`. 75 | 76 | ## Summary 77 | 78 | But if you don't have such situations in your code and just have 79 | multiple attributes that are either optional or all set at the 80 | same time, then why not. 81 | 82 | ## P.S. 83 | 84 | Check out more patterns that can help you in maintaining Rails apps in our [Fearless Refactoring: Rails controllers ebook](http://rails-refactoring.com/) 85 | -------------------------------------------------------------------------------- /posts/2017-09-29-which-ruby-version-am-i-using-how-to-check.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-09-29 11:22:09 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'ruby', 'version', 'check' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # Which ruby version am I using — how to check? 10 | 11 | Are you not sure which Ruby version you are using right now? Wondering how to check it? Say no more. Here are two simple ways to check for it. 12 | 13 | 14 | 15 | ## In irb 16 | 17 | Run `irb` and type: 18 | 19 | ```ruby 20 | RUBY_VERSION 21 | # => "2.4.1" 22 | ``` 23 | 24 | ## From command line 25 | 26 | Just type `ruby -v` 27 | 28 | ``` 29 | $ ruby -v 30 | ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux] 31 | ``` 32 | 33 | ## In RVM 34 | 35 | Are you using RVM? 36 | 37 | Run `rvm current` and get the answer 38 | 39 | ``` 40 | $ rvm current 41 | ruby-2.4.1 42 | ``` 43 | 44 | ## In rbenv 45 | 46 | Are you using rbenv? Just run `rbenv version` 47 | 48 | ``` 49 | $ rbenv version 50 | 2.4.1p111 (set by /Users/rupert/.rbenv/version) 51 | ``` 52 | 53 | ## Using which 54 | 55 | Do you want to know where your `ruby` binary is installed? It can also sometimes reveal the version you are using as it is usually part of directory structure. Just run `which ruby`. 56 | 57 | ``` 58 | $ which ruby 59 | /home/rupert/.rvm/rubies/ruby-2.4.1/bin/ruby 60 | ``` 61 | 62 | ## Using gem env 63 | 64 | If you want to know even more about your current ruby setup, there is a command for that as well! 65 | 66 | ``` 67 | $ gem env 68 | RubyGems Environment: 69 | - RUBYGEMS VERSION: 2.6.12 70 | - RUBY VERSION: 2.4.1 (2017-03-22 patchlevel 111) [x86_64-linux] 71 | - INSTALLATION DIRECTORY: /home/rupert/.rvm/gems/ruby-2.4.1 72 | - USER INSTALLATION DIRECTORY: /home/rupert/.gem/ruby/2.4.0 73 | - RUBY EXECUTABLE: /home/rupert/.rvm/rubies/ruby-2.4.1/bin/ruby 74 | - EXECUTABLE DIRECTORY: /home/rupert/.rvm/gems/ruby-2.4.1/bin 75 | - SPEC CACHE DIRECTORY: /home/rupert/.gem/specs 76 | - SYSTEM CONFIGURATION DIRECTORY: /etc 77 | - RUBYGEMS PLATFORMS: 78 | - ruby 79 | - x86_64-linux 80 | - GEM PATHS: 81 | - /home/rupert/.rvm/gems/ruby-2.4.1 82 | - /home/rupert/.rvm/gems/ruby-2.4.1@global 83 | - GEM CONFIGURATION: 84 | - :update_sources => true 85 | - :verbose => true 86 | - :backtrace => false 87 | - :bulk_threshold => 1000 88 | - "gem" => "--no-document" 89 | - REMOTE SOURCES: 90 | - https://rubygems.org/ 91 | ``` 92 | -------------------------------------------------------------------------------- /posts/2017-10-06-rails-how-to-find-records-where-column-is-not-null-or-empty.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-09-04 16:11:04 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'rails', 'active record' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # How to find records where column is not null or empty in Rails 4 or 5 10 | 11 | We all know that especially in legacy applications sometimes our database columns are not that well maintained. So we need to query for, or exclude rows containing `nil`/`NULL` and empty strings (`""`) as well. How can we do it in ActiveRecord? 12 | 13 | 14 | 15 | Let's say your Active Record model is called `User` and the DB column we are going to be searching by is `category`. 16 | 17 | ## find records where column is null or empty 18 | 19 | That's simple. 20 | 21 | ```ruby 22 | User.where(category: [nil, ""]) 23 | ``` 24 | 25 | ## find records where column is not null or empty 26 | 27 | Still easy. 28 | 29 | ```ruby 30 | User.where.not(category: [nil, ""]) 31 | ``` 32 | 33 | _A contribution from [pjmartorell](https://github.com/pjmartorell):_ 34 | 35 | > `User.where.not(category: "")` will also find records where column is not null or empty, but in a more efficient way than using the array form (`NOT IN`). Because of the nature of `not`, it does not fetch records where `category` is `null`. You can read more about this topic [here](https://thoughtbot.com/blog/activerecord-s-where-not-and-nil). Checked in Rails 6.0.1 and PostgreSQL. 36 | 37 | This `not()` clause is going to only apply to one `where`. You are not going to negate all previous conditions. In other words you can safely use it like this: 38 | 39 | ```ruby 40 | User. 41 | where(state: "active"). 42 | where.not(category: [nil, ""]). 43 | where("created_at > ?", 5.days.ago) 44 | ``` 45 | 46 | to get SQL statement like this: 47 | 48 | ```sql 49 | SELECT "users".* FROM "users" 50 | WHERE 51 | "users"."state" = "active" AND 52 | (NOT (("users"."category" = '' OR "users"."category" IS NULL))) AND 53 | (created_at > '2017-09-01 14:27:11') 54 | ``` 55 | 56 | ## Would you like to continue learning more? 57 | 58 | If you enjoyed the article, [subscribe to our newsletter](http://arkency.com/newsletter) so that you are always the first one to get the knowledge that you might find useful in your 59 | everyday Rails programmer job. 60 | 61 | Content is mostly focused on (but not limited to) Ruby, Rails, Web-development and refactoring big, complex Rails applications. 62 | -------------------------------------------------------------------------------- /posts/2016-11-09-ruby-exceptions-are-4400-times-faster-than-activerecord-base-number-create.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-11-09 10:15:36 +0100 3 | publish: true 4 | tags: [ 'ruby', 'exceptions' ] 5 | author: Andrzej Krzywda 6 | --- 7 | 8 | # Ruby exceptions are 4400 times faster than ActiveRecord::Base#create 9 | 10 | How slow are Ruby exceptions as compared to other frequent actions we may be doing in Rails apps. Like for example, as compared to ActiveRecord::Base#create 11 | 12 | 13 | 14 | Big thanks to [Robert Pankowecki](https://twitter.com/pankowecki) who created [the original gist](https://gist.github.com/paneq/a643b9a3cc694ba3eb6e) and to [Piotr Szotkowski](https://twitter.com/chastell) who provided even more data. The gist was originally created as part of our [Fearless Refactoring: Rails Controllers book](http://rails-refactoring.com) where Ruby exceptions are suggested as one of the possible techniques of the controller communication with service objects. 15 | 16 | ```ruby 17 | 18 | require 'active_record' 19 | require 'benchmark/ips' 20 | 21 | ActiveRecord::Base.logger = nil 22 | ActiveRecord::Base.establish_connection adapter: 'postgresql', 23 | database: 'whatevers' 24 | 25 | Whatever = Class.new(ActiveRecord::Base) 26 | 27 | Benchmark.ips do |bench| 28 | bench.report('SQL query') { Whatever.create(text: 'meh') } 29 | bench.report('exception hit') { raise StandardError.new rescue nil } 30 | bench.report('exception miss') { raise StandardError.new if false } 31 | bench.compare! 32 | end 33 | ``` 34 | 35 | Then we can run it with: 36 | 37 | 
ruby -v bench.rb
 38 | 39 | with results like this: 40 | 41 | ``` 42 | ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux] 43 | Warming up -------------------------------------- 44 | SQL query 18.000 i/100ms 45 | exception hit 57.640k i/100ms 46 | exception miss 269.546k i/100ms 47 | Calculating ------------------------------------- 48 | SQL query 182.124 (±15.9%) i/s - 882.000 in 5.003193s 49 | exception hit 808.613k (± 4.8%) i/s - 4.035M in 5.004163s 50 | exception miss 10.129M (± 3.2%) i/s - 50.675M in 5.007747s 51 | 52 | Comparison: 53 | exception miss: 10129279.5 i/s 54 | exception hit: 808613.1 i/s - 12.53x slower 55 | SQL query: 182.1 i/s - 55617.43x slower
 56 | ``` 57 | 
 58 | 59 | which means that for this configuration, the exceptions are 4438 times faster than AR::Base#create. 60 | -------------------------------------------------------------------------------- /posts/2020-04-02-the-truth-about-rails-convention-over-configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2020-04-02 10:24:17 +0200 3 | author: Andrzej Krzywda 4 | tags: [ 'rails', 'architecture' ] 5 | publish: false 6 | --- 7 | 8 | # The truth about Rails Convention over Configuration 9 | 10 | This blogpost is a work in progress. It's also a call for collaboration to Arkency friends (via pull requests - [https://github.com/arkency/posts/edit/master/posts/2020-04-02-the-truth-about-rails-convention-over-configuration.md](https://github.com/arkency/posts/edit/master/posts/2020-04-02-the-truth-about-rails-convention-over-configuration.md) ) if the goal of this blogpost resonates with you. 11 | 12 | The goal of this post is to: 13 | 14 | * show definition of the word "convention" 15 | * remind what configuration meant before Rails appeared (maybe examples from Struts XML?) 16 | * provide as many examples of Rails conventions as possible 17 | * summarize/group those conventions - it's likely that many of those are just metaprogramming or "magic" 18 | * explain why those conventions optimize for the first N days of developing the Rails app 19 | * explain, provide examples - how some of the conventions introduce coupling at the design level 20 | * conclude that Rails Convention over Configuration may lead and often leads to technical debt 21 | * explain ideas behind Architecture over Convention 22 | * provide alternative solutions to the listed Rails conventions - link to other blogposts/resources, including Arkency ones, but not limited to them 23 | 24 | Feel free to help with any of the points here, just create a section and contribute. Probably the easiest contributions (but helpful!) would be listing more examples of Rails conventions. 25 | 26 | Once the goals are accomplished, this blogpost will be published, linked from Arkency blog index and linked from the sitemap. Before it happens, it has its own URL which you can send to other potential collaborators - [https://blog.arkency.com/the-truth-about-rails-convention-over-configuration](https://blog.arkency.com/the-truth-about-rails-convention-over-configuration) 27 | 28 | 29 | 30 | ## Convention - the definitions 31 | 32 | ## The old days of XML Configuration hell 33 | 34 | ## Examples 35 | 36 | * copying controller instance variables into views 37 | * automatic mapping from column names to ActiveRecord attributes 38 | 39 | ## How the conventions help 40 | 41 | ## Coupling - examples 42 | 43 | ## Convention over Configration leading to a Technical debt 44 | 45 | ## Architecture over Convention 46 | 47 | ## Alternative solutions to typical Rails conventions 48 | 49 | -------------------------------------------------------------------------------- /posts/2013-05-13-the-a-team.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-05-14 10:50:19 +0200 3 | publish: true 4 | author: Jan Filipowski 5 | tags: ['async remote', 'identity', 'team'] 6 | newsletter: async_remote_main 7 | --- 8 | 9 | # The A Team 10 | 11 | I work in Arkency for almost 4 years and lately I've started to think how it influences me. Working together is not only about delivering some good as a bunch of people - team has own identity, own culture. These days I decided to write down what I found out about my team for two reasons. First is just to see some day how it evolved over years - but that's a good reason to write it in "My sweet diary", holden under a pillow. I'm also inspired by great blog series ["We are principled"](http://blog.8thlight.com/tags/craftsmanship.html) by 8th Light. 12 | 13 | 14 | 15 | I find team culture as a mix of team members common goals and values, which are mainly focused on good they produce, but also how they see the future, their career etc. So here's what I found out about my team: 16 | 17 | ## Goals: 18 | 19 | 1. **Produce good for the customer** - nothing to say, it's a common goal for all companies. 20 | 21 | 2. **Hacker-friendly job** - we all are more or less nerds. We want to play with code, we want new challenges, we want to automate things. 22 | 23 | 3. **Educate** - great hackers share their knowledge and discuss about ideas. And we want to be great hackers. 24 | 25 | 4. **Remoteness** - work wherever you are and whenever you want. 26 | 27 | ## Values: 28 | 29 | * **Collective code ownership** - each line of code is owned by whole team, so when bug shows up it's whole team's problem, not author of latest commit or author of given line. 30 | 31 | * **Don't repeat mistakes** - learn from them, test against them. 32 | 33 | * **Reliability** - customer can depend on you: your skills and your commitment. 34 | 35 | * **Anarchy** - organizational loose coupling. 36 | 37 | * **Respect** - you listen carefully to people you respect. 38 | 39 | * **Business orientation** - always keep in mind what problem do you solve for your customer and your customer's users - where the money come from or where can we save them. Know the large picture of project and it's scale. 40 | 41 | ## So what? 42 | 43 | What can I get from this set of goals and rules? It's kind of model of this small world -- team's physics. So like in all other models - it simplicize reality, but helps to understand what's going on. Knowing current state you can also try to shape it into something different. It can also help you when you try to hire someone. 44 | 45 | So here's question to you, kind reader: how is your team? 46 | -------------------------------------------------------------------------------- /posts/2017-01-02-on-upcoming-immutable-string-literals-in-ruby.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-01-02 11:23:52 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'ruby', 'immutable', 'string' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # On upcoming immutable string literals in Ruby 10 | 11 | Today I checked one of the solutions made by our [Junior Rails Developer class](/junior-rails-developer/) 12 | student. As part of the course they make Rails apps but also learn from smaller 13 | code examples delivered by exercism.io. 14 | 15 | I found there an opportunity for him to learn more about a few things... 16 | 17 | 18 | 19 | That's his code which inspired me for a reflection. 20 | 21 | ```ruby 22 | string = '' 23 | string += 'Pling' if num % 3 == 0 24 | string += 'Plang' if num % 5 == 0 25 | string += 'Plong' if num % 7 == 0 26 | string += num.to_s if string.empty? 27 | string 28 | ``` 29 | 30 | I recommended reading about `#tap`; how one could use a Hash to remove some duplication. 31 | I also thought it could be a good occasion to talk about how Ruby String's are mutable 32 | but in some time [string literals will be immutable](https://bugs.ruby-lang.org/issues/11473) 33 | . The keyword here is **literals**. 34 | 35 | 36 | Let's focus on a very small part of the code: 37 | 38 | ```ruby 39 | string = '' 40 | string += 'Pling' if num % 3 == 0 41 | ``` 42 | 43 | We could easily refactor it to: 44 | 45 | ```ruby 46 | string = '' 47 | string << 'Pling' if num % 3 == 0 48 | ``` 49 | 50 | But not when string literals are enabled to be immutable. 51 | 52 | ``` 53 | rvm use ruby-2.3.0 54 | RUBYOPT=--enable-frozen-string-literal irb 55 | ``` 56 | 57 | ```ruby 58 | s = "" 59 | 60 | s.frozen? 61 | # => true 62 | 63 | s << "asd" 64 | # RuntimeError: can't modify frozen String 65 | ``` 66 | 67 | Obviously, because `s = ""` is a string literal. 68 | 69 | In such case we would need to go with a less elegant solution probably: 70 | 71 | ```ruby 72 | string = String.new 73 | string << 'Pling' if num % 3 == 0 74 | ``` 75 | 76 | because 77 | 78 | ```ruby 79 | s = String.new 80 | 81 | s.frozen? 82 | # => false 83 | 84 | s << "asd" 85 | # => "asd" 86 | ``` 87 | 88 | So just be aware that in upcoming Ruby versions `s = ""` and 89 | `s = String.new` might not be equal. And in the case when you 90 | are building a new string via multiple transformations or 91 | concatenations the 2nd version might be preffered. 92 | 93 | I wonder if some time later ruby (4 ?) will make all Strings 94 | immutable (not only those created via literals) and introduce 95 | `StringBuilder` class like .Net or Java has? 96 | 97 | Happy 2017! 98 | -------------------------------------------------------------------------------- /posts/2023-11-20-who-calls-who-a-simple-events-heuristic.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2023-11-20 09:46:34 +0100 3 | author: Tomasz Stolarczyk 4 | tags: ['commands', 'events', 'context mapping', 'bounded contexts'] 5 | publish: true 6 | --- 7 | 8 | # Who calls who? — a simple events heuristic 9 | 10 | When integrating two components, you may wonder who should own the commands and events you want to use for communication. Today, I will show you a simple heuristic that may help you make this decision. The heuristic relies on the **frequency of change** in components. Before using it, we should visualize relationships between those components. Context Mapping is a perfect tool for that, as it shows how changes in one Bounded Context affect others (there is an excellent resource about the concept itself [here](https://github.com/ddd-crew/context-mapping)). 11 | 12 | And now, without further ado, let's jump into an example. Let's assume that we have two components: _Registration_ and _Payment_. _Registration_ component changes a lot (green dots below mean a single change), as it has some rules that can change. On the other hand, _Payment_ component rarely changes. 13 | 14 | " width="100%"> 15 | 16 | Now let's assume we decided to communicate between those components using commands/events (interfaces) from the _Registration_: 17 | 18 | " width="100%"> 19 | 20 | It means that by doing that, we also defined the upstream-downstream relationship between those components, where _Registration_ is an upstream: 21 | 22 | " width="100%"> 23 | 24 | It also means there is a bigger chance that changes in the _Registration_ will cause some changes in _Payment_. There is a higher coupling between those two components. To make it looser, we should change the direction of the upstream-downstream relationship. We can do it by simply using commands/events from _Payment_ like this: 25 | 26 | " width="100%"> 27 | 28 | It's worth noticing how the names of interfaces changed based on which component was the upstream — such an experiment can sometimes impact how we see things. 29 | 30 | You can use the described heuristic to fix some relationships or to create them wisely if you integrate new Bounded Contexts. Of course, there are always more nuances, like, for example, changes in model vs. changes in contracts, but that can be a topic for a different story 😉 -------------------------------------------------------------------------------- /posts/2017-10-29-a-bug-that-only-appears-once-a-year.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-10-28 22:03:26 +0200 3 | publish: true 4 | author: Anton Paisov 5 | tags: [ 'bugs', 'testing' ] 6 | img: "ruby-rails-dst-timezone-change-bug-once-a-year/header-glass-time-watch-x.png" 7 | --- 8 | 9 | # A bug that only appears once a year 10 | 11 | There are some bugs that only appear under certain circumstances. Today was the day I've got one of those (there is a hint in this sentence). 12 | 13 | 14 | 15 | I pushed a small change and got a red build as a result. I already had the corresponding test fixed so red build was not something I was expecting. 16 | 17 | An exception I've got was from a check in `TicketTransferPolicy` which had nothing at all to do with my changes. And so the investigation began. 18 | 19 | ```ruby 20 | raise DeadlinePassed if deadline_passed?(event) 21 | ``` 22 | 23 | ```ruby 24 | def deadline_passed?(event) 25 | if FT.on?(:extended_tickets_transfer_deadline, organizer_id: event.user_id) 26 | event.ends_at < Time.current 27 | else 28 | event.starts_at < Time.current.advance(days: 1) 29 | end 30 | end 31 | ``` 32 | 33 | _Hint: failing test was not related to extended deadline._ 34 | 35 | I've looked into the failing test and here's the line that instantly got my attention: 36 | 37 | ```ruby 38 | event = test_organizer.create_published_event(starts_at: 25.hours.from_now) 39 | ``` 40 | 41 | This was an instant 'aha' moment when I've realized, today's the day when we have 25 hours in the day. 42 | 43 | In my opinion, the best solution here is to use `Time.current.advance(days: 1, hours: 1)` in the test instead of `25.hours.from_now`, this approach is more consistent with the code we're testing. 44 | Changing `25` to `26` would also work ;) 45 | 46 | Thanks, [DST](https://www.timeanddate.com/time/dst/) :P 47 | 48 | ### Would you like to continue learning more? 49 | 50 | If you enjoyed that story, [subscribe to our newsletter](http://arkency.com/newsletter). We share our every day struggles and solutions for building maintainable Rails apps which don't surprise you. 51 | 52 | You might enjoy reading: 53 | 54 | * [inject vs each_with_object](/inject-vs-each-with-object/) 55 | * [The === (case equality) operator in Ruby explained](/the-equals-equals-equals-case-equality-operator-in-ruby/) 56 | * [Relative Testing vs Absolute Testing](/relative-testing-vs-absolute-testing/) - the mentioned test is only using relative time in a setup. It could be nice to have 3 absolute tests checking a normal day, DST started day and DST ended day. 57 | * [Falsehoods programmers believe about time](http://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time) - it starts with _There are always 24 hours in a day_ :) 58 | -------------------------------------------------------------------------------- /posts/2016-06-10-see-how-we-create-books-live.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-06-10 03:30:00 +0200 3 | publish: true 4 | author: Wiktor Mociun 5 | tags: [ 'rails', 'react', 'front end', 'livestream' ] 6 | newsletter: react_books 7 | --- 8 | 9 | # See how we create books. Live 10 | 11 | 12 | 13 | We are in the middle of a process of converting our bestselling book [Rails Meets React](http://blog.arkency.com/rails-react/) from CoffeeScript to ES6. This book turned out to be a huge help for many Rails developers seeking a sane way to create their front end applications. 14 | 15 | Many people are not using CoffeeScript and could use the information from the book. This is our main request for improving this book, we got from you all. And **we are providing this upgrade to all people, who will ever buy _Rails Meets React_** 16 | 17 | ## Working on a book on a live stream 18 | 19 | The process of rewriting the content may be interesting for people wanting to convert their React codebase from CoffeeScript to ES6. So, I thought it may be a good time to experiment with video streaming. 20 | 21 | 22 | 23 | There is one thing I like about gaming live streams. The streamer has a chance 24 | to interact with the audience in a real-time. There is a chat where everyone can 25 | ask a question. This is a great way to connect with an audience. That makes me 26 | really interested in trying this out. 27 | 28 | I started to think about how exactly the whole stream could look like and I got an idea. 29 | 30 | ## Book creation process at Arkency - Rails Meets React 31 | 32 | **I want to show you the part of our book-creation process**. The tooling, the automation, the content creation. We spent a bit of time on making whole technical process around our books quick and easy. We don't want to keep it only to ourselves. 33 | 34 | Here's the plan of things I want to show: 35 | 36 | * How do we structure our books? 37 | * **How does our book building process look like?** 38 | * The process of converting a piece of chapter from CoffeeScript to ES6 (with JSX) 39 | 40 | **The stream will start at 2pm UTC this Friday**. You can [watch it on YouTube](https://www.youtube.com/watch?v=Mqz3b02-CpE). **I will answer your questions on chat**, during the event. 41 | 42 | This is our first attempt to making this kind of live streaming event. And I hope it won't be the last! :) There are much more things we would love to share with you! 43 | 44 | And yes! **The stream will be saved and published later on our [YouTube channel](https://www.youtube.com/c/arkency)**. 45 | 46 | _Psst..._ and there will be a special offer for people watching the stream. ;) 47 | -------------------------------------------------------------------------------- /posts/2021-04-28-decorate-your-runner-session-like-a-pro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Decorate your runner session like a pro 3 | created_at: 2021-04-28T08:17:38.564Z 4 | author: Jakub Kosiński 5 | tags: ["rails"] 6 | publish: true 7 | --- 8 | 9 | [Paweł](https://blog.arkency.com/authors/pawel-pacana) described some [tricks](https://blog.arkency.com/rails-console-trick-i-had-no-idea-about/) you could use to tune-up your Rails console. 10 | I want to tell you about the [`runner`](https://api.rubyonrails.org/classes/Rails/Application.html#method-i-runner) method you can use to enhance your runner sessions. 11 | 12 | Decorating runner sessions is a little bit less convenient as we don't have a module like `Rails::ConsoleMethods` that is included when runner session is started. So adding some methods available for runner scripts is not that easy. 13 | However you can still add some code that will be executed at the start and the end of your runner sessions. 14 | 15 | For example you can send some notifications so you don't need to look at the terminal to check if the script evaluation has been finished. You can also measure time or set some [Rails Event Store](https://railseventstore.org/) metadata 16 | to easily determine [what has been changed](https://blog.arkency.com/correlation-id-and-causation-id-in-evented-systems/) during your session. 17 | 18 | Here is an example we are using in one of our projects to log the elapsed time, set some [RES](https://railseventstore.org/) metadata and send Slack notifications. You can just add it to your `config/application.rb`. 19 | 20 | ```ruby 21 | runner do 22 | session_id = SecureRandom.uuid 23 | script = ARGV.join(" ") 24 | Rails.configuration.event_store.set_metadata( 25 | causation_id: session_id, 26 | correlation_id: session_id, 27 | script: script, 28 | locale: I18n.locale.to_s, 29 | ) 30 | t = Process.clock_gettime(Process::CLOCK_MONOTONIC) 31 | notify_slack( 32 | username: "Script runner", 33 | text: "[#{Rails.env}] Runner script session #{session_id} has started: '#{script}'", 34 | channel: "notifications", 35 | icon_emoji: ":robot_face:" 36 | ) 37 | 38 | at_exit do 39 | notify_slack( 40 | username: "Script runner", 41 | text: "[#{Rails.env}] Runner script session #{session_id} has finished: '#{script}' (elapsed: #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - t} seconds)", 42 | channel: "notifications", 43 | icon_emoji: ":robot_face:" 44 | ) 45 | end 46 | end 47 | ``` 48 | 49 | With such snippet, each time you run some script (or evaluate some inline Ruby code) with `rails runner` it will set the metadata for your [RES](https://railseventstore.org/) instance and it will send a Slack notification 50 | at the beginning and the end of the runner session. 51 | -------------------------------------------------------------------------------- /posts/2025-04-03-rails-when-nothing-changed-is-the-best-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2025-04-03 21:42:38 +0200 3 | author: Szymon Fiedler 4 | tags: [rails, legacy] 5 | publish: true 6 | --- 7 | 8 | # Rails: when "nothing changed" is the best feature 9 | 10 | Recently, I had a chat with a friend of mine, who used to do Rails back in the days. For the last ~10 years he’s focused on mobile development. I was curious what are his observations and asked if he’s happy with his decision or maybe he actually misses web development. 11 | 12 | 13 | 14 | He replied: 15 | 16 | > I miss doing backend, but I’m disgusted with web development. I’m too old to chase every new framework to do the same thing in every new ES flavor of JS... Jumping around npm, yarn, pnpm, bun or whatever is cool this quarter... 17 | 18 | But then he followed: 19 | 20 | > Recently I had to implement a tiny backend app. I dusted off Rails and everything was the same. Same commands, same gems, even nokogiri crashed the same way during bundle install, just like 10 years ago... 21 | 22 | For me it’s an impressive story about boring software. Boring software that’s highly regarded. I absolutely love Rails longevity. 23 | 24 | ## Low learning curve for returnees 25 | Someone who worked with Rails in 2010 can pick up a Rails application in 2025 and still recognize core patterns, conventions and commands. It all remained fundamentally similar. 26 | 27 | ## Knowledge retention 28 | The skills developers build working with Rails tend to stay relevant for years, which is rare in the fast–moving web development world. 29 | 30 | ## Team flexibility 31 | New team members who have Rails experience can become productive quickly without extensive onboarding. 32 | 33 | ## Documentation stability 34 | Solutions and patterns documented years ago often still apply, creating a rich knowledge base that remains useful. 35 | 36 | ## Reduced "framework fatigue" 37 | While Rails has evolved, it hasn’t required developers to 38 | completely relearn their workflow every quarter or two like JS ecosystem does. 39 | 40 | This stability creates enormous practical value. It means companies can maintain Rails applications for the long term without constantly rewriting them to keep up with framework changes. It also means the pool of developers who can work on a Rails project is broader, including those who may have been away from Rails for years but can quickly get back up to speed. 41 | 42 | From a domain modeling perspective, this stability means you can focus on evolving your business logic and domain models without constantly rebuilding the technical foundation underneath them. 43 | 44 | I smile to myself every time I see a JS app team implementing from scratch a framework feature that is simply available in Rails. Apparently, not everyone gets a chance to work with a mature web framework. 45 | -------------------------------------------------------------------------------- /posts/2012-12-03-why-we-dont-use-orm.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2012-12-03 11:25:59 +0100 3 | publish: true 4 | author: Jan Filipowski 5 | newsletter: arkency_form 6 | tags: [ 'ruby', 'storage', 'ddd', 'orm' ] 7 | --- 8 | 9 | # Why we don't use ORM 10 | 11 | You've probably already read that [we don't use Rails](http://blog.arkency.com/2012/11/not-rails/) or any other framework to build [chillout.io](http://chillout.io). Having that said I must add we neither use ORM. 12 | 13 | 14 | 15 | ## Storage-less model 16 | 17 | First of all - your business models shouldn't know anything about storage. Why? Single Responsibility Principle could be one of valid answers. But I'd rather argue it's because storage responsibility is to extend app state, in which models live - **your models don't know anything about memory management, so why should they be interested in persistent memory?** 18 | 19 | ## Database-less perspective 20 | 21 | There's something more: [forget about your storage default - database](http://blog.8thlight.com/uncle-bob/2012/05/15/NODB.html). Imagine, that data from your domain model could be persisted in many ways - to simple files, to key-value stores, to relational databases and so on. In application with high-quality architecture you can defer decision which one to choose - some models will need to be restored in very short time, and for some of them it won't matter. Maybe one model will look like relational record, and another like document? 22 | 23 | ## That's why we don't use ORM 24 | 25 | To be accurate - that's why we don't use ORM on domain-level: we don't want to mix storage with our business, we don't want to depend on non-domain interface of our models. But to be honest - we also don't use ORMs on app-level, because we didn't find any tool that only map object by interface description to storage and vice versa. [DataMapper 2 looks promising](https://github.com/datamapper/dm-core/wiki/Roadmap), but it's not there yet. 26 | 27 | ## Classic approach - repository 28 | 29 | So how do we handle storage? We use [repository objects](http://martinfowler.com/eaaCatalog/repository.html) that encapsulate information how to map models into storage entities and acts like domain collection. 30 | 31 | Each domain model should have own repository (or none, if we don't have to store it) - that way the only reason to modify repository implementation is change in model interface. Each of repositories can have different interface, based on your domain needs. Each of them can use different storage, but all storage adapters should have same API to make them easy to change to other. 32 | 33 | ## Conclusion 34 | 35 | Storage is not a part of your domain, even if most of your domain objects have to be persisted. It's just one of the details that you should change easily. 36 | 37 | Do you think I'm wrong and that's too complicated to handle data? Leave a comment and tell your story. 38 | -------------------------------------------------------------------------------- /posts/2013-11-21-chronos-and-kairos.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2013-11-21 17:18:19 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | newsletter: async_remote_main 6 | tags: [ 'async remote' ] 7 | stories: ['async-remote'] 8 | --- 9 | 10 | # Chronos vs Kairos: Find out how you think about time when working on a project 11 | 12 | " width="100%"> 13 | 14 | When working on any project (personal or professional) we are always confrontend 15 | with tremendous amount of tasks that bring us closer to the goal or let us 16 | finish the project (many, many times there is no such a thing as the end of a project, 17 | just years of long, ongoing, constant improvement of the process and code). 18 | There are two ways you can look at it... 19 | 20 | 21 | 22 | * **I have so much to do and so little time and the time is just passing by...** 23 | 24 | This is the classic way of perceiving time. Time in general. Chronological 25 | time as indicated by the dates on a calendar or the ticking of a clock. 26 | The Greeks called it _Chronos_ (or _Kronos_). 27 | 28 | * **I have 2 hours of my time, what is the best way I can use it?** 29 | 30 | For a special and unique time in your life, especially regarding important 31 | events such as wedding there was a separate word: _Kairos_. 32 | What is special depends on who is articulating the word. And that was 33 | the word used when you were encouraged to make the best use of the time, 34 | of your time. While _Chronos_ is quantitative, _Kairos_ has a qualitative 35 | nature. 36 | 37 | From now on, I encourage you to look at your project in the second way always. 38 | The amount of tasks is probably never going to drop down. Everything that we do 39 | needs improvment. There are always thousands of ideas on how to make the 40 | software better, which technical debt to pay, what new features should be added, 41 | what parts of the design improved, etc. The backlog is probably full no matter 42 | how hard you try. And that is ok. When you work, time will just pass by (_Chronos_), 43 | but what happens to you and your project when working, can be _Kairos_. Therefore: 44 | 45 | * [small](/2013/09/story-of-size-1/) 46 | * [prioritized, unassigned](/2013/10/refactor-to-remote-leave-tasks-unassigned/) 47 | * [tasks to take and finish](/2013/10/take-the-first-task/) 48 | 49 | That is our recipe to make those moments of time uniqe and worthwhile to you as a 50 | developer. To be sure that we actually did someting in that time, instead of just 51 | letting it pass. 52 | 53 | Read more about improving your current project in our book: 54 | **[Async Remote](http://blog.arkency.com/async-remote/) ebook** 55 | 56 | ## Links 57 | 58 | * http://www.examiner.com/article/time-kairos-or-chronos 59 | * http://wordpress.crossroadefc.org/pastors-ponderings/time-kronos-and-kairos/ 60 | -------------------------------------------------------------------------------- /posts/2014-06-16-async-standups.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2014-06-16 19:04:36 +0200 3 | publish: true 4 | author: Andrzej Krzywda 5 | newsletter: async_remote_main 6 | tags: [ 'async remote', 'communication', 'standups' ] 7 | stories: ['async-remote'] 8 | --- 9 | 10 | # Take most out of async textual standups 11 | 12 |

13 |

14 | " width="100%"> 15 |
16 |

17 | 18 | When you work remotely, you want to have some kind of a standup meeting regularly. In our team, after experimenting with many different approaches, we settled with **text-based, asynchronous standups every day**. Additionally, every project has a weekly 'sync' meeting. 19 | 20 | 21 | 22 | Whatever tool we currently use for remote communication (irc in the past, now Slack), we create a channel that is dedicated to #standup. **We don't have a specific time to post there, usually we do it, when we begin our work session** - so, in the spirit of async - different people at different times. 23 | 24 | I consider #standup to be a very good opportunity **to communicate cross-project, to educate, to learn, to help**. Short standup messages are not bad, but they miss this opportunity. 25 | 26 | When writing the standup message, **think more about the others, than about yourself** - what can they get from it by reading your status? 27 | 28 | ## Example 29 | 30 | _Yesterday I finished the "fix the price calculator" feature, which was mostly about removing the Coffee code and rely on the value retrieved from the backend, via ajax. The nice thing was that the backend code was already covered with tests, while the Coffee one wasn't. After that I helped Jack with the "allow logging in with email" feature (we need it now because we have a batch import of users from system X soon). After that I did a small ticket, where I block buying licences for special periods of time. This was nicely TDD'ed, thanks to the concept of aggregate, introduced by Robert recently - all the tests pass < 1s. Here is a commit worth looking at. Today I'm going to start with foreman'ing the recent commits and after that I want to work the XYZ system to allow a better editing of entries. I'm not sure how to start it, so all help is welcome._ 31 | 32 | ### What's good? 33 | 34 | * many details, 35 | * letting other people jump in and help me, 36 | * some opinions about the code that I saw, 37 | * some details about practices I applied (TDD, foreman'ing) 38 | * reminds about some business information - import of users from the X system, happening soon 39 | * links to the commit (potential education) 40 | 41 | ### Format 42 | 43 | 1. yesterday 44 | 2. today 45 | 3. good things 46 | 4. bad things 47 | 5. challenges 48 | 6. call for help 49 | 7. reminder about good practices (tdd, foreman, help colleagues) 50 | 8. code examples (link) 51 | 9. business-related info 52 | -------------------------------------------------------------------------------- /posts/2015-03-06-you-get-feature-toggle-for-free-in-event-driven-systems.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-03-06 15:01:01 +0100 3 | publish: true 4 | author: Jakub Rozmiarek 5 | newsletter: arkency_form 6 | tags: [ 'domain event', 'event store', 'event-driven' ] 7 | --- 8 | 9 | # You get feature toggle for free in event-driven systems 10 | 11 | Event-driven programming has many advantages. One of my favourite ones is a fact that by design it provides feature toggle functionality. 12 | In one of projects we've been working on we introduced an event store. This allows us to publish and handle domain events. 13 | 14 | 15 | 16 | Below you can see an example of `OrderEvents::OrderCompleted` event that is published after an order has been completed: 17 | 18 | ```ruby 19 | class Orders::CompleteOrder 20 | def initialize(event_store) 21 | self.event_store = event_store 22 | end 23 | 24 | def call(order) 25 | # Do something 26 | 27 | event_store.publish(OrderEvents::OrderCompleted.new({ 28 | event_id: order.event_id, 29 | organization_id: order.organization_id, 30 | buyer_id: order.user_id, 31 | order_id: order.id, 32 | locale: order.locale, 33 | })) 34 | end 35 | 36 | private 37 | 38 | attr_accessor :event_store 39 | end 40 | ``` 41 | 42 | After this fact take place, we want to deliver an email to the customer. We utilize an event handler to do it. To make the handler work we need to subscribe it to the event. We subscribe handlers to events in a config file like this: 43 | 44 | ```yaml 45 | OrderEvents::OrderCompleted: 46 | stream: "Order$%{order_id}" 47 | handlers: 48 | - Order::DeliverEmail 49 | ``` 50 | 51 | When the event is published it is stored in a stream and for each of subscribed handlers "perform" class method is called with the event passed as an argument: 52 | 53 | ```ruby 54 | class Order::DeliverEmail 55 | def self.perform(event) 56 | new.call(event) 57 | end 58 | 59 | def call(event) 60 | data = event.data.with_indifferent_access 61 | order_id = data.fetch(:order_id) 62 | locale = data.fetch(:locale) 63 | delivery_attempts = data.fetch(:delivery_attempts, 0) 64 | enqueue_delivery(order_id, locale, delivery_attempts) 65 | end 66 | end 67 | ``` 68 | 69 | Happy customer has just received a confirmation email about their order. 70 | 71 | Now if we want to turn email delivery off for some reason, we can do it easily by unsubscring the handler - in this case by removal of the handler line from the config file. 72 | As you can see it doesn't require any additional work to implement feature toggle - it's available out of the box when using event store. It can be very handy, for example when business requirements change or when we develop a new feature - we can safely push the code and don't worry if it isn't fully functional yet. As long as the handler is not subscribed to the event it won't be fired. 73 | -------------------------------------------------------------------------------- /posts/2017-10-07-how-to-add-a-default-value-to-an-existing-column-in-a-rails-migration.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-08-03 13:16:47 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'rails', 'active record', 'migration' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # How to add a default value to an existing column in a Rails migration 10 | 11 | You probably know that you can easily set a **default** when adding a new column in an Active Record migration. 12 | 13 | ```ruby 14 | add_column(:events, :state, :string, default: 'draft', null: false) 15 | ``` 16 | 17 | ```sql 18 | ALTER TABLE "events" ADD "state" varchar(255) DEFAULT 'draft' NOT NULL 19 | ``` 20 | 21 | 22 | 23 | But what about adding a default to an existing column? Fortunately in Rails 4 and 5 there is an easy way to achieve that. Use `change_column_default` method. 24 | 25 | ## Set default value for an existing column 26 | 27 | The Api is: 28 | 29 | ```ruby 30 | change_column_default( 31 | table_name, 32 | column_name, 33 | default 34 | ) 35 | ``` 36 | 37 | or 38 | 39 | ```ruby 40 | change_column_default( 41 | table_name, 42 | column_name, 43 | from:, 44 | to: 45 | ) 46 | ``` 47 | 48 | Check out an example: 49 | 50 | ```ruby 51 | change_column_default( 52 | :events, 53 | :state, 54 | 'draft' 55 | ) 56 | ``` 57 | 58 | Providing `nil` results in dropping the default. 59 | 60 | ```ruby 61 | change_column_default( 62 | :events, 63 | :state, 64 | nil, 65 | ) 66 | ``` 67 | 68 | However, it's better to use `:from` and `:to` named arguments. It will make the migration reversible which is sometimes useful. 69 | 70 | ```ruby 71 | change_column_default( 72 | :events, 73 | :state, 74 | from: nil, 75 | to: "draft" 76 | ) 77 | ``` 78 | 79 | BTW. There is a similar method named `change_column_null` which (as you probably guessed right now) allows you to easily set or remove the `NOT NULL` constraint. 80 | 81 | ```ruby 82 | change_column_null( 83 | :events, 84 | :state, 85 | false 86 | ) 87 | ``` 88 | 89 | means that `state` cannot be `NULL` (null -> false). 90 | 91 | ```ruby 92 | change_column_null( 93 | :events, 94 | :state, 95 | true 96 | ) 97 | ``` 98 | 99 | means that `state` can be `NULL` (null -> true). 100 | 101 | If you want, you can also set a new value for records who currently have `NULL`. 102 | 103 | ```ruby 104 | change_column_null( 105 | :events, 106 | :state, 107 | false, 108 | "draft" 109 | ) 110 | ``` 111 | 112 | ## Would you like to continue learning more? 113 | 114 | If you enjoyed the article, [subscribe to our newsletter](http://arkency.com/newsletter) so that you are always the first one to get the knowledge that you might find useful in your 115 | everyday Rails programmer job. 116 | 117 | Content is mostly focused on (but not limited to) Ruby, Rails, Web-development and refactoring big, complex Rails applications. 118 | -------------------------------------------------------------------------------- /posts/2015-12-08-how-to-start-a-new-rails-app-and-not-end-in-legacy.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-12-08 12:25:37 +0100 3 | publish: false 4 | author: Andrzej Krzywda 5 | --- 6 | 7 | # How to start a new Rails app and not end in legacy 8 | 9 | You've been there before. Starting a new Rails app, everything goes fast at the beginning. This time it's going to be a super clean code base. Some weeks/months later, the progress slows down. The client's trust is much worse. Hard to test and more bugs. 10 | 11 | You did it again, didn't you? 12 | 13 | Is it Rails fault? 14 | 15 | 16 | 17 | One of my techniques to get the most out of the Rails benefits, but not end in the usual mess: 18 | 19 | 1. Start with the most typical Rails-way approach 20 | 2. After some time (2-3 weeks?) start escaping from Rails into Clean Code / DDD 21 | 22 | Rails is fantastic at the beginning. The productivity, the joy, the happiness, it's all here. Rails is optimized for the first hours/days/weeks of development. At that time, you often add new tables, new columns, new forms, etc. Everyone loves the speed at the beginning, me as the developer, the clients/users who see the app growing up very quickly. 23 | 24 | 25 | However, after some time, things start to slow down, if you keep following the Rails Way approach. First bugs appear, hard to add new features, hard to test. It's hard to implement complex business logic - workflows, state machines. The client is no longer loving you as much as in the beginning. 26 | 27 | **Where's the secret**? 28 | 29 | Start reducing the Rails Way before it happens. 30 | I know, we don't travel in time, so we need to base it on our intuition and experience. 31 | 32 | What exactly can we do to reduce The Rails Way? 33 | 34 | Start to write more tests and see the pain points: 35 | 36 | * It's hard to test controllers, so simplify them by extracting **service objects** 37 | * The tests are fragile because JavaScript snippets rely on backend-generated html - move more logic to the **frontend** and generate more html with JavaScript (maybe React.js?) 38 | * The ActiveRecord patterns feels invasive to the application? - consider extracting **repository** objects 39 | * It's hard to test the application with 3rd party API? consider extracting **adapter** objects. 40 | 41 | Some links which may be helpful, if this technique sounds interesting: 42 | 43 | service objects 44 | 45 | [http://blog.arkency.com/2013/09/services-what-they-are-and-why-we-need-them/](http://blog.arkency.com/2013/09/services-what-they-are-and-why-we-need-them/) 46 | 47 | [http://blog.arkency.com/2015/05/extract-a-service-object-using-simpledelegator/](http://blog.arkency.com/2015/05/extract-a-service-object-using-simpledelegator/) 48 | 49 | [adapter objects with Rails](http://blog.arkency.com/2014/08/ruby-rails-adapters/) 50 | 51 | [Rails repository objects](http://blog.arkency.com/2015/06/thanks-to-repositories/) 52 | 53 | [React.js](http://blog.arkency.com/2015/11/arkency-react-dot-js-resources/) 54 | 55 | -------------------------------------------------------------------------------- /posts/2021-03-10-how-to-make-idempotent-create-requests-against-a-3rd-party-http-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to call 3rd party APIs idempotently 3 | created_at: 2021-03-10T07:56:46.014Z 4 | author: Tomasz Wróbel 5 | tags: [] 6 | publish: false 7 | --- 8 | 9 | Why? 10 | 11 | * we make more and more 3rd party api calls in our apps these days, mostly in background jobs 12 | * background jobs cannot be assumed to be run only once - there's no way around that (why? job can be retried because of an exception or even run twice in parallel in some weird scenarios) 13 | * that's why jobs need to be designed to be idempotent, i.e. safe to run any number of times, while still producing the desired end state on the 3rd party system - if a job that sends emails is retried 10 times, it should still send only 1 email, not 10 14 | 15 | Now how to make an 3rd party api call idempotent? It depends on what this particular api provides. 16 | 17 | Some requests are easily made idempotent, like updating the status to `COMPLETED` (provided it should never go back). 18 | 19 | Some request are harder to make idempotent, in particular adding an item to a collection. How not to end up with a bunch of unnecessary objects created when your job is being retried for some reason? 20 | 21 | ## Case 1 — 3rd party api support generic idempotency keys 22 | 23 | Just like Stripe API here: https://stripe.com/docs/api/idempotent_requests 24 | 25 | This is the best for you. Just add the idempotency key and you're good. 26 | 27 | ## Case 2 — client side generated id 28 | 29 | If the 3rd party api allows you to set a client side generated id which is guaranteed to be unique, it works pretty much as an idempotency key. 30 | 31 | ## Case 3 — objects in _draft_ state 32 | 33 | Some APIs may allow you to create an object in some kind of _draft_ state. If you split your creation request into two: (1) create a draft, (2) transition the draft to the target state (which is idempotent "by nature") — you're good too. In worst case you can end up with some unnecessary drafts, which most often should be a problem. 34 | 35 | 36 | 37 | ## Case 4 — existence check 38 | 39 | If your api lets you check for existence. 40 | 41 | But here you need to deal with potential race conditions around concurrency, if your job runs twice in parallel, which you can never rule out. 42 | 43 | 44 | 45 | ## Random points, to be edited 46 | 47 | 48 | 49 | * make a DB lock to ensure that only 1 request for a specific ID can happen in a single moment 50 | * being able to add an identifier to resource metadata might be helpful 51 | * sidekiq limiter 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /posts/2016-06-17-cover-all-test-cases-with-permutation.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-06-25 21:06:21 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'ruby', 'permutation', 'rspec', 'testing'] 6 | newsletter: arkency_form 7 | img: "ruby-permutation/permutation-cube.jpg" 8 | --- 9 | 10 | # Cover all test cases with #permutation 11 | 12 | alt="" width="100%" /> 13 | 14 | When dealing with system which cooperate with many other 15 | subsystems in an asynchronous way, you are presented with 16 | a challenge. Due to the nature of such systems, messages 17 | may not arrive always in the same order. How do you test 18 | that your code will react in the same way in all cases? 19 | 20 | Let me present what I used to be doing and how I changed my 21 | approach. The example will be based on [a saga](/course/saga/) but it 22 | applies to any solution that you want to test for 23 | order independence. 24 | 25 | 26 | 27 | ```ruby 28 | specify "postal sent via API" do 29 | procs = [ 30 | ->{ postal.call(fill_out_customer_data) }, 31 | ->{ postal.call(paid_data) }, 32 | ->{ postal.call(tickets_generated_data) }, 33 | ].shuffle 34 | 35 | procs[0].call 36 | procs[1].call 37 | 38 | expect(api_adapter).to receive(:transmit) 39 | procs[2].call 40 | end 41 | ``` 42 | 43 | This solution however has major drawbacks 44 | 45 | * It does not test all possibilities 46 | * Failures are not easily reproducible 47 | 48 | It will eventually test all possibilites. Given enough runs on CI. 49 | And you can reproduce it if you pass the [`--seed`](https://www.relishapp.com/rspec/rspec-core/docs/command-line/order) 50 | attribute. 51 | 52 | But generally it does not make our job easier. And it might miss some bugs 53 | until it is executed enough times. 54 | 55 | It was rightfully questioned by Paweł, my coworker. We can do better. 56 | 57 | ## #permutation 58 | 59 | We should strive to test all possible cases. It's boring to go manually through all 6 of them. 60 | With even more possible inputs the number goes high very quickly. And it might be error prone. 61 | So let's generate all of them with the little help of `#permutation` method. 62 | 63 | ```ruby 64 | 65 | [ 66 | fill_out_customer_data, 67 | paid_data, 68 | tickets_generated_data, 69 | ].permutation.each do |fact1, fact2, fact3| 70 | specify "postal sent via API when #{[fact1.class, fact2.class, fact3.class].to_sentence}" do 71 | postal.call(fact1) 72 | postal.call(fact2) 73 | 74 | expect(api_adapter).to receive(:transmit) 75 | postal.call(fact3) 76 | end 77 | end 78 | ``` 79 | 80 | ## Caveats 81 | 82 | * The more cases you generate the faster they should run individually 83 | * There is obviously a certain limit after which doing this does not make sense anymore. 84 | Maybe in such case fuzzy testing or moving it outside the main build is a better solution. 85 | 86 | If you enjoyed this blog post you will like our [books](/products) as well. 87 | -------------------------------------------------------------------------------- /posts/2012-11-29-not-rails.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2012-11-29 14:48:02 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | newsletter: react_books 6 | tags: [ 'ruby', 'sequel', 'active record', 'threads' ] 7 | --- 8 | 9 | # Not Rails 10 | 11 | So today I started my day with reading recent posts on rails-core group and one post 12 | looked interesting to me: [Add support for multiple, concurrent, connection pools](https://groups.google.com/d/topic/rubyonrails-core/EF1ycXz-3BA/discussion). 13 | ActiveRecord and threads and multiple databases. What could possibly go wrong? 14 | 15 | 16 | 17 | ## Ruby developers use Active Record 18 | 19 | So I commented on our Ruby User Group IRC channel that it is exactly the reason why we choose not to 20 | use AR in our newest product [chillout.io](http://chillout.io). If you are interested in this topic 21 | I strongly recommend reading [ActiveRecord Concurrency Currently: Good News and Bad](http://bibwild.wordpress.com/2012/03/15/activerecord-concurrency-currently-good-news-and-bad/). 22 | The next obvious question was, so what do we use? 23 | 24 | ## Except when they don't 25 | 26 | We use [Sequel](http://sequel.rubyforge.org/). I wanted to use Sequel since the moment 27 | I read [How I Test Sequel](http://sequel.heroku.com/2010/05/19/how-i-test-sequel/) article 28 | written by Jeremy Evans, the creator of Sequel. But I never had the reason (or the guts) 29 | to leave my comfort zone of using ActiveRecord. 30 | 31 | ## Ruby developers use Rails 32 | 33 | The next question was: How does it work with Rails ? What problems did you encounter? Well, I don't know. 34 | 35 | ## Except when they don't 36 | 37 | It struck me into my head that it was automatically assumed that our app is Rails app when 38 | it has no reason to be it. It is a built from multiple components (currently 3 but this number 39 | will grow in future) which communicate together using Sequel, ZMQ and Queues. Because 40 | monitoring your app might be about many things but none of them makes much sense when 41 | being put into Rails. We wanted to avoid building monolithic architecture at all cost. 42 | Even if that means getting our hands dirty and dealing with lot of new things at the same time. 43 | But it was a necessary step to guarantee isolation and scaling of the components, which all has 44 | completely different needs. 45 | 46 | ## We ❤ Ruby, Rails and ActiveRecord 47 | 48 | We love all those tools, built many products using them and made our customers happy. 49 | 50 | But that does not mean we should use them in every project. We chose a different toolset 51 | because we believe it is now finally time to start caring more about threading and concurrency in a whole 52 | Ruby community and the best way to do it, is to give it a try in real-life projects. And 53 | use tools that were built with [threading in mind](http://sequel.rubyforge.org/rdoc/files/doc/thread_safety_rdoc.html). 54 | 55 | ## Try it out 56 | 57 | So, wanna give a try to Ruby but Not-Rails-Not-Even-ActiveRecord application? Sign up! 58 | 59 | -------------------------------------------------------------------------------- /posts/2018-02-14-building-custom-search-is-hard-and-boring.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2018-02-14 06:00:00 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'search', 'algolia', 'rails', 'react' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # Building custom search is hard and boring 10 | 11 | Have you ever had to implement a search page for your client? I did and I've gotta say it's often one of the most boring tasks. Not always, but often. There is a big list of features that the client usually expects other similar pages have. Especially when it comes to e-commerce websites. It needs to be fast, it needs to allow filtering, grouping and limiting by various attributes. On the backend side that usually means building sophisticated SQL queries, which is never a fun task when the search is highly dynamic and based on a variety of options available in the UI. Alternatively, you can use ElasticSearch or Lucene/Solr, but I've realized that while they are often super fast there is a lot of quirks that you need to learn when you would like them to just work. 12 | 13 | 14 | 15 | So, on one hand, there is all this complexity and challenge which you might like as a developer. On the other hand, there is rarely anything novel about the task of building a search page and you might feel demotivated about doing it. Not to mention the deadlines. How can you build something sophisticated and have so many features (that are usually wanted and needed) in a week or two? 16 | 17 | Let's try to list a few usual requirements: 18 | 19 | * full-text search (often with the ability to handle some typos) that recognizes priority of the attributes in which a text is found 20 | * various sort orders (date, price, popularity etc) 21 | * filtering by brands or categories 22 | * highlighting or displaying snippets of found text matches 23 | * pagination or infinite scroll 24 | * limiting by dates or price ranges or ratings 25 | * responsiveness (refreshing results in milliseconds after changing options or providing new text query) 26 | 27 | Now, separately these features are usually not that hard, but the challenge comes from the fact that our customers usually want all of that combined and working together. For every such feature/requirement, you need to implement it on the backend, implement a frontend component and connect them together. That's a lot of work. And this is just a start. After these basic requirements, there come new ones, those specific to the domain and application that you work on which require more custom logic and display. That's what interesting. That's what you would like to focus on. Here often lies the competitive advantage. 28 | 29 | For quite some time I also struggled with this conundrum on how to build great search pages quickly and painlessly for our customers. 30 | 31 | [to be continued...](/the-anatomy-of-search-pages/) 32 | 33 | _Are you also feeling the pain of building search pages from scratch every time? Or maybe you just want to learn how to deal with it upfront? We have a [video course](https://blog.arkency.com/search-rails/) that can help :)_ 34 | -------------------------------------------------------------------------------- /bin/new_post: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | require 'bundler/inline' 5 | 6 | gemfile do 7 | source 'https://rubygems.org' 8 | gem 'stringex' 9 | gem 'git' 10 | gem 'os' 11 | end 12 | 13 | $git = Git.open(File.join(__dir__, "..")) 14 | 15 | class Post 16 | def initialize(author, date, title) 17 | @author = author 18 | @date = date 19 | @title = title 20 | end 21 | 22 | def create 23 | puts "on disk | #{full_path}" 24 | puts "on blog | #{url}" 25 | puts 26 | unless File.exist?(full_path) 27 | File.write(full_path, template) 28 | puts "post created" 29 | else 30 | puts "post already exists" 31 | exit 1 32 | end 33 | end 34 | 35 | def browse 36 | Kernel.system("open", url) 37 | end 38 | 39 | def edit(editor) 40 | if cli_editor?(editor) 41 | Kernel.exec(editor, full_path) 42 | else 43 | Kernel.system("open", full_path, "-a", editor) 44 | end 45 | end 46 | 47 | def commit(git) 48 | git.pull 49 | git.add(full_path) 50 | git.commit_all(%Q|Initial draft of "#{@title}"|) 51 | git.push 52 | end 53 | 54 | private 55 | 56 | def cli_editor?(editor) 57 | [ 58 | /\/?vim$/, 59 | ].any? { |regex| regex.match(editor) } 60 | end 61 | 62 | def full_path 63 | File.expand_path(File.join(__dir__, "../", path)) 64 | end 65 | 66 | def path 67 | "posts/#{formatted_date}-#{title_for_url}.md" 68 | end 69 | 70 | def url 71 | "https://blog.arkency.com/#{title_for_url}" 72 | end 73 | 74 | def formatted_date 75 | @date.strftime('%Y-%m-%d') 76 | end 77 | 78 | def title_for_url 79 | @title.to_url 80 | end 81 | 82 | def template 83 | <<~EOT 84 | --- 85 | created_at: #{@date} 86 | author: #{@author} 87 | tags: [] 88 | publish: false 89 | --- 90 | 91 | # #{@title} 92 | 93 | FIXME: Place post lead here. 94 | 95 | 96 | 97 | FIXME: Place post body here. 98 | 99 | ```ruby 100 | Person.new.show_secret 101 | # => 1234vW74X& 102 | ``` 103 | EOT 104 | end 105 | end 106 | 107 | parser = 108 | OptionParser.new do |opts| 109 | opts.on('-t TITLE', String, "Title of the post you're about to write") 110 | opts.on('-a AUTHOR', String, "How people call you") 111 | opts.on('-b', "Open browser") 112 | opts.on('-c', "Commit and push initial draft") 113 | opts.on('-e EDITOR', "Open post with given editor") 114 | end 115 | begin 116 | parser.parse!(into: params = {}) 117 | raise OptionParser::MissingArgument unless params[:t] 118 | rescue OptionParser::MissingArgument, OptionParser::InvalidOption 119 | puts parser.help 120 | exit 121 | end 122 | 123 | author = params[:a] || $git.config["user.name"] 124 | post = Post.new(author, Time.now, params[:t]) 125 | post.create 126 | post.browse if params[:b] 127 | post.commit($git) if params[:c] 128 | post.edit(params[:e]) if params[:e] 129 | 130 | 131 | -------------------------------------------------------------------------------- /posts/2016-11-30-recovering-unbootable-nixos-instance-using-hetzner-rescue-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-11-30 19:37:19 +0100 3 | publish: true 4 | author: Rafał Łasocha 5 | tags: [ 'nixos', 'infra', 'hetzner', 'devops' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # Recovering unbootable NixOS instance using Hetzner rescue mode 10 | 11 | Some time ago we had a small fuckup with one of our CI build machines. One of the devs was changing sizes of the file system partitions and he forgot to commit new NixOS configuration to the git repository where we synchronize it. After some time, I've uploaded NixOS config from git repo (which had, like I said, outdated configuration) to the machine and run `nixos-rebuild --switch` which essentialy made system into unbootable state because of wrong UUIDs in `/etc/fstab`. 12 | 13 | 14 | 15 | It was only a one of our build machines (nothing extremely critical to fix) and thankfully we had good scripts for provisioning new build machine, so if I wanted to, I could easily just run a bunch of scripts and create new build machine from scratch. **I was curious however, whether NixOS could deliver what it is promising and give me a way to easily rollback to previous, correct configuration of our system.** 16 | 17 | Firstly we've enabled Hetzner's rescue mode for that machine and logged in through SSH. I've mounted root and boot partitions of our build machines. Then my plan was to chroot into system and run NixOS rollback configuration command to restore the previous configuration. There are a few links on the Internet explaining that it is possible to chroot into NixOS root partiton but with neither of them I was able to run `nixos-rebuild` command - mostly it was errors about dbus or other services not running in chroot environment. 18 | 19 | In the end it turned out that **I've forgotten about one of the NixOS core sales-pitch features: each system configuration is a separate entry in the GRUB config**. I quickly forgot that, because for me it looked like a feature which is useless in server environment - in the end, you don't have access to GRUB menu when booting a server machine, right? Not quite. There's at least one useful command you can use, `grub-reboot`, which basic functionality is _"During next boot, instead of entry X, use entry Y as a default"_. **Thus, the only thing I need was to execute one command and reboot the machine:** 20 | 21 | ``` 22 | grub-reboot --boot-directory=/mnt/boot "NixOS - Configuration 4 (2016-09-10 - 16.03.git.2ed3eee)" 23 | ``` 24 | 25 | After reboot I had my old, working configuration (`configuration 4`) so I was able to upload the correct `/etc/nixos/configuration.nix` file and rerun `nixos-rebuild --switch` to create new, working configuration (`configuration 6`) as a default one, instead of invalid one (`configuration 5`). 26 | 27 | It was my first opportunity to fix broken NixOS system. What are your experiences with such situations? Let me know if you know better ways of handling such cases. 28 | 29 | _During working on this task I was looking for a blogpost like that and I've found none. So now, there's at least one :)_ 30 | -------------------------------------------------------------------------------- /posts/2025-02-06-improve-your-ux-with-turbo-frames.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2025-02-06 18:52:04 +0100 3 | author: Maciej Korsan 4 | tags: [hotwire, turbo, rails, frontend] 5 | publish: true 6 | --- 7 | 8 | # Improve your user experience with Turbo Frames 9 | 10 | I’ve spent a good chunk of my career optimizing performance in web apps — mostly from the frontend perspective. Recently, I stumbled upon a simple trick with Turbo Frames that can improve the user experience when a particular part of the page is painfully slow to load. 11 | 12 | ## When Slow Pages Hurt UX 13 | 14 | Imagine you have a view that shows a giant, complex list of data. Maybe it involves heavy database queries, advanced filtering, or complicated logic that can take a couple of seconds to finish. Traditionally, the user is stuck watching a blank or previous page until everything completes. 15 | 16 | That’s obviously subpar for UX. The user might think the page is broken or slow, and they might leave before the content even shows up. 17 | 18 | " width="100%"> 19 | 20 | ## Splitting Out the Slow Section 21 | 22 | One way to tackle this is to give the slow list its own route. Your main view then serves everything else right away — like a quick summary or basic info — while the heavy query is executed in a separate request. 23 | 24 | You drop a `` in your main view, pointing its `src` to the new endpoint that returns just the slow data. Turbo automatically fetches that data and replaces the frame’s content once it’s ready. Meanwhile, the user can already see and interact with the rest of the page. 25 | 26 | ```html 27 |

My super page

28 | 29 | 30 | 31 | ``` 32 | 33 | " width="100%"> 34 | 35 | ## Preventing In-Frame Navigation 36 | 37 | If you place links inside that frame, you might run into a second surprise: clicking a link will keep you “trapped” in the frame, rendering all subsequent pages inside it. That’s obviously not always what you want. 38 | 39 | The fix is straightforward: add `data-turbo-frame="_top"` to any link that you want to break out of the frame and load as a full page. 40 | 41 | ```html 42 | <%%= link_to "Go Full Page", some_full_page_path, data: { turbo_frame: "_top" } %> 43 | ``` 44 | 45 | Or you can use the `target="_top"` on the frame itself to make all links open in the top frame. 46 | 47 | That way, your users aren’t stuck inside a sub-view forever. 48 | 49 | ## Final Thoughts 50 | 51 | This approach may not be groundbreaking, but it’s a handy shortcut for dealing with performance bottlenecks on any page. It won't make your slow query faster - you still should think about optimizing it. By isolating the slow portion behind a `` that points to a separate endpoint, you keep the rest of the page quick and responsive. The overall perception of speed increases — and your users will appreciate the difference. -------------------------------------------------------------------------------- /posts/2017-10-06-how-to-overwrite-to-json-as-json-in-active-record-models-in-rails.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-09-26 11:44:52 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'to_json', 'as_json', 'rails', 'active record'] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # How to overwrite to_json (as_json) in Active Record models in Rails 10 | 11 | Let's say you have a model in Rails with certain attributes and columns. When you serialize it with `to_json`, by default Rails will include all columns. How can you add one more or remove some from now appearing there? 12 | 13 | 14 | 15 | ## to_json ? Use as_json instead 16 | 17 | In the simplest way you can define the `as_json` method on your model. You can call `super` to get the standard behavior from `ActiveRecord::Base` and then add or remove more attributes on that result. 18 | 19 | Imagine that you have `Event` class. And initially the JSON looks like. 20 | 21 | ```js 22 | { 23 | "id": 1, 24 | "title": "Ergonomic Granite Table course", 25 | "description": "You can’t just skip ahead to where you think...", 26 | "starts_at": "2017-10-10T00:00:00.000Z", 27 | "ends_at": "2017-10-13T00:00:00.000Z", 28 | "category": "Holiday", 29 | "state": "published", 30 | "image_url": "http://lorempixel.com/600/338/animals/1", 31 | "created_at": "2017-10-03T09:30:25.481Z", 32 | "updated_at": "2017-10-04T08:36:43.049Z", 33 | } 34 | ``` 35 | 36 | You might want to remove `created_at`, `updated_at` and add one new field such as `is_single_day_event`. 37 | 38 | You can do it that way: 39 | 40 | ```ruby 41 | class Event < ApplicationRecord 42 | def single_day_event? 43 | starts_at.to_date == ends_at.to_date 44 | end 45 | 46 | def as_json(*) 47 | super.except("created_at", "updated_at").tap do |hash| 48 | hash["is_single_day_event"] = single_day_event? 49 | end 50 | end 51 | end 52 | ``` 53 | 54 | And now when you call `event.to_json` you are going to get 55 | 56 | ```js 57 | { 58 | "id": 1, 59 | "title": "Ergonomic Granite Table course", 60 | "description": "You can’t just skip ahead to where you think...", 61 | "starts_at": "2017-10-10T00:00:00.000Z", 62 | "ends_at": "2017-10-13T00:00:00.000Z", 63 | "category": "Holiday", 64 | "state": "published", 65 | "image_url": "http://lorempixel.com/600/338/animals/1", 66 | "is_single_day_event": true, 67 | } 68 | ``` 69 | 70 | If the logic around serializing your objects gets more complex over time or you need multiple representations of the same model, I suggest you decouple JSON serialization from ActiveRecord. You can do it by using of the gems such as `ActiveModel::Serializers` or similar. 71 | 72 | Potential gems for you to investigate 73 | 74 | ## Would you like to continue learning more? 75 | 76 | If you enjoyed the article, [subscribe to our newsletter](http://arkency.com/newsletter) so that you are always the first one to get the knowledge that you might find useful in your 77 | everyday Rails programmer job. 78 | 79 | Content is mostly focused on (but not limited to) Ruby, Rails, Web-development and refactoring big, complex Rails applications. 80 | -------------------------------------------------------------------------------- /posts/2025-08-01-watch-out-for-this-one-deprecation-warning-when-upgrading-from-rails-7-dot-1-to-7-dot-2-on-heroku.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2025-08-01 13:07:00 +0200 3 | author: Łukasz Reszke 4 | tags: ["rails", "rails upgrade", "heroku"] 5 | publish: true 6 | --- 7 | 8 | # Watch out for this one deprecation warning when upgrading from Rails 7.1 to 7.2 on Heroku 9 | 10 | We recently upgraded Rails from 7.0 to 7.1 for one of our clients. It went smoothly. 11 | When Rails 7.1 went live, we were pleased to see a new set of deprecation warnings. To avoid being overwhelmed by them, we decided to address the issue right away. 12 | However, we ran into a nasty issue... 13 | 14 | ## The one with nasty issue 15 | 16 | The application didn't crash. 17 | 18 | The server wasn't throwing 500s like a crazy Viking throwing axes. 19 | 20 | Either of those would have been better. The worst that can happen is silence. 21 | 22 | The deprecation warning was: 23 | 24 | ```ruby 25 | [DEPRECATION] DEPRECATION WARNING: `Rails.application.secrets` is deprecated 26 | in favor of `Rails.application.credentials` and will be removed in Rails 7.2. 27 | ``` 28 | We moved all the secrets and encrypted secrets to the credentials file. 29 | 30 | We also moved the `secret_key_base` to credentials because it's the default place for this secret since introduction of the credentials feature in Rails 5.2. 31 | 32 | We removed values from `ENV["SECRET_KEY_BASE"]` to credentials and checked that the value was correct by calling 33 | `Rails.application.credentials.secret_key_base`. 34 | 35 | It turned out that you can also get the secret_key_base by calling `Rails.application.secret_key_base`. 36 | 37 | 38 | Let's take a look at this code: 39 | 40 | ```ruby 41 | def secret_key_base 42 | if Rails.env.development? || Rails.env.test? 43 | secrets.secret_key_base ||= generate_development_secret 44 | else 45 | validate_secret_key_base( 46 | ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base 47 | ) 48 | end 49 | end 50 | ``` 51 | 52 | Ok so to sum it up, until now: 53 | - We removed ENV 54 | - So it should take the value from credentials 55 | 56 | Right? But instead... 57 | 58 | Instead it failed silently. **All the cookies become invalid**. So where’s the poop? 59 | 60 | " width="100%"> 61 | 62 | The poop is in Heroku trying to be smarter than developers. Unfortunately. It turned out that removing `SECRET_KEY_BASE` env leads to... [regenerating it with new **random** value.](https://github.com/heroku/heroku-buildpack-ruby/issues/1143) 63 | 64 | So our external devices depending on it couldn’t work because of new, randomly generated key. 65 | 66 | ## Summary 67 | To sum it up: 68 | - If you’re getting rid of the `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2 69 | - And you’re on Heroku 70 | - And you’re using Heroku Buildpacks 71 | - Make sure you keep `SECRET_KEY_BASE` in both credentials and in Heroku config variable. Or at least in the latter. 72 | - Either way... you may end up in nasty silent error. Which is not good. 73 | -------------------------------------------------------------------------------- /posts/2020-02-24-legacy-rails-ddd-migration-strategy-from-read-models-through-events-to-aggregates.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2020-02-24 15:08:12 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['ddd', 'legacy', 'aggregate', 'service object', 'read model', 'domain event'] 6 | --- 7 | 8 | # Legacy Rails DDD Migration strategy — from read models, through events to aggregates 9 | 10 | How to migrate legacy Rails apps into DDD/CQRS in a relatively safe way? 11 | 12 | 13 | 14 | Recently, I was answering a question on our [Rails Architect Masterclass](https://arkency.com/masterclass/) Slack channel. The question was related to a video which explained the strategy of extracting read models as the first step. The part which wasn't clear enough was on the topic how the read models extraction can help in designing aggregates. Here's my written attempt to explain this strategy: 15 | 16 | ## Introduce a Service objects layer (aka application layer) 17 | 18 | 19 | ```ruby 20 | class RegisterUser 21 | def call 22 | User.create 23 | Mailer.send 24 | end 25 | end 26 | ``` 27 | 28 | ## Start publishing events in service objects 29 | 30 | In the service objects introduce publishing events, so when there’s a `RegisterUser` service object it would have a line event_store.publish(UserRegistered) 31 | 32 | ```ruby 33 | class RegisterUser 34 | def call 35 | Transaction.begin 36 | User.create 37 | event_store.publish(UserRegistered.new) 38 | end 39 | Mailer.send 40 | end 41 | end 42 | ``` 43 | 44 | ## Build read models 45 | 46 | Build read models like `UsersList` as the reaction to those events (and only to those events). Note that this read models can use its own "internal detail" ActiceRecord, which resembles the original one, but it's just for view purpose. 47 | 48 | ```ruby 49 | class UsersList 50 | def register_user 51 | UsersList::User.create 52 | end 53 | 54 | def ban_user 55 | UserList::User.destroy 56 | end 57 | end 58 | ``` 59 | 60 | ## Detect the suffix in the event names 61 | 62 | Once you have all the events required for a UsersList view, you will see the pattern that the suffix (the subject the events start with) will suggest aggregate names. In our example that would be `User` aggregate (probably in the `Access` bounded context) 63 | 64 | ## Recognize the verbs in event names 65 | 66 | Additionaly, the event names (the what was done) - the verbs in passive - `Registered`, `Rejected`, `Banned` may suggest the method names in that aggregate 67 | 68 | ## Design the aggregate 69 | 70 | This brings us to the potential design of the aggregate 71 | 72 | ```ruby 73 | module Access 74 | class User 75 | def register 76 | def approve 77 | def ban 78 | end 79 | end 80 | ``` 81 | 82 | ## Explore other possible designs of business objects 83 | 84 | Once you learn more about the other flavours of implementing aggregates, business objects (objects which ensure business constraints), you will see that verbs can suggest the state changes and the polymorphism-based aggregates: 85 | 86 | ```ruby 87 | class RegisteredUser 88 | class BannedUser 89 | ``` 90 | 91 | See more aggregates flavours examples in our [aggregates](https://github.com/arkency/aggregates) repo. 92 | -------------------------------------------------------------------------------- /posts/2020-04-21-most-controversial-rules-in-arkency.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2020-04-27T12:17:09.240Z 3 | author: Tomasz Wróbel 4 | tags: ['async remote'] 5 | publish: true 6 | --- 7 | 8 | # Most controversial rules in Arkency 9 | 10 | In random order: 11 | 12 | 1. Don't **comment** the code. 13 | 0. Don't use **branches**. Commit to master, push often, don't break stuff. 14 | 0. Let **developers manage** the project. 15 | 0. No **refactoring** sprints. Always refactor. 16 | 0. Don't send **private messages**. Use channels. 17 | 0. Don't **@mention** people unless it's really worth it. 18 | 0. **Estimations** are often a waste of time, energy and trust. 19 | 0. **Meetings** are often a waste of time, energy and momentum. Especially true if they're large, compulsory, regularly scheduled or not focused. 20 | 0. Got an **idea**? Ship it and prove it instead of asking for a blessing. Expect someone's _revert of indignation_ in worst case. _Better to ask for forgiveness than permission_. 21 | 0. Proactively and continually communicate your progress instead of **asking questions** only when stuck. _Tell, don't ask_. 22 | 0. Avoid **communication proxies**. 23 | 0. Share communication with the whole team. 24 | 0. Share **accesses** with the whole team. Everyone should be able to do anything without asking others. 25 | 0. Best **story size** is 1. 26 | 0. There should be **one backlog**. 27 | 0. Don't attach certain people to certain types of tasks. Always pick the **first task from the top** of a prioritized backlog. Always have 1 ticket assigned. 28 | 0. Don't expect anyone to read or respond to your message immediately. 29 | 0. Avoid **blocking** (other people to wait on you - or - yourself to wait on others). 30 | 31 | ## These guidelines happen to also be the: 32 | 33 | * key reasons why people love to work _in_ Arkency 💚 34 | * key reasons why people love to work _with_ Arkency 💚 35 | * key ingredients that facilitate remote & async work 36 | 37 | ## Wait, but... 38 | 39 | Of course most of these rules have their own: 40 | 41 | * **rationale** 42 | * **assumptions** - we're a small, cohesive team of experienced & responsible engineers 43 | * **exceptions** - our internal weekly call is large and regular and unfocused - but it's voluntary and everyone loves it 44 | * **alternatives** - namely what do we do instead, for example: record & summarize meetings to let others skip them and remain on the same page 45 | * **costs** - which we still choose to pay 46 | 47 | ## Should we elaborate? 48 | 49 | Do you find any of these _rules_ particularly exciting, outraging or confusing? **Let us know** via email or twitter (you can reply to [this tweet](https://twitter.com/arkency/status/1254784379190038534)). We appreciate your input. 50 | 51 | We'd like to keep **elaborating** on these points in coming blogposts. If you wanna **stay in the loop** [subscribe to our newsletter](https://arkency.com/newsletter/). We believe these guidelines are very valuable and we'd like our prospective clients and teammates to be aware of the way we like to work. 52 | 53 | We already **covered** most of these topics in [The Book](https://arkency.dpdcart.com/product/71091) or in our [26 other blogposts about async/remote](https://blog.arkency.com/tags/async-remote/). 54 | -------------------------------------------------------------------------------- /posts/2016-05-19-70-percent-off-the-rails-slash-tdd-slash-ddd-slash-mutant-video-class-until-11pm-19-dot-05-dot-2016-cest.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-05-19 09:57:12 +0200 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: [ 'tdd', 'ddd', 'mutation testing', 'video class', 'course' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # 70% off the Rails / TDD / DDD / mutant video class until 11pm 19.05.2016 CEST 10 | 11 | Hi, 12 | 13 | Just 24 hours left for the 70% discount of the Rails/TDD/DDD/mutant video class. You can buy it here: 14 | https://vimeo.com/r/1I82/STcxRjFuel 15 | 16 | Thanks to everyone who already bought the video class! 17 | 18 | The number of sales is so overwhelming that Vimeo wrote this to me today: 19 | 20 | 21 | 22 | <%= img_fit("tdd-ddd-rails-ruby-video/vimeo.png") %> 23 | 24 | Believe me, it's really cool to receive such messages - thank you so much! 25 | 26 | To celebrate the results, I've decided to make some of the videos FREE so that those of you who haven't decided yet can have a look: 27 | 28 | 29 | 30 | 31 | ## Q&A 32 | 33 | ### Is the code available? 34 | 35 | Many people asked me if the code from the videos will be also available. 36 | 37 | Yes! It's definitely going to be available. The minor blocker for now is that I'm not sure how to get emails of the customers (or any other way of contacting) so that I can collect the github logins and give you access to the Github repository. I'm talking to the Vimeo crew about it! 38 | 39 | ### Is it going to be about Rails or just Ruby? 40 | 41 | The first ~20 videos are not touching Rails yet. It's by design ;) The story will reveal itself to the point where we'll switch to Rails in an interesting way ;) The goal of this class is to show you how to do TDD with a Rails app! 42 | 43 | ### Is it again something about JavaScript or it's just Ruby/Rails this time? 44 | 45 | I guess the people asking this question may miss our React.js love ;) 46 | This time, we'll save you the React.js or any other JavaScript things. No React.js this time ;) 47 | (those of you who are disappointed, stay tuned, we'll cooking something React/Rails related for you as well in another form). 48 | 49 | ## Testimonials 50 | 51 | Here's some of the things people wrote to me after watching some of the videos: 52 | 53 | * _"It's a great idea with the class, I bought it, watched the first videos and already learnt a lot, thanks!"_ 54 | * _"I´m really enjoying the course."_ 55 | * _"I am also learning cool tricks for Rubymine. Thank you."_ 56 | * _"Oglada sie bardzo przyjemnie. Fajny flow - nie za szybko i nie za wolno." (which translates to - It was nice to watch it, good flow, not too fast, not too slow)_ 57 | 58 | ## Decided to buy? 59 | 60 | Just click this link: https://vimeo.com/r/1I82/STcxRjFuel 61 | 62 | BTW, I've just checked with Vimeo. It all looks like we can keep our refund policy with this class as well. 63 | Just to remind you - all Arkency products (ebooks/videos) are on the policy of refunding at any time, without asking any question. If you buy this class and then decide it's not for you, just send as an email and we'll issue a refund, no questions asked! 64 | -------------------------------------------------------------------------------- /posts/2023-01-20-offloading-write-side-with-a-read-model.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2023-02-06 12:56:50 +0100 3 | author: Piotr Jurewicz 4 | tags: ['ddd', 'read model', 'cqrs', 'commands'] 5 | publish: true 6 | --- 7 | 8 | # Offloading write side with a read model 9 | 10 | Imagine the following business requirement: 11 | 12 | *All the products should be reserved for a customer on order submission. 13 | Simply adding items to the cart does not guarantee product availability. 14 | However, the customer should not be able to add a product that is already unavailable.* 15 | 16 | 17 | 18 | Actually, it is not any fancy requirement. I used to work on e-commmerce project with such a feature. 19 | When diving deeper into Domain-driven design, I started thinking about how to properly meet this requirement with DDD building blocks. 20 | 21 | At first, the rule *"the customer should not be able to add to the cart a product which is already out of stock"* sounded like an intuitive invariant to me. 22 | I have even implemented a `Inventory::CheckAvailability` command which was invoked on `Inventory::InventoryEntry` aggregate root. 23 | ```ruby 24 | def check_availability!(desired_quantity) 25 | return unless stock_level_defined? 26 | raise InventoryNotAvailable if desired_quantity > availability 27 | end 28 | ``` 29 | In fact, It was doing nothing with the aggregate's internal state. This method was just raising an error if the product was out of stock. 30 | **It was a terrible candidate for a command**. It obfuscated the aggregate's code, which should stay minimalistic, and did no changes within the system. 31 | 32 | When I realized that my command made nothing but the read, I started looking for a solution in the read model. 33 | An efficient read model is eventually consistent. It is not a problem in our case. 34 | In fact, placing an order just after checking availability directly on the aggregate root neither guarantees success. Just 1 ms after checking, it could change. 35 | That's just because that command did not affect the aggregate's state. 36 | 37 | So, I prepared `ProductsAvailability` read model, which subscribes to `Inventory::AvailabilityChanged` events. 38 | I use it as a kind of validation if invoking `Ordering::AddItemToBasket` command makes any sense. 39 | 40 | " width="100%"> 41 | 42 | ```ruby 43 | def add_item 44 | if Availability::Product.exists?(["uid = ? and available < ?", params[:product_id], params[:quantity]]) 45 | redirect_to edit_order_path(params[:id]), 46 | alert: "Product not available in requested quantity!" and return 47 | end 48 | command_bus.(Ordering::AddItemToBasket.new(order_id: params[:id], product_id: params[:product_id])) 49 | head :ok 50 | end 51 | ``` 52 | 53 | Lessons that I have learned: 54 | - Started to distinguish hard business rules which go together with some state change within the system. 55 | Requirements of this kind are, in fact, good candidates for aggregates invariants. 56 | - Noticed that some requirements improve user experience but are not so critical to affecting aggregate design. 57 | Checking those, we do not care for 100% consistency with a write side. 58 | - It is OK to have some read models that are not strict for viewing purposes. 59 | -------------------------------------------------------------------------------- /posts/2022-09-28-simplify-your-system-debugging-by-introducing-event-store-linking.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2022-09-28 10:33:43 +0200 3 | author: Łukasz Reszke 4 | tags: ['rails event store', 'event sourcing', 'linking event to stream'] 5 | publish: true 6 | --- 7 | 8 | # Simplify your system debugging by introducing event store linking 9 | 10 | Last week I was dealing with an interesting bug. In our system, there was a user that didn’t belong to any tenant. And a tenant that didn’t have any users. It’s a situation that shouldn’t happen in our application. 11 | 12 | Debugging that issue was quite simple because of the [linking feature](https://railseventstore.org/docs/v2/link/) of RailsEventStore that we use in our application to correlate all events related to a user in a single stream. By linking to such a user stream you get the possibility to see all events that are related to that certain user account. 13 | 14 | " width="100%"> 15 | 16 | What is interesting here is that there’s a `UserLeftTenant` event, which in our case, should lead to deleting the user’s account if that was the only tenant that the user belonged to. But that didn’t happen. Additionally, as you can see events that happened after, there was still a possibility to log in as that user. Which resulted in a very ugly error. 17 | 18 | " width="100%"> 19 | 20 | Well, at least we can see that the account still exists and it’s still possible to log in, right? Eventually, it turned out that there was another way for a user to leave the tenant. It didn’t follow the process of checking if that was the only tenant that the user belongs to. It was also quite easy to find in the code as I was able to grep by the `UserLeftTenant` event and find that place in our codebase. Another benefit of using events ;) 21 | 22 | ```ruby 23 | class LinkByUserId 24 | def initialize(event_store: Rails.configuration.event_store, prefix: "$by_user_id_") 25 | @event_store = event_store 26 | @prefix = prefix 27 | end 28 | 29 | def call(event) 30 | user_id = event.metadata[:user_id] 31 | @event_store.link([event.event_id], stream_name: "#{@prefix}#{user_id}") if user_id 32 | end 33 | end 34 | ``` 35 | 36 | You have to subscribe to all events 37 | ```ruby 38 | event_store.subscribe_to_all_events(LinkByUserId.new) 39 | ``` 40 | 41 | Now we have to add the user_id into the metadata. 42 | In the usual Rails application, you can set it up in your `ApplicationController` as `around_action` callback. 43 | 44 | ```ruby 45 | class ApplicationController < ActionController::Base 46 | … 47 | around_action :enrich_events_with_current_user_metadata, if: :current_user 48 | … 49 | def enrich_events_with_current_user_metadata 50 | extra_metadata = { user_id: current_user.id, locale: I18n.locale.to_s } 51 | event_store.with_metadata(extra_metadata) { yield } 52 | end 53 | end 54 | ``` 55 | 56 | Of course, you don’t have to limit yourself to linking events to user streams. Anything interesting for you, your team, or the business stakeholders will work well. Don’t be scared to find new insights about your application. 57 | 58 | If you get in trouble setting up your RES, feel free to join our [community](https://railseventstore.org/community/) -------------------------------------------------------------------------------- /posts/2021-01-12-gradual-automation-in-ruby.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gradual automation in Ruby 3 | created_at: 2021-01-12T10:51:20.895Z 4 | author: Tomasz Wróbel 5 | tags: ["infra", "devops", "deployment"] 6 | publish: true 7 | --- 8 | 9 | # Gradual automation in Ruby 10 | 11 | It's the simplest piece of Ruby code you'll read today. I originally stumbled upon it [here](https://blog.danslimmon.com/2019/07/15/do-nothing-scripting-the-key-to-gradual-automation/), where it's referred as _do-nothing scripting_. I have yet another name for it, though: **Puts-First Automation** or _Puts-Driven Automation_. 12 | 13 | ## Problem 14 | 15 | You want to codify a manual process like setting up another instance of your e-commerce app. It may involve several steps with varying potential for automation (like seed the db, set up a subdomain, set up admin account). 16 | 17 | * Solution 1 📖: lay out all steps in a wiki page and teach people to conform. 18 | * Solution 2 🧠: don't even document the steps, keep it in your own head. Have people always come to you or discover it from scratch, develop tribal knowledge. 19 | * Solution 3 🖲: make everything happen at the push of a button in your glossy dashboard app. Spend weeks implementing it and months maintaining it. Lie to yourself that this is good ROI. 20 | * Solution 4 ⚙️: skip the UI part, write a script that automates it all. Wonder why people don't use it. 21 | * Solution 5 📝 + ⚙️: make a _do-nothing script_ that only tells you what to do next. Gradually automate it where it makes sense. 22 | 23 | ## An example 24 | 25 | The original example is in Python. This is how I once did it in Ruby. You can see why it's called _Puts-First Automation_ — at first you `puts` what has to be done, and you're happy to have your steps documented. Then you gradually automate, only when you think it's worth it. You can see here how one step is done manualy, and the other is automated. 26 | 27 | ```ruby 28 | STEPS = [ 29 | -> { 30 | puts "Please open https://my.hosting/dashboard" 31 | puts "and create a new subdomain." 32 | }, 33 | -> { 34 | puts "Creating admin user" 35 | system(%q{ heroku run -a my-app rails r "User.create(name: 'admin')" }) 36 | puts "Created admin user" 37 | }, 38 | ] 39 | 40 | def ask_to_continue 41 | puts 'Continue? [Y/n]' 42 | input = STDIN.gets.chomp 43 | unless input == '' || /^[Yy]$/.match(input) 44 | puts 'User cancelled.' 45 | exit 46 | end 47 | end 48 | 49 | STEPS.each_with_index do |step, i| 50 | puts "---------------------------------" 51 | puts "Step #{i}" 52 | puts "---------------------------------" 53 | puts 54 | step.call 55 | puts 56 | ask_to_continue if i < (STEPS.size - 1) 57 | end 58 | ``` 59 | 60 | ## Advantages of _Puts-First Automation_ 61 | 62 | * It's version controlled just as the rest of your stuff. 63 | * It's easy to start with — at the beginning nothing needs to be automated. 64 | * It can keep track of your progress (as opposed to a wiki page) 65 | * You can automate just some steps, leave the rest to be done manually 66 | 67 | ## I bet you can make the above snippet better! 68 | 69 | Send me a gist showing how you do it and I'll link your example here. [DMs open](https://twitter.com/tomasz_wro). 70 | 71 | Got comments? [Reply under this tweet](https://twitter.com/tomasz_wro/status/1348956291117547520). 72 | -------------------------------------------------------------------------------- /posts/2017-06-23-tracking-dead-code-with-metrics.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-06-23 15:57:39 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'chillout', 'custom metrics', 'dead code' ] 6 | newsletter: arkency_form 7 | img: ruby-rails-metrics-for-detecting-unused-code/chillout-io-grafana-alert-unused-code2.jpg 8 | --- 9 | 10 | # Tracking dead code in Rails apps with metrics 11 | 12 | When you work in big Rails application sometimes you would like to remove certain lines of code or even whole features. But often, you are not completely sure if they are truly unused. What can you do? 13 | 14 | 15 | 16 | With [chillout.io](http://chillout.io/) and other monitoring solutions that's easy. Just introduce a new metric in the place of code you are unsure about. 17 | 18 | ```ruby 19 | class SocialSharesController < ApplicationController 20 | def friendster 21 | Chillout::Metric.track('SocialSharesController#friendster') 22 | 23 | # normal code 24 | end 25 | end 26 | ``` 27 | 28 | After you add a graph to your panel, you can easily [configure an alert](http://docs.grafana.org/alerting/rules/) with notifications to Slack, email or whatever you prefer, so that you are pinged if this code is executed. 29 | 30 | <%= img_fit("ruby-rails-metrics-for-detecting-unused-code/chillout-io-grafana-alert-unused-code2.jpg") %> 31 | 32 | Wait an appropriate amount of time such as a few days or weeks. Make sure the code was not invoked and talk to your business client, boss, CTO or coworkers to make the final call that the feature should be dropped. Now you have the arguments. 33 | 34 | We all know that unused code is burden for our whole team because we keep supporting it, refactoring (yes, sometimes we do renames or upgrades and we spend time on code delivering no value, don't we?). Even because it keeps appearing in search results or occupying space in our mind. 35 | 36 | As [Michael Feathers greatly explained](http://michaelfeathers.typepad.com/michael_feathers_blog/2011/05/the-carrying-cost-of-code-taking-lean-seriously.html) 37 | 38 | > No, to me, code is inventory. It is stuff lying around and it has substantial cost of ownership. It might do us good to consider what we can do to minimize it. 39 | 40 | > I think that the future belongs to organizations that learn how to strategically delete code. Many companies are getting better at cutting unprofitable features in their products, but the next step is to pull those features out by the root: the code. Carrying costs are larger than we think. There's competitive advantage for companies that recognize this. 41 | 42 | Or as [Eric Lee put it](https://blogs.msdn.microsoft.com/elee/2009/03/11/source-code-is-a-liability-not-an-asset/): 43 | 44 | > However, the code itself is not intrinsically valuable except as tool to accomplish some goal. Meanwhile, code has ongoing costs. You have to understand it, you have to maintain it, you have to adapt it to new goals over time. The more code you have, the larger those ongoing costs will be. It’s in our best interest to have as little source code as possible while still being able to accomplish our business goals. 45 | 46 | Or as [James Hague](http://prog21.dadgum.com/177.html) expressed it: 47 | 48 | > To a great extent the act of coding is one of organization. Refactoring. Simplifying. Figuring out how to remove extraneous manipulations here and there. 49 | 50 | -------------------------------------------------------------------------------- /posts/2016-12-23-why-would-you-even-want-to-listen-about-ddd.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-12-26 08:15:56 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'ddd' ] 6 | newsletter: arkency_form 7 | img: "rails-ddd-listen/why-ddd-rails.png" 8 | --- 9 | 10 | # Why would you even want to listen about DDD? 11 | 12 | You might have heard about this thing called DDD which stands for _Domain-Driven Design_. 13 | The name does not reveal much about itself. So maybe you wonder why should you listen 14 | about it. What's so good in it? What problems does it try to solve? 15 | 16 | 17 | 18 | If you look at the cover of the book (often referred to as the _Blue Book_) which brought 19 | a lot of attention to DDD you will see the answer. 20 | 21 | ![](https://t0.gstatic.com/images?q=tbn:ANd9GcRMZdy6ljlwtPjLnytZhArgnMkeQX9SusHSVtmIur3sTlNOhp2E) 22 | 23 | The subtitle says "Tackling Complexity in the Heart of Software". That's what DDD is all about. 24 | Managing, fighting and struggling with complexity. Building software according to certain 25 | principles which help us build *maintainable* code. 26 | 27 | So... If every 3 months you start a new simple Rails application, a new prototype which may or may 28 | not is successful then probably DDD is not for you. You don't accumulate enough complexity in 3 29 | months probably. If you work on short projects (in terms of development and time to live) 30 | for example, because you work for a marketing agency and that's the kind of applications you develop 31 | then DDD is probably not for you. 32 | 33 | When is DDD most useful in my opinion? In the long term. When you work on years-long projects 34 | which are supposed to have even more years-long time of usage. When the cost of maintenance 35 | and expanding is much more important than the cost of development. But even there you start to 36 | introduce the techniques gradually when the need arises. When you see the complexity reaching a certain level. When you understand the domain better. 37 | 38 | DDD is just a name for a set of techniques such as: 39 | 40 | * Bounded Contexts 41 | * Domain Events 42 | * Aggregates 43 | * Entities 44 | * Repositories 45 | * Value Objects 46 | * Sagas 47 | * Read models 48 | 49 | As with every programming technique, you don't need to use all of them. You can cherry pick those that you benefit most from and start using them at the beginning. In my projects, the most beneficial were Bounded Contexts, Domain Events, Sagas. 50 | 51 | So if you are wondering... Are [DDD books](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) for me? Is [Arkency's DDD workshop](/ddd-training/) for me? Should I invest my 52 | time and money into learning those techniques? Then the first questions you should ask yourself is 53 | 54 | * Do I have complexity in my application that I struggle with? 55 | * Do I feel the pain of developing this application? 56 | 57 | Because if not then you can watch DDD from distance, with curiosity, but without much commitment to it. 58 | You simply have other problems in life :) 59 | 60 | But DDD was one of the [5 most important books for DHH](https://signalvnoise.com/posts/3375-the-five-programming-books-that-meant-most-to-me) 61 | so definitelly you will benefit from learning it as well. Join our [upcoming DDD workshop](/ddd-training/) 62 | in January to spend 2 days practicing those techniques in Rails applications. 63 | -------------------------------------------------------------------------------- /posts/2015-03-09-fast-introduction-to-event-sourcing-for-ruby-programmers.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-03-09 23:41:01 +0100 3 | publish: true 4 | author: Tomasz Rybczyński 5 | newsletter: arkency_form 6 | tags: [ 'domain event' ] 7 | img: "events/events.jpg" 8 | --- 9 | 10 | # Fast introduction to Event Sourcing for Ruby programmers 11 | 12 |

13 |

14 | " width="100%"> 15 |
16 |

17 | 18 | Many applications store a current state in these days. Although there are situations where we want to see something more than a current information about our domain model. 19 | If you feel that need Event Sourcing will help you here. 20 | 21 | The Event Sourcing is an architectural pattern which allows us to keep information about object’s state as a collection of events. 22 | These events represent modifications of our model. If we want to recreate current state we have to apply events on our „clean” object. 23 | 24 | 25 | 26 | ## Domain Events 27 | 28 | Domain Events are the essence of whole ES concept. We use them to capture changes on model’s state. Events are something that has had already happened. Each event represent one step of our model’s life. 29 | The most important feature is that every Domain Event is immutable. This is because they represent domain actions that took place in the past. We should not modify persisted event. 30 | Every change has to be reflected in model's state. 31 | 32 | Events should be named as verb in past tense. The name should represent `Ubiquitous Language` used in project. For example `CustomerCreated`, `OrderAccepted` and so on. 33 | Implementation of event it is very simple. Here I have an example created by one of my team-mates in Ruby: 34 | 35 | ```ruby 36 | module Domain 37 | module Events 38 | class OrderCreated 39 | include Virtus.model 40 | 41 | attribute :order_id, String 42 | attribute :order_number, String 43 | attribute :customer_id, Integer 44 | 45 | def self.create(order_id, order_number, customer_id) 46 | new({order_id: order_id, order_number: order_number, customer_id: customer_id}) 47 | end 48 | end 49 | end 50 | end 51 | ``` 52 | 53 | As we can see It is only a data structure with all needed attributes. (Example solution has been taken from [here](https://github.com/mpraglowski/cqrses-sample)) 54 | 55 | ## Event Store 56 | 57 | Event Sourcing approach events are our storage mechanism. The place where we keep events is called Event Store. 58 | It can be everything like a relational DB or NoSQL. We save events as streams. Each stream describe state of one model (Aggregate). 59 | Typically, event store is capable of storing events from multiple types of aggregates. We save events as they happened in time. This way we have complete a log of every state change ever. 60 | After all we can simply load all of the events for an Aggregate and replay them on new object instance. This is it. 61 | 62 | ## Base of knowledge 63 | 64 | - http://martinfowler.com/eaaDev/EventSourcing.html 65 | - http://ookami86.github.io/event-sourcing-in-practice/#considering-event-sourcing/01-pros-and-cons-of-event-sourcing.md 66 | - http://martinfowler.com/eaaDev/DomainEvent.html 67 | - https://geteventstore.com/ 68 | - http://www.udidahan.com/ 69 | - https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf 70 | - https://github.com/mpraglowski/cqrses-sample 71 | 72 | -------------------------------------------------------------------------------- /posts/2016-07-10-respond-to-format-is-useful-even-without-multiple-formats.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-07-21 16:39:29 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'rails', 'respond_to', 'format' ] 6 | --- 7 | 8 | # respond_to |format| is useful even without multiple formats 9 | 10 | You might think that if a controller action is only 11 | capable of rendering HTML, there is not much reason 12 | to use `respond_to`. After all, this is what 13 | Rails scaffold probably taught you. 14 | 15 | 16 | 17 | ```ruby 18 | class PostsController < ApplicationController 19 | def index 20 | @posts = Post.all 21 | end 22 | end 23 | ``` 24 | 25 | There is, however, one very annoying situation in which 26 | this code will lead to an exception: when a silly client 27 | asks to get your page in XML format. 28 | 29 | Try for yourself: 30 | 31 | ``` 32 | curl -H "Accept: application/xml" localhost:3000/posts 33 | ``` 34 | 35 | You will get an exception: 36 | 37 | ``` 38 | Missing template posts/index, ... :formats=>[:xml] 39 | ``` 40 | 41 | The client will get `500` error indicating that the 42 | problem was on the server side. 43 | 44 | ``` 45 | 46 | 47 | 500 48 | Internal Server Error 49 | 50 | ``` 51 | 52 | This problem will be logged by an 53 | exception tracker, that you use for your Rails app. 54 | However, there is nothing we can do about the 55 | fact that someone out there thinks they can get 56 | a random page in our app via XML. We don't need 57 | a notification every time that happens. And the 58 | bigger your website, the more often such random 59 | crap happens. 60 | 61 | But we also don't want to ignore those errors completely 62 | when they occur. There could be a situation in 63 | which they can help us catch a real problem i.e. 64 | a refactoring which went wrong. 65 | 66 | How can we fix the situation? Just add `respond_to` 67 | section indicating which formats we support. 68 | You don't even need to pass a block to `html` 69 | method call. 70 | 71 | ```ruby 72 | class PostsController < ApplicationController 73 | def index 74 | @posts = Post.all 75 | respond_to do |format| 76 | format.html 77 | end 78 | end 79 | end 80 | ``` 81 | 82 | Alternatively, you can go with `respond_to :html`. 83 | But that itself is not sufficient and requires 84 | using it together with `respond_with`. 85 | 86 | ```ruby 87 | class PostsController < ApplicationController 88 | respond_to :html 89 | 90 | def index 91 | @posts = Post.all 92 | respond_with @posts 93 | end 94 | end 95 | ``` 96 | 97 | After such change, the client will get `406` error 98 | when the format is not supported. 99 | 100 | ``` 101 | 102 | 103 | 406 104 | Not Acceptable 105 | 106 | ``` 107 | 108 | And _missing template_ exception leading to `500` error code 109 | will occur only when you really have a problem with the template 110 | for supported MIME types (HTML in our example). 111 | 112 | P.S. If you haven't heard yet, [Post-Rails Way Book Bundle](http://www.railsbookbundle.com/) 113 | is ending soon. With 55% discount you can buy 8 products that will help you a lot. Especially 114 | if you work with bigger or legacy Rails applications. Enjoy! 115 | -------------------------------------------------------------------------------- /posts/2015-03-24-how-to-store-large-files-on-mongodb.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-04-02 11:58:03 +0200 3 | publish: true 4 | author: Robert Krzysztoforski 5 | tags: [ 'mongodb', 'gridfs', 'sidekiq' ] 6 | newsletter: arkency_form 7 | img: "how-to-store-large-files-on-mongodb/img.jpg" 8 | --- 9 | 10 | # How to store large files on MongoDB? 11 | 12 |

13 |

14 | " width="100%"> 15 |
16 |

17 | 18 | The common problem we deal with is importing files containing a large amount of records. In my previous article 19 | I've presented how to [speed up saving data in MongoDB](http://blog.arkency.com/2015/03/why-saving-data-using-mongohq-takes-so-long/). In this article i will focus on how we can store these files. 20 | 21 | 22 | 23 | Sometimes we want to store file first and parse it later. This is the case when you use async workers like Sidekiq. 24 | To workaround this problem you need to store the file somewhere. 25 | 26 | ## First solution 27 | 28 | MongoDB allows us to store files smaller than 16MB as a string in DB. We can simply do it by putting all the data in _file\_data_ attribute. 29 | 30 | ```ruby 31 | class FileContainer 32 | include Mongoid::Document 33 | include Mongoid::Timestamps 34 | 35 | field :file_name, type: String 36 | field :file_format, type: String 37 | field :file_data, type: String 38 | end 39 | 40 | file = File.open(xls_file_path) 41 | FileContainer.new.tap do |file_container| 42 | file_container.file_format = File.extname(file.path) 43 | file_container.file_name = File.basename(file.path, file_container.file_format) 44 | file_container.file_data = file.read 45 | file_container.save 46 | end 47 | ``` 48 | 49 | The code above may work well if you upload files smaller than 16MB, but sometimes users want to import (or store) files even larger. 50 | The bad thing in presented code is that **we are losing information about the original file**. That thing may be very helpful when you need to open the file in a different encoding. **It's always good to have the original file.** 51 | 52 | ## Second solution 53 | 54 | In this case we’ll use a concept called **GridFS**. This is MongoDB module for storing files. To enable this feature in Rails we need to import a library called 55 | _mongoid-grid\_fs_. The lib gives us access to methods such as: 56 | 57 | - grid\_fs.put(file_path) - to put file in GridFS 58 | - grid\_fs.get(id) - to load file by id 59 | - grid\_fs.delete(id) - to delete file 60 | 61 | 62 | ```ruby 63 | require 'mongoid/grid_fs' 64 | 65 | class FileContainer 66 | include Mongoid::Document 67 | include Mongoid::Timestamps 68 | 69 | field :grid_fs_id, type: String 70 | end 71 | 72 | file = File.open(xls_file_path) 73 | 74 | grid_fs = Mongoid::GridFs 75 | grid_file = grid_fs.put(file.path) 76 | 77 | FileContainer.new.tap do |file_container| 78 | file_container.grid_fs_id = grid_file.id 79 | file_container.save 80 | end 81 | ``` 82 | 83 | In the second solution we are storing the original file. We can do anything what we want with it. **GridFS is useful not only for storing files that exceed 16MB but also for storing any files for which you want access without having to load the entire file into memory.** 84 | 85 | ## References 86 | - https://github.com/ahoward/mongoid-grid_fs 87 | - http://docs.mongodb.org/manual/faq/developers/#faq-developers-when-to-use-gridfs 88 | 89 | -------------------------------------------------------------------------------- /posts/2016-07-10-domain-events-schema-definitions.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-07-10 15:40:11 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'ddd', 'schema', 'rails_event_store' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # Domain Events Schema Definitions 10 | 11 | When we started three years ago publishing Domain Events 12 | in our applications, we were newbies in 13 | the DDD world. I consider the experiment to be 14 | very successful but some lessons that had to 15 | be learned the hard way. 16 | 17 | 18 | 19 | At the very beginning, we were just publishing events. 20 | We didn't think much about consuming them. 21 | We haven't yet considered them a very powerful 22 | mechanism for communication between the application 23 | subsystems (called Bounded Contexts in DDD world). 24 | And we didn't think much about how those events 25 | would evolve in the future. 26 | 27 | Nowadays, one of our events has 18 handlers. And I believe 28 | this number will continue growing. 29 | 30 | We also started using domain events in many smaller 31 | test i.e. tests for one class or one sub-system. 32 | 33 | So at some point in time, it became necessary that what 34 | we publish in code, what we expect in tests and what 35 | we use to set up a state in tests has the same interface. 36 | All those events should contain the same attribute names 37 | and the same types of values in them. 38 | 39 | For that I used `classy_hash` gem which raises 40 | useful exceptions when things don't match. 41 | 42 | ```ruby 43 | class PaymentNeedsMoreTime < RailsEventStore::Event 44 | SCHEMA = { 45 | order_id: Integer, 46 | payment_id: Integer, 47 | payment_gateway_name: String, 48 | seconds_needed: Integer, 49 | } 50 | 51 | def self.strict(data, **attr) 52 | ClassyHash.validate(data, SCHEMA) 53 | new(data, **attr) 54 | end 55 | end 56 | ``` 57 | 58 | I tried an approach in which the event schema is validated 59 | in a constructor phase `(new/initialize)` but later decided against 60 | it. In a few very rare cases we might be OK with an event 61 | which is not completely full (not all attributes are present). 62 | When we get historical events 63 | from event store we don't want (or need) to verify the schema as well. 64 | 65 | So instead when you want to verify the schema (in 97% of cases) 66 | you should just use the `strict` method to create the event 67 | instead of `new`. 68 | 69 | ```ruby 70 | stream_name = "payment_#{payment.id}" 71 | event = PaymentNeedsMoreTime.strict( 72 | order_id: payment.order_id, 73 | payment_id: payment.id, 74 | payment_gateway_name: "v8", 75 | seconds_needed: 30*60*60, 76 | ) 77 | client.publish_event(event, stream_name) 78 | ``` 79 | 80 | `classy_hash` supports nullable keys (value is nil), optional keys (value not present), 81 | multiple choices, regular expressions, ranges, lambda validations, nested arrays and hashes. 82 | 83 | I know some people who use `dry-types` for defining events' schema 84 | and they were happy with that library as well. 85 | 86 | With 220 domain events that we already publish, with every new 87 | that I add, I remember to define its schema. That way it's much 88 | easier for every other team member to know what they can expect 89 | in those events just by looking at their definition. 90 | 91 | Check out our [`rails_event_store`](http://railseventstore.arkency.com) 92 | gem if you want to start publishing domain events as well. 93 | -------------------------------------------------------------------------------- /posts/2017-07-14-the-easiest-posts-to-write-for-a-programming-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-07-14 10:25:06 +0200 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'blogging' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # The easiest posts to write for a programming blog 10 | 11 | Here are the 4 easiest types of posts that you can write about on your programming blog: 12 | 13 | * before/after 14 | * **show some code, explain** 15 | * explain how you solved a problem 16 | * opinion on another blog-post 17 | 18 | 19 | 20 | 21 | 22 | The purpose of this list is not for you to always write easiest posts. But to know that not every post needs to be a bible, guide or very deep dive in. Shorter forms are welcomed nicely in programming communities and does not require tremendous amount of time to read. They have their own, valuable place in your blogging style. Especially at the beginning of your blogging journey when you are not an expert yet. And especially when you want to build a habit and keep writing regularly, but you don't feel inspired or don't have the time for anything very long. 23 | 24 | I wanted to elaborate a bit more about _show some code and explain_ technique presented by Andrzej in the video. 25 | 26 | I want you to remember, **it does not need to be your code**. Sometimes you don't feel inspired by the code you wrote recently in your daily work. **Don't force yourself**. 27 | 28 | Think about your **favorite** gem, package, library. Go to github (usually) to check its code. Find lib or src or other directory with code. Find an a filename which sounds important or interesting and start reading. Don't assume you are going to understand the code easily, but try to get an understanding what this class/function/file does and how it fits in the whole library that you already like and use. Spend as long doing it as you want. 29 | 30 | Usually we don't like jumping to unknown code as programmers and reading someone else code for the first time can be challenging. However... **most popular open source libraries usually maintain good quality because of the whole community effort**. When you browse code of a library you use and like, you already know more or less its top-level API exposed for programmers. So this is not a completely strange code to you. 31 | 32 | I guarantee the feeling won't be the same as diving for the first time into a new, legacy project that your company got :) Although that [can be interesting as well](/2015/10/advantages-of-working-on-a-legacy-rails-application/). The feeling will be more refreshing. **More curiosity instead of worrying**. After all, you don't need to maintain this codebase. You are just passing by, trying to learn something new and interesting that will make you a better programmer. Something worth sharing with your readers. 33 | 34 | You will be doing a few things at the same time: 35 | 36 | * becoming a **better programmer** by reading and learning from good code 37 | * becoming **familiar with internals** of interesting code 38 | * promoting a library you like 39 | * **teaching other developers** about programming techniques and bits of code from that library which inspired you 40 | 41 | Helping others and helping yourself at the same time. Everybody wins. 42 | 43 | Here is [how I did it last time](/2017/06/what-i-learnt-today-from-reading-gems-code/). 44 | -------------------------------------------------------------------------------- /posts/2018-03-28-what-ive-learned-at-arkency-and-why-i-am-leaving.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2018-03-28 12:53:24 +0200 3 | publish: true 4 | author: Piotr Macuk 5 | tags: [ 'async remote', 'tools' ] 6 | newsletter: arkency_form 7 | --- 8 | 9 | # What I've learned at Arkency and why I am leaving 10 | 11 | The story I'd like to tell you starts at the beginning of 2012. At that time I've run my side project [Konfeo -- event registration and management system](https://www.konfeo.com/en/). After three years of working on that, first partially and then full time, I've decided that I need to split my time to enhance and diverse income sources. I joined Arkency in 2015. 12 | 13 | 14 | 15 | It was a very good decision. I've joined to quite large legacy Rails project with a complex domain. I've started to cooperate with more developers working on the project and with the client directly. I've learned how to use new tools like [Heroku](https://www.heroku.com/home), [MongoDB](https://www.mongodb.com), [Sidekiq](https://sidekiq.org) with its pros and cons. 16 | 17 | Arkency is known for its [Domain-Driven design](/domain-driven-rails/) approach to work with Rails legacy apps. I've learned a lot of that architecture and started to use that design in my work. I even wrote a post on our blog about one of DDD tools: [repositories](/2015/06/thanks-to-repositories/). 18 | 19 | The style of work at Arkency is very good planned. We use [Trello](https://trello.com/) with [Kanban](https://pl.atlassian.com/agile/kanban) like process. Even most complicated features are easy to accomplish because we split each feature into tiny tasks and work with one baby step at a time approach. The work is processing with [remote and async style](/async-remote/). It means that we don't need to work at client's place and at client's time. All communication in the project and in the company is doing via [Slack](https://slack.com/) and [Trello](https://trello.com/) asynchronously. The one synchronous moment in a week is our Friday weekly meeting but it is also recorded and may be consumed in an async way. 20 | 21 | There are at least two interesting and important habits I've learned at Arkency. The first one is Monday security update (thanks [Paweł](/by/pacana/)). Every Monday I update my all projects and infrastructure. For code, I use tools like `bundle-audit` and `bundle outdated`. For servers, I update packages and apply security patches. 22 | 23 | The second habit is [starting from the middle approach](/2015/03/blogging-start-from-the-middle/) (thanks [Andrzej](/by/andrzejkrzywda/)). It's like [MVP](https://en.wikipedia.org/wiki/Minimum_viable_product) for every task. The work should be time-boxed, shippable and done with next iterations in mind. There is a proverb for this approach: "done is better than perfect" :) 24 | 25 | So the question is why am I leaving such great company? As I said at the beginning my plan was to enhance and diverse income sources. After 3 years and around 2.500 hours of work, I had to decide what should be my next step. I had two challenging projects to work on and both projects need more and more attention. Additionally, I've started to practice [Aikido](https://en.wikipedia.org/wiki/Aikido) a few months ago and feel more need to no rush myself and have some time to develop my inner peace. 26 | 27 | I've decided to go full time with my own SaaS business [Konfeo](https://www.konfeo.com/en/) which is profitable more and more each month. Is the decision good? Time will show... 28 | 29 | -------------------------------------------------------------------------------- /posts/2021-03-19-use-activeadmin-like-a-boss.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2021-03-19 17:37:56 +0100 3 | author: Szymon Fiedler 4 | tags: ["activeadmin", "rails", "legacy", "ddd", "read model"] 5 | publish: true 6 | --- 7 | 8 | # Use ActiveAdmin like a boss 9 | 10 | ActiveAdmin is widely used administration framework in Rails applications. This post explains how to hook more sophiscicated domain logic into it. 11 | 12 | 13 | 14 | An ActiveAdmin resource can be generated to perform CRUD operations on the application's models. However, there are scenarios where: 15 | 16 | - business logic is no longer a part of the model and it lives somewhere else, eg. service, aggregate 17 | - it is not desired to remove the data, but instead set a status, eg. "Cancelled" 18 | - admin need super powers which are not living in the ActiveRecord model (like in the first item) 19 | 20 | Taking this into account, classic CRUD approach is simply not enough. Below, there's a short example extracted from sample [Order management app](https://github.com/RailsEventStore/cqrs-es-sample-with-res) 21 | 22 | ```ruby 23 | ActiveAdmin.register Order do 24 | controller { actions :show, :index, :cancel } 25 | 26 | member_action :cancel, method: %i[post] do 27 | command_bus.( 28 | Ordering::CancelOrder.new(order_id: Order.find(params[:id]).uid), 29 | ) 30 | redirect_to admin_orders_path, notice: 'Order cancelled!' 31 | end 32 | 33 | action_item :cancel, 34 | only: %i[show], 35 | if: proc { 'Submitted' == resource.state } do 36 | link_to 'Cancel order', 37 | cancel_admin_order_path, 38 | method: :post, 39 | class: 'button', 40 | data: { 41 | confirm: 'Do you really want to hurt me?', 42 | } 43 | end 44 | end 45 | ``` 46 | 47 | What's happening here: 48 | 49 | - `controller { actions :show, :index, :cancel }` — only certain actions are allowed, no _destroy_, _create_ nor _update_ 50 | - custom `cancel` action is defined, which invokes `Ordering::CancelOrder.new` command. In effect, we call a [`cancel` method on `Order` aggregate](https://github.com/RailsEventStore/cqrs-es-sample-with-res/blob/af0c89831328f6f0a707797e2e660e538899585b/ordering/lib/ordering/order.rb#L44-L48) and read model [is being updated accordingly](https://github.com/RailsEventStore/cqrs-es-sample-with-res/blob/af0c89831328f6f0a707797e2e660e538899585b/app/read_models/orders/on_order_cancelled.rb#L1-L9) 51 | - to display a button to perform action `action_item` block is used; the button is rendered only when operation is available to perform — not all the _Orders_ are cancellable. [It's also a good pattern to not present to user operations which can't be performed](https://stories.justinewin.com/disabled-buttons-dont-have-to-suck-10da0bb6d37e) 52 | 53 | One can say — _but it's just a status column update_. Nope, that tiny column update is a result of business operation taking specific constraints into account. Moreover, data remain consistent because there's no longer a possibility to "just" update the column or many of them. 54 | That updated column is a representation in read model which is not meant to deliver domain behaviour, only data for display — _Implementing Domain-Driven Design, Vaughn Vernon_. 55 | 56 | I find this useful, especially if one wants to avoid multiple checks and conditions (eg. IF statements) in code to determine whether certain actor (admin in our scenario) is capable of doing certain operation. 57 | -------------------------------------------------------------------------------- /posts/2015-01-03-fearless-refactoring-1-dot-1-validations.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2015-01-03 15:07:05 +0100 3 | publish: true 4 | author: Robert Pankowecki 5 | tags: [ 'rails', 'validations' ] 6 | img: "validations-categories/157H.jpg" 7 | --- 8 | 9 | # The categories of validations 10 | 11 |

12 |

13 | " width="100%"> 14 |
15 |

16 | 17 | There are many reasons why rails changed the landscape of web development when it was released. 18 | I think one of the aspects was how easy it was to communicate mistakes (errors) to the user. 19 | Validations is a very nice convention for CRUD applications. Over time however, many of our active 20 | record objects tend to grow in terms of validations. I think they fit at least into a few 21 | categories (probably more). 22 | 23 | 24 | 25 | * **trivial validations** 26 | * _"is this field empty?"_ 27 | * trivial doesn't mean not important (we still need them) 28 | * **domain validations** 29 | * _"is this product available in inventory"_ 30 | * _"does this coupon code apply in this situation"_ 31 | * **security validation** 32 | * _"is this user allowed to do it"_ 33 | * _"is the data not crafted in wrong way"_ 34 | * _"do all associated children belong to the same user as parent record"_ 35 | * **use-case based** 36 | * _"is this facebook user registering and does she/he have this attribute present"_ 37 | * **aggregate internal state** 38 | * _"is this Order without empty OrderLines"_ 39 | * _"does the order transaction amount equal amount from sum of order lines"_ 40 | 41 | So it's not uncommon to have half a dozen unrelated validations in an active record class. And they **sometimes lack cohesion** when you look at them together. To achieve better clarity and modularity in the application it might be good to start moving them to separate places. 42 | 43 | Maybe the trivial validations are better suited in form objects in your case? Maybe domain checks should be moved into Service objects because our `Order` class shouldn't know and access the `Inventory` related data? Maybe **security validations don't need to be validations at all**? If security constraint is violated raising an exception could be better. Polite users won't see this validation ever anyway. Why be nice to hackers and display them a nice validation message? 44 | 45 | Use-case based verifications? Maybe they belong to that one usecase (service object) only and the rest of the app doesn't need to know about it. The point being... 46 | 47 | Once you find yourself in a situation where your object is coupled with many validations, especially such that are **crossing multiple boundaries** and verifying things from completely other parts of the system, you might wanna decouple it a little bit. Make it lighter. Move the validations into other places where they fit better. **Validations are often big bag of things that we need to check before we let the user proceed further. It's worth to think from time to time how to organize this bag. Maybe into a nice rucksack?** 48 | 49 | That's why [Fearless Refactoring: Rails Controllers](http://rails-refactoring.com/) 1.1 release includes 2 practical and 2 theoretical chapters that will help you get started. 50 | 51 | * Extract conditional validation into Service Object 52 | * Extract a form object 53 | * Validations: Contexts 54 | * Validations: Objectify 55 | 56 | Happy refactoring 57 | 58 | Robert Pankowecki & Andrzej Krzywda 59 | -------------------------------------------------------------------------------- /posts/2021-04-16-render-is-not-your-final-word-in-your-rails-controllers-action.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "'render' is not your final word in your Rails controller's action" 3 | created_at: 2021-04-16T07:45:16.021Z 4 | author: Jakub Kosiński 5 | tags: ["rails", "rendering"] 6 | publish: true 7 | --- 8 | 9 | Today I have a quick tip for you. Suppose you are using some library that performs some logic in your controller and render some templates once some invariants are not met. 10 | Assume the library is old and was created once you were not using JSON API but was only rendering HTML templates. 11 | Now you need to add similar logic to your API controller. You may think that you should now modify the library to handle JSON responses, but that's not the only solution you can use. 12 | 13 | Remember that the [`render`](https://api.rubyonrails.org/classes/ActionController/Renderer.html#method-i-render) method in your controllers is not returning the control flow from your action and you can still modify the response or perform some operations once you call [`render`](https://api.rubyonrails.org/classes/ActionController/Renderer.html#method-i-render). 14 | You only cannot call [`render`](https://api.rubyonrails.org/classes/ActionController/Renderer.html#method-i-render) more than once in a single action as that will raise the [`DoubleRenderError`](https://api.rubyonrails.org/classes/AbstractController/DoubleRenderError.html) exception. 15 | 16 | This means you can enhance your action without touching the library. Let's assume your library is exposing a module with a method that is rendering a template in case on an exception (I'm not going to discuss if the library is well-written here, this in only an example): 17 | 18 | ```ruby 19 | module ActivationCheck 20 | 21 | def check_active(id) 22 | ActivationChecker.new.call(id) # returns true if a project with given id is active, raises an error otherwise 23 | rescue ActivationCheck::Error => exc 24 | render("activation_check/error", message: exc.message) 25 | false 26 | end 27 | end 28 | ``` 29 | 30 | and you API controller looks like this: 31 | 32 | ```ruby 33 | class ProjectsController 34 | include ActivationCheck 35 | 36 | def show 37 | project = Project.find(params[:id]) 38 | if check_active(project.id) 39 | render json: project 40 | end 41 | end 42 | end 43 | ``` 44 | 45 | The problem with using the library in your controller is that when the `check_active` method returns `false`, it also means the HTML template with `200 OK` status will also be rendered in the response. 46 | You can always create some JSON template to overwrite the default HTML template provided by your library, but this will still return `200 OK` status (and you should not return successful status code if your response is not successful). In order to handle this, let's just modify your response status directly later in the flow: 47 | 48 | ```ruby 49 | class ProjectsController 50 | include ActivationCheck 51 | 52 | def show 53 | project = Project.find(params[:id]) 54 | if check_active(project.id) 55 | render json: project 56 | else # in this branch ActivationCheck has already rendered a template 57 | response.status = :bad_request 58 | end 59 | end 60 | end 61 | ``` 62 | 63 | Now, as long as you create a JSON template to overwrite the default one (e.g. `app/views/activation_check/error.json.erb`), you will return the JSON response with proper status code in your controller without modifying the original library. 64 | -------------------------------------------------------------------------------- /posts/2014-01-07-you-win-by-being-remote.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2014-01-09 12:21:00 +0100 3 | publish: true 4 | author: Szymon Fiedler 5 | tags: ['identity', 'team', 'async remote'] 6 | newsletter: async_remote_main 7 | stories: ['async-remote'] 8 | --- 9 | 10 | # 37 signals was not lying, you win by being remote 11 | 12 | There is such moment in developer's life when you start looking for a new job, sooner or later. You can observe that even in Poland, there are plenty of Ruby on Rails job offers, often in very perspective companies. I probably could find interesting job in Poznań, where I live, but there were some presumptions which pushed me to apply to Arkency. 13 | 14 | 15 | 16 | Due to my life situation becoming a bit complicated and me feeling totally whacked out, I needed to find workplace where I can feel really comfortable. I wanted it to be a place where I could develop my skills and face new challenges. 17 | 18 | Few months ago I have found ["webworktravel"](https://www.facebook.com/webworktravel) facebook page and started thinking that it would be great to have a possibility to work this way. I developed my first commercial project as a remote team member, so I was close to this nominally. Unfortunatelly I was studying simultaenously and it was really hard for me to achieve this. Then I run for three and a half year of _"classic"_ office work. During this, I had some bad experiences in cooperating with some remote workers and I tried to figure why some things gone wrong and whether it is possible to make things right in the future. I started reading articles about remote work and trying to figure out how to handle it properly. Then Arkency released preview of their ["Async Remote"](https://blog.arkency.com/async-remote) book. I bought it without a falter and read within single breath. Suddenly, 37signals released "Remote". I was joking that maybe it would be as good as the Arkency one. I started to believe again that remote work could be possible if company has well organised processes to handle this way of working. 19 | 20 | When I was looking for a job I received some propositions, mainly with relocation to some other cities in Poland or to Berlin, Germany. Some of the companies looked promising to me, some of them even offered remote work. I was able to relocate, but working remotely was an interesting alternative for me. Even so I felt that I could become some kind of a dropout because of not being in the office and having less contact with the rest of the team. Working remote, but still from 9am till 5pm makes not really big sense for me. In this case, the only value is no need of relocation. Then I realised that there's a team that works remotely and asynchronously. Asynchrony is the key factor which makes remote working really attractive. It's really great feature when you can work in your comfort zone, you can go to the doc or plan your working hours to spend more time with your family, especially when your spouse doesn't have such flexible work. 21 | 22 | I have known some of Arkency guys and I have known their skills, care about proper design and engagement in Ruby community. Their articles are often published in Ruby Weekly. That really impressed me, but thing which tipped the scale was their approach to remote work, described in the ["Async Remote"](https://blog.arkency.com/async-remote) book. 23 | 24 | During first day of my new job I was coworking, pair programming and when I was travelling back home from Wrocław, I started writing this blog post. Cool, huh? At last my new company is developer oriented. 25 | -------------------------------------------------------------------------------- /posts/2023-04-28-few-static-analysis-tricks-to-bulletproof-your-application.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2023-04-28 11:17:58 +0200 3 | author: Piotr Jurewicz 4 | tags: ['ruby', 'rails', 'static analysis'] 5 | publish: true 6 | --- 7 | 8 | # Few static analysis tricks to bulletproof your application 9 | 10 | Static analysis is the process of examining code without executing it to identify potential issues and improve its quality. 11 | By employing valuable static analysis techniques, you can enhance your application's reliability. 12 | In this article, I discuss three practical techniques that can help you resolve issues in your codebase. 13 | 14 | ## Badly named tests 15 | 16 | Recently, while tracking down unused code in our client's application, 17 | We came across a RSpec test that clearly could not pass. 18 | Indeed, when we executed the test individually using `rspec` and specifying its path, it failed as expected. 19 | However, when running the entire test suite with `bundle exec rspec`, all tests passed. 20 | Upon further investigation, it turned out that this test file didn't follow RSpec's test naming convention. 21 | 22 | As stated in the RSpec documentation: 23 | ```bash 24 | # Default: Run all spec files (i.e., those matching spec/**/*_spec.rb) 25 | $ bundle exec rspec 26 | ``` 27 | We wanted to verify whether other test files might be bypassed by RSpec. 28 | The project was huge, so it was impossible to do it manually. 29 | 30 | Using the following command, we managed to identify all problematic files: 31 | ```bash 32 | find ./spec -type f -not -name \*_spec.rb -not -path "./spec/factories/*" -not -path "./spec/support/*" | xargs rg RSpec\.describe 33 | ``` 34 | We found numerous files with incorrect naming patterns, such as `*.spec.rb`, `*_sepc.rb`, and so on. After renaming these files, half of these tests turned out to be failing. 35 | 36 | ## Not resolving constants 37 | 38 | In one of my previous posts, I explained in-depth how we [tracked down not resolving constants with a parser gem](https://blog.arkency.com/tracking-down-not-resolving-constants-with-parser/). 39 | These not resolving constants represent potential runtime errors that can be easily prevented with static analysis. 40 | I have shared our script on the [public repository](https://github.com/arkency/constants-resolver) allowing you to copy [collector.rb](https://github.com/arkency/constants-resolver/blob/main/collector.rb) and effortlessly run it against your project. 41 | ```bash 42 | bundle exec ruby collector.rb app/ 43 | ``` 44 | 45 | ## Unnecessary routes 46 | 47 | Another useful script for cleaning up your codebase checks if your `routes.rb` file define any routes which do not have a corresponding controller action nor a view for implicit rendering. 48 | ```ruby 49 | # unused_routes.rb 50 | require_relative "config/environment" 51 | 52 | Rails.application.routes.routes.map(&:requirements).each do |route| 53 | next if route.blank? 54 | next if route[:internal] 55 | 56 | controller_name = "#{route[:controller].camelcase}Controller" 57 | next if controller_name.constantize.new.respond_to?(route[:action]) 58 | 59 | implicit_render_view = Rails.root.join("app", "views", *route[:controller].split('::'), "#{route[:action]}.*") 60 | next if Dir.glob(implicit_render_view).any? 61 | 62 | puts "#{controller_name}##{route[:action]}" 63 | rescue NameError, LoadError 64 | puts "#{controller_name}##{route[:action]} - controller not found" 65 | end 66 | ``` 67 | Simply copy the script and run it using `ruby unused_routes.rb`. You may be surprised by the results. 68 | -------------------------------------------------------------------------------- /posts/2022-10-07-the-final-trick-when-moving-from-crud-to-event-sourcing.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2022-10-07 12:58:50 +0200 3 | author: Łukasz Reszke 4 | tags: ['rails event store', 'event sourcing', 'initial event', 'migration event', 'ddd'] 5 | publish: true 6 | --- 7 | # The final trick when moving from Rails CRUD to Event Sourcing 8 | 9 | ## From CRUD to EventSourcing 10 | Did you ever wonder how to actually switch the model from the CRUD one to EventSourcing? There's one thing you should consider. The initial event that has to be published for existing data. Also called the migration event. 11 | 12 | 13 | 14 | At some point, one will reach a moment when they're able to switch from the old model to the new one. Well crafted, event sourced aggregate. With 100% mutant coverage. 15 | 16 | But then the questions pop up... How do you migrate data from legacy model to the new aggregate? How should you seed the aggregate with the migration event? 17 | 18 | ## Before you start, you have a decision to make 19 | 20 | Basically you have at least two options when it comes to the migration event. 21 | 22 | The first option is to start with the event that "starts" the life-cycle of your aggregate. If your aggregate is a `BankAccount` that is brought to life by `open` method, which produces `BankAccountOpened` event, you would migrate your legacy model by calling the method to produce this event. You could do that by script, example below 😉. 23 | 24 | The second option is a little bit different. Instead of starting with regular event that starts the lifecycle of the aggregate, you can introduce a new one that will be used only for migration. For the initial opening balance. In case of `BankAccount` the migration event could be named `LegacyBankAccountImported`. 25 | 26 | Additional, unnecessary work you might say. 27 | 28 | This approach has one huge advance. In the future if you have to analyze the stream, you'll be able to distinct aggregates that were migrated from aggregates that were created after the migration. From the debugging perspective this is very useful piece of information. I usually prefer that way. 29 | 30 | ## Let's deal with the initial event 31 | Once you know which approach you want to follow, you can use a script similar to the one below: 32 | ```ruby 33 | def stream_name(aggregate_id) 34 | "BankAccount$#{aggregate_id}" 35 | end 36 | 37 | legacy_bank_accounts = BankAccount.unscoped 38 | 39 | repository = AggregateRoot::Repository.new 40 | 41 | legacy_bank_accounts.each do |legacy_bank_account| 42 | aggregate_id = legacy_bank_account.uniq_id 43 | ApplicationRecord.with_advisory_lock(legacy_bank_account.uniq_id) do 44 | repository.with_aggregate(Banking::BankAccount.new(aggregate_id), stream_name(aggregate_id)) do |bank_account| 45 | bank_account.import_deleted(legacy_bank_account.balance, legacy_bank_account.balance_date) 46 | end 47 | end 48 | end 49 | end 50 | ``` 51 | 52 | As you can see, the script loads the data with old model. Then it iterates through that data and calls one of the `import` methods (depending on the legacy model state), producing the migration event for the aggregate. The event in this case is either `LegacyBankAccountImported` or `ClosedLegacyBankAccountImported` 53 | 54 | And that's it. You're ready to switch to the new model now 🙌 55 | 56 | 57 | PS. After the initial migration I recommend to remove the import method. The reason is that you don't want anyone to use the second time after the first usage. It would be a _a little bit_ missleading durning some debugs. 58 | -------------------------------------------------------------------------------- /posts/2016-10-27-the-typical-ruby-bugs-with-changing-the-last-line-in-a-method.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2016-10-27 23:00:34 +0200 3 | publish: true 4 | tags: [ 'ruby', 'service objects' ] 5 | author: Andrzej Krzywda 6 | --- 7 | 8 | # The typical Ruby bugs with changing the last line in a method 9 | 10 | In Ruby, we don't have to type `return` at the end of the method. As many other things in our favourite language, this is implicit, not explicit. The value of the last line in a method is also automatically the value of the whole method. Which is a good thing as it helps making the code less verbose and more poem-like. But it also has some dangers. 11 | 12 | 13 | 14 | Only this year, we've had two different situations where experienced developers introduced bugs into a system. 15 | 16 | The first story started when business wanted us to remove one existing feature from the application. This feature was something about analytics code in the view. The intention was to no longer track something. 17 | 18 | There was something like this in the code. 19 | 20 | ```ruby 21 | 22 | def analytics_code 23 | setup_which_is_meant_to_stay_here 24 | track_something 25 | end 26 | ``` 27 | 28 | The developer looked at the code. It was quite clear that the last call should be removed, so he did that. 29 | 30 | ```ruby 31 | 32 | def analytics_code 33 | setup_which_is_meant_to_stay_here 34 | end 35 | ``` 36 | 37 | Apparently, it was crucial that the value returned by this method was actually used in the view. 38 | 39 | ``` 40 | = tracker.analytics_code 41 | ``` 42 | 43 | It went through several layers, so it wasn't that easy to spot. 44 | The result? 45 | 46 | The result was actually very bad - the `setup_which_is_meant_to_stay_here` call returned a hash with a lot of information about internals of our system. And it all went to the front page of one of the systems. Which we learnt only a few hours later. 47 | 48 | The second story happened just recently, in my project. There's a place in the UI (react+redux), where we register new customers. Submitting the form creates an ajax request, which goes to the backend, which then calls a special microservice (bounded context) and then we get the response back to let the UI know that all is good with some additional information to display. It was all good and working. 49 | 50 | But then, we've had a need to extend the existing backend code with publishing an event. The code was a typical service object in a Rails app: 51 | 52 | ```ruby 53 | 54 | class RegisterNewCustomer 55 | def call 56 | customer = Customer.new(customer_params) 57 | customer_repo.save(customer) 58 | end 59 | end 60 | ``` 61 | 62 | After extending the service object, it looked like this: 63 | 64 | ```ruby 65 | 66 | class RegisterNewCustomer 67 | def call 68 | customer = Customer.new(customer_params) 69 | customer_repo.save(customer) 70 | event_bus.publish(customer_registered) 71 | end 72 | end 73 | ``` 74 | 75 | The thing is, this service object was used from a controller, like this: 76 | 77 | ```ruby 78 | 79 | def create 80 | customer = RegisterNewCustomer.new.call(customer_params) 81 | render json: customer 82 | end 83 | ``` 84 | 85 | We haven't noticed the problem at first. The visible difference was that the UI now showed a failure message, but it was actually adding the customer to the system! 86 | 87 | And the exception under the hood was something about `IOError`, which didn't help in debugging it. 88 | 89 | As you see, two different stories, but the same problem - changing the last line of a method. Be careful with that :) 90 | -------------------------------------------------------------------------------- /posts/2017-02-05-how-to-unit-test-classes-which-depend-on-rails-models.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2017-02-05 16:48:44 +0100 3 | publish: true 4 | author: Andrzej Krzywda 5 | tags: ['testing'] 6 | --- 7 | 8 | # How to unit test classes which depend on Rails models? 9 | 10 | Let's say you have such a class: 11 | (this code is borrowed from this [Reddit thread](https://www.reddit.com/r/rails/comments/5rzeeb/how_do_you_unit_test_classes_that_depend_on_models/) 12 | 13 | ```ruby 14 | 15 | class CreateSomethingService 16 | def initialize(params) 17 | parse_parameters params 18 | end 19 | 20 | def run 21 | Something.create(name: @name) 22 | end 23 | 24 | private 25 | 26 | def parse_parameters(params) 27 | @name = params[:name] 28 | end 29 | end 30 | ``` 31 | 32 | 33 | 34 | How can we test this class without loading Rails? 35 | 36 | One way to unit test it is by using the repository object and the concept of replacing the repo object with an in-memory one in tests. 37 | 38 | ```ruby 39 | 40 | class CreateSomethingService 41 | def initialize(repo, params) 42 | @repo = repo 43 | parse_parameters params 44 | end 45 | 46 | def run 47 | repo.create_something(@name) 48 | end 49 | 50 | private 51 | 52 | def parse_parameters(params) 53 | @name = params[:name] 54 | end 55 | end 56 | 57 | class SomethingsRepo 58 | def create_something(name) 59 | Something.create(name: @name) 60 | end 61 | end 62 | 63 | class InMemorySomethingsRepo 64 | attr_accessor :somethings 65 | 66 | def initialize 67 | @somethings = [] 68 | end 69 | 70 | def create_something(name) 71 | @somethings << name 72 | end 73 | end 74 | 75 | class SomethingsTest 76 | def test_creates_somethings 77 | repo = InMemorySomethingsRepo.new 78 | CreateSomethingService.new(repo, "Arkency") 79 | assert_equal(1, repo.somethings.length) 80 | end 81 | end 82 | ``` 83 | 84 | Note that the service now takes the repo as the argument. It means the controller needs to pass the right repo in the production code and we use the InMemory one in tests. 85 | Obviously, if your implementations of the repos diverge, you have a problem :) (which best to mitigate by having integration tests which do run this code with Rails) 86 | 87 | You can read more about the setup here: 88 | 89 | [InMemory fake adapters](http://blog.arkency.com/2015/12/in-memory-fake-adapters/) 90 | 91 | [Rails and adapter objects - different implementations in production and tests](http://blog.arkency.com/2016/11/rails-and-adapter-objects-different-implementations-in-production-and-tests/) 92 | 93 | It's worth noting here, that it may be better to treat a bigger thing as a unit than a single service object. For example you may want to consider testing CreateSomethingService together with GetAllSomethings, which makes the code even simpler, as the InMemory implementation doesn't need to have the :somethings attribute. 94 | 95 | [Unit tests vs class tests](http://blog.arkency.com/2014/09/unit-tests-vs-class-tests/) 96 | 97 | [Services - what they are and why we need them](http://blog.arkency.com/2013/09/services-what-they-are-and-why-we-need-them/) 98 | This setup has its limitations (the risk of diverging), but it's solvable. The benefit here is that you don't rely on Rails in tests, which makes them faster. 99 | 100 | If you like this kind of approaches to Rails apps, then you will enjoy more such techniques in my book about [Refactoring Rails](http://rails-refactoring.com) 101 | 102 | 103 | -------------------------------------------------------------------------------- /posts/2021-10-23-less-known-capability-of-rubys-json-dot-parse.md: -------------------------------------------------------------------------------- 1 | --- 2 | created_at: 2021-10-23 13:19:08 +0200 3 | author: Szymon Fiedler 4 | tags: [ruby, rails, json] 5 | publish: true 6 | --- 7 | 8 | # A lesser known capability of Ruby's JSON.parse 9 | 10 | If you ever got annoyed by the fact that `JSON.parse` returns hash with string keys and prefer hashes with symbols as keys, this post is for you. 11 | 12 | 13 | 14 | If you're a Rails developer, you're probably familiar with `deep_symbolize_keys` method in `Hash` which can help with such case. Especially, in ideal world, where our data structure is a hash, like: 15 | 16 | ```ruby 17 | require 'json' 18 | 19 | json = <<~JSON 20 | { 21 | foo: { 22 | bar: 23 | "baz" 24 | } 25 | } 26 | JSON 27 | 28 | > JSON.parse(json) 29 | => { "foo" => { "bar" => "baz" } } 30 | 31 | > JSON.parse(json).deep_symbolize_keys 32 | => { foo: { bar: "baz" } } 33 | ``` 34 | 35 | Maybe it's good enough, but we don't always live in Rails world with all the `ActiveSupport` benefits. Moreover, our JSON payloads won't always be just a hash-like structures. Let's agree on what valid JSON can be, first: 36 | 37 | ```ruby 38 | > JSON.dump("") 39 | => "\"\"" 40 | 41 | > JSON.dump(nil) 42 | => "null" 43 | 44 | > JSON.dump([{ foo: { bar: "baz" } }]) 45 | => "[{\"foo\":{\"bar\":\"baz\"}}]" 46 | ``` 47 | 48 | What we can learn from that is the fact, that the trick with `deep_symoblize_keys` won't work on all the examples above unless you go with some tricky, recursive algorithm checking the type and running `symbolize_keys` or `deep_symbolize_keys` when applicable. 49 | 50 | Let's see what Ruby itself can offer us in [JSON class documentation](https://ruby-doc.org/stdlib-3.0.0/libdoc/json/rdoc/JSON.html#module-JSON-label-Output+Options). 51 | 52 | ```ruby 53 | json = <<~JSON 54 | { 55 | foo: { 56 | bar: 57 | "baz" 58 | } 59 | } 60 | JSON 61 | 62 | > JSON.parse(json, symbolize_names: true) 63 | => { foo: { bar: "baz" } } 64 | ``` 65 | 66 | Let's check how it rolls on Array with collection of hashes: 67 | 68 | ```ruby 69 | > JSON.parse("[{\"foo\":{\"bar\":\"baz\"}}]", symbolize_names: true) 70 | => [{ foo: { bar: "baz" } }] 71 | ``` 72 | 73 | Perfect. 74 | 75 | How I discovered this feature? Some time ago I worked on a read model which had some data stored in PostgreSQL json columns. As you probably know, data are serialized and deserialized automatically. Which means, that in result of reading from json column we get data structure with string keys. 76 | 77 | ```ruby 78 | # before 79 | class FancyModel < ActiveRecord::Base 80 | end 81 | 82 | > FancyModel.last.my_json_column 83 | => [{"foo" => { "bar" => "baz" } }] 84 | ``` 85 | 86 | This was quite inconvenient to me. I wanted a reliable way to have value accessible via symbols, especially that it was an array containing individual hashes. I explored docs a bit, which allowed me to write a custom serializer: 87 | ```ruby 88 | class FancyModel < ActiveRecord::Base 89 | class SymbolizedSerializer 90 | def self.load(json) 91 | JSON.parse(json, symbolize_names: true) 92 | end 93 | 94 | def self.dump(data) 95 | JSON.dump(data) 96 | end 97 | end 98 | 99 | serialize :my_json_column, SymbolizedSerializer 100 | end 101 | 102 | > FancyModel.last.my_json_column 103 | => [{foo: { bar: "baz" } }] 104 | ``` 105 | 106 | I have a feeling that this is not a popular feature of `JSON` class in Ruby. Please don't mind sharing this post if you find it helpful. 107 | --------------------------------------------------------------------------------