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?
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 | 
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 |
--------------------------------------------------------------------------------