Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coding challenge #23

Open
wants to merge 37 commits into
base: master
Choose a base branch
from

Conversation

madalindaniel92
Copy link

@madalindaniel92 madalindaniel92 commented Aug 28, 2021

This implements the coding challenge.
This project uses Ruby 3.0.

Setup steps:
- clone repo
- make sure you have Ruby 3.0 installed - using rvm or chruby will switch Ruby to the right version
- gem install bundler # Make sure bundler is installed for Ruby 3.0
- bundle install # Install dependencies
- bundle exec rspec # Run tests
- bundle exec rubocop # Delight in code style guide nagging :P

How to run the program:

# Using local file
./coffee_place 47.6 -122.4 coffee_shops.csv

# Using remote file
./coffee_place 47.6 -122.4 https://raw.githubusercontent.com/Agilefreaks/test_oop/master/coffee_shops.csv

A few assumptions are made:

  • the Earth is flat - not really an assumption, more of an assertion

In order to appease the Non-Flat-Earth believers, we could use a gem for the haversine function.

In a real world application I would use the awesome Postgresql POSTGIS extension.
It also has the ability to use spatial indexes for distance queries.

Give folks the ability to use my awesome ideas :D
Masochism and constant nagging await
Allow spec files to span as many lines as they need to.
Skip testing coffee_place executable.
Big whoops - apparently latitude is the North-South one :D
This makes rspec output too verbose.
Uncomment this line only when debugging slow test times.
Maybe it was the wind...
@madalindaniel92 madalindaniel92 marked this pull request as ready for review August 28, 2021 06:52
I would like to give a shoutout to all other coding challenge
participants in the past that also forgot to include info about
setup or running the code.

Y'all are the real MVP.

PS: Only looked at previous PRs on this repo after already
submitting mine. This is just preemptive fixing :P
Copy link
Member

@pirvudoru pirvudoru left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for submitting. Cool solution overall. Have some questions that I would like to hear your thoughts on.


def validate_user_location!(cli_opts)
lat, lon = cli_opts.remaining
location_result = Location.validate(lat: lat, lon: lon, name: 'Current user')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reasoning behind the validate method to return, in case of success, an instance of CoffeePlace::Location? From the naming, I'd not expect it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this method does more than what is expected.
I'll try to refactor this part and push another commit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: fixed by 482136f9

location = new(lat: result_lat.value, lon: result_lon.value, name: name.to_s)

return Result.failure("Invalid latitude: #{lat}") unless location.valid_latitude?
return Result.failure("Invalid longitude: #{lon}") unless location.valid_longitude?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your take on multiple return statements? Is it good / bad, why, when should one use it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^I've the same question. 😄

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like using guard clauses - it is the only place in Ruby where I use the return statment.

Basically the success path travels down the method, while guard clauses will end control flow prematurely
in case of an unexpected input, or some precondition not met.

Here, if the location is invalid, which should be unlikely, I fail-fast on the first error and return it.
Another approach would be to have an @errors array, in which to collect errors.
I would then only check the errors at the end of the method and use Ruby's implicit expression return value
to return either the errors -if there are any, or the success value.

I think both approaches are valid.

I like Avdi Grimm's Confident Ruby book quite alot.
He uses guard clauses in order to clean up methods with branching logic in pretty neat ways.

# frozen_string_literal: true

module CoffeePlace
Result = Struct.new(:success?, :value, :error) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you decide when to return instances of this class from a method? Seeing it is used across the solution.

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Ruby, predicate methods return true / false, while finder methods return some truthy value / nil.
I find that in a lot of situations a Result type, with two variants - Success(value), and Failure(error) comes in handy. This is common in many functional languages, but also useful in OOP, when the return value you need
has to be able to represent different variants.

A really nice gem for handling this across an application is dry-monads
You don't have to go heavy on the FP. Just using variant types consistently, when neither a predicate nor a finder
does the job is sufficient.

This also enables neat ways of assembling operations, as seen in dry-transaction
Note: dry-transaction seems to be undergoing API changes, as it is not in a 1.0 version yet :D

I've used this gem in the past in a production application, validating user submitted Excel files, and it worked pretty nicely - the code was easy to extend and refactor.


This is an attempt at solving the closest coffee shop coding challenge.

All good software starts with making flawed assumptions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that ain't the truth, I don't know what is! 😹

Comment on lines +18 to +21
@locations
.map { |location| [target_location.distance_to(location), location] }
.sort_by(&:first)
.take(num_results)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏿

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love Ruby's Enum ❤️
I also enjoy this style of chaining operations in Elixir.
While inspired by LISP, it uses a data-first approach, placing the
emphasis on the data, and not on the functional transformation.

For JavaScript, I enjoy using the Ramda npm package.
Ramda puts emphasis on function composition though.
This makes it much harder to use and learn, in my opinion.

Copy link
Member

@drqCode drqCode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advanced solution overall, but a bit over engineered.

Comment on lines +23 to +24
show_help_and_exit! if cli_opts.print_help?
show_version_and_exit! if cli_opts.show_version?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious - why did you write up the methods with a bang?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like using a bang when naming methods that have important side effects.
This warns the programmer to be weary of side effects.
These methods exit the program - which is pretty severe, as far as side effects go.

In ActiveRecord's design, most methods with a bang have the side effect of throwing errors.
(e.g. save!, validate!)

In Ruby's standard library, a bang usually means that a method mutates data in place,
instead of creating a new data structure - for example Array#map!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I imagined that's the reason. Thank you for answering. :)

require_relative './search'

module CoffeePlace
# Parses and runs CLI application using supplied args.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you feel the need to explain what the classes do through additional comments?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tldr; Rubocop is being naggy as always! 🤖 😛

Rubocop has a style cop that emits warnings when you haven't added a class comment description.
While disabling this cop is valid, when method names are explicit, I prefer using a consistent style.

Basically, some classes need no further explanation - their name is sufficient to understand their purpose.
Other classes need additional explanation as to their responsibility.
This would result in some classes having a documentation comment at the top, while others would
not have it - a minor style inconsistency.

This rubocop linter forces you to consistently add documentation at the top of any class,
making sure the style remains consistent.

I'm pretty sure the rubocop config for rails disables this, as convention over configuration makes
alot of class descriptions redundant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologise for writing you a short novel in response to your comment ... I don't know how to stop myself from rambling sometimes 😛

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually disable that cop, I find it naggy too, but recently the thought occured to me that it can be much more useful in more complex projects, as it would help new developers understand the project implementation-wise more easily.

Out of curiosity - have you used this documentation cop in production? If so, has it helped the team understand the project better?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually used the rails-rubocop config, which I think has this cop turned off.

What helped us understand new projects best were design documents and mockups,
which are initially created when first interacting with the client. These explain what the
project aims to be, which is usually more than its current implementation state.

Maybe including links to helpful documents in the project's own ReadMe would help
with onboarding on new projects. We usually just shared these on Slack :D

Comments at the top of more complicated methods, or specs in which these methods are
tested usually help clarify specific parts of the code - but its hard to build a 'big picture' only
from these.

class CLIOptions
attr_accessor :version, :help, :remaining, :num_results

def initialize(version: false, help: false, remaining: [])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting use of flags. Generally, are there any issues that could arise from the use of boolean params?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidentally passing in nil is always a major pain.
There is always the possibility of someone passing a String too - such as 'false'.

ActionControlller usually does some light type casting to ensure values sent from checkboxes
or other fields are converted into boolean values.

When I use these sort of params internally in my code, I try to make sure that I always send a boolean,
even when the value is missing - by passing in a default value.

When using user submitted fields, some sort of type casting is useful.
I have found this StackOverflow answer helpful when parsing user submitted CSV files,
that contain columns which represent boolean values.

I would use something like:

module BooleanHelper
  module_function

  def cast(value)
     ActiveModel::Type::Boolean.new.cast(value)
  end
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This are all valid points, but there is also a code smell that arises from using bools in your methods, you can read more about it in this Martin Fowler article, dating way back in 2011

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more article recommendation from me: Clean code: The curse of a boolean parameter.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that using boolean arguments for control flow leads to needless complexity.
Having simpler methods to handle each case is better.
Thank you for the two articles.

Comment on lines +65 to +67
uri.open(&block)
rescue OpenURI::HTTPError => e
add_import_error "Failed to download remote file \"#{uri}\": #{e.message}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if an invalid URI is provided, e.g. http://asd.asd?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method from where with_remote_file is called - with_source has a rescue as well.

    def with_source(source_name, &block)
      uri = URI.parse(source_name)

      # When we have an URL, download remote file
      return with_remote_file(uri, &block) if uri.scheme

      # When we have a local file, try to open it
      return with_local_file(uri, &block) if File.exist?(uri.path)

      add_import_error unknown_source_error_message(source_name)
    rescue URI::InvalidURIError
      add_import_error "#{source_name} is not a valid URL or local filename"
    end

That URI::InvalidURIError catches invalid URL's.
What I try to do here is to see if it is a parsable (i.e. valid) URI.
If it is an URI, then see if it has a scheme - as all http and https URLs do.
If it is valid as an URI, but not also a URL, then blindly assume we are dealing with a file,
and try to load the CSV from that local file.

Ways this could horribly backfire: someone sends some URL with a different scheme,
e.g. a database scheme: redis://<some_ip>/whoops

This could potentially become a security vulnerability.
More sanity checks should be done on the input, before trying to open that uri.

In a real application, of course, I would place a todo at the top of the method:

  # TODO: make sure we aren't hacked in a stupid way  (commented by madalindaniel 11months ago) ... 

😛

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, whenever you download some remote file to your server ... be weary!
The night is dark and full of errors. 👻

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't read the example you provided carefully the first time.
http://asd.asd is not an invalid URI.
asd is simply not yet defined as a top level domain.

I think uri.open will result in a DNS query trying to find the IP responsible for this domain, and fail,
resulting in a OpenURI::HTTPError error being raised.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whelp, just tested it ... it throws a wonderful:
3.0.0/net/http.rb:987:in `initialize': getaddrinfo: Name or service not known (SocketError)

I am not catching that error, and it is bubbling through both rescue clauses.
I was hoping Ruby's net/http catches and re-throws these as OpenURI::HTTPError, but it doesn't.
I'll add a few more rescue clauses to catch SocketError as well ... sigh ...

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: fixed by 89e854, abb0fb09

Thank you for showing me an edge case - nice save!

nice_save

Note: normally I would delete my previous 5 comments on this same issue ... but I've decided to leave them as is.

These comments show a journey of self discovery - from conceited self-assurence, to testing and understading
false assumptions, and finally fixing a bug. Thanks for setting me on this journey Alex 😸

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, no problem :D

location = new(lat: result_lat.value, lon: result_lon.value, name: name.to_s)

return Result.failure("Invalid latitude: #{lat}") unless location.valid_latitude?
return Result.failure("Invalid longitude: #{lon}") unless location.valid_longitude?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^I've the same question. 😄

Comment on lines +77 to +80
<<~ERROR
Don't know how to import locations from: #{source_name.inspect}
Make sure the URL or filename is not misstyped.
ERROR

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you proceed if we'd want to re-use this error message in 10 other places?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tldr; constant or helper method

Long form answer

If the error message was static, I would define it as a constant, at the top of some helper module.
As this message requires a source_name parameter, it has to become a method ... probably also on some helper module.

module ErrorHelper
   module_function

   def invalid_import_location(source_name)
     <<~ERROR
        Don't know how to import locations from: #{source_name.inspect}
        Make sure the URL or filename is not misstyped.
      ERROR
   end
end

I have a thing for helper modules, don't I?! ... huh ...
They are kind of like singleton classes that don't bind any state 😛

On a serious note, it's easier to have one ErrorMessagesHelper module with all of the error messages
used throughout a large application, than having to CTRL+F the whole codebase trying to find were all
of these things are defined.

Alternative approach

I like the way the devise uses localisation yml files to keep all error messages in the same place,
and also allow for i18n. So, using the i18n gem + localisation files is also a valid option for a web app

Pointless side story containing my grumbling and complaints about past projects.

I worked on a React application in the past that had similar error messages within the render functions
of many components. To change some error message I would need to find the right component, change
the message, wait for the web-dev-server to reload, and pray that I changed the message in the right
file. It would become a CTRL+F journey through many, many files - which was thoroughly annoying.

I tried moving all of the goddamn error messages as constants in the same vanilla JS, and importing
it from there to each component that needed it.

Something like

// within src/utils/error_messages.js
export const BAD_THINGS_HAPPENED = 'Oh noes!';

// within components, at the top
import { BAD_THINGS_HAPPENED } from '@/src/utils/error_messages.js'

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to break the short novel I wrote in the previous comment, into multiple logical chapters.
Stay tuned for it's sequel!

Copy link

@alex-damian-negru alex-damian-negru Sep 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My approach is usually to just extract them to a locales/errors/en.yml or something similar and just rely on I18n.t. Worked like a charm so far, you can do interpolation if needed too.

A similar approach is also used by the dry-validation gem

Comment on lines +10 to +16
def success(value)
new(true, value, nil)
end

def failure(error)
new(false, nil, error)
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting approach - what concept are you using here to describe happy/unhappy paths?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using a sum type, with two variants.
This is similar to Result in Rust, or the Maybe type from functional languages.

The happy path should always hold a helpful value.
The unhappy paths always returns an error message.

A better approach might be to have the failure cases return any
object that responds to a message method - allowing for various
ducked type error message objects to be passed in.

This would require string error messages to be wrapped in such an object,
which could happen automatically inside the failure method here.

class Result
  FailureWrapper = Struct.new(:message)

  def self.failure(error)
    error = FailureWrapper.new(error) unless error.respont_to?(:message)

    new(false, nil, error)
  end
end

I think that using string error messages for the failure cases is sufficient
for a small example app like this, though.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, basically you're describing monads 😛

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poor man's monads, without fmap and chain/bind.
But yes! :D
(I'm referring to the Result implementation in my code)

Also, using the dry-monads gem consistently throughout multiple projects sounds nice.
It comes with alot of goodies.

Copy link

@alex-damian-negru alex-damian-negru Sep 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used it in a few projects, it worked quite great, especially when used across all the layers of the project, as errors could be wrapped in a Failure either at the repository level or business layer and it would be passed along all the way to the presentation one where it would get handled appropriately, basically making it possible to have no need for rescues, awesome stuff.

You mentioned above that you enjoy operation chaining as in Elixir - dry-monads can help in this quite nicely, something like:

def find_or_create(params)
  Try { some_repo.find(params[:id]) } # raise an exception since the record doesn't exist
    .fmap(&:itself)
    .fmap { |record| <process_record> }
    .or { some_repo.create(params.except(:id)) }
end

@@ -0,0 +1,5 @@
inherit_from: '../.rubocop.yml'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. What do you think are the pros/cons in using multiple rubocop config files over a single one?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actual answer to your question.

In my experience you need at least two rubocop.yml files.
One for your app and lib folder - to determine the style for your code.
Another one for your spec folder - to determine the style for your tests.
Tests usually have different style requirements than regular code, especially
when using RSpec's DSL.

Having more than two config files leads to inconsistencies, and is a bad practice in my opinion.

Ramblings about rubocop-rspec

I'm pretty sure rubocop-rspec automatically does this - it loads it's own rubocop config for the specs.
I didn't use rubocop-rspec because it is too strict for my taste.
I've used it on previous professional projects, but spent a lot of time fighting it.

I'm sure you can learn to benefit from it's warnings, but it distracts me from what I am trying to
test, and how I want to logically structure my tests. And the fact that I have to worry about my setup
functions beeing under a specific line length is mostly a hindrance, and not a help to improve my specs.

Legacy code woes

One reason to have extra rubocop config files is when working with legacy code.
Basically you could namespace your legacy code into a Legacy namespace, and apply
a different, far more lenient rubocop config, while your new code uses a better rubocop config.

Trying to refactor legacy code, because Rubocop complains, is a terrible idea in my opinion, as
you loose valuable context from the previous git commit, as you overwrite lines.

You can still dig through your git history, to see what the context of the previous commit to the one
that refactored for Rubocop's sake is, but that is alot of extra hassle.

Comment on lines 9 to 17
expect(geo.within_latitude_bounds?(0)).to eq(true)
expect(geo.within_latitude_bounds?(42.0)).to eq(true)
expect(geo.within_latitude_bounds?(-42.0)).to eq(true)

expect(geo.within_latitude_bounds?(90.0)).to eq(true)
expect(geo.within_latitude_bounds?(-90.0)).to eq(true)

expect(geo.within_latitude_bounds?(92.0)).to eq(false)
expect(geo.within_latitude_bounds?(-92.0)).to eq(false)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, why did you choose to break the rule of having one expectation per test?

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm lazy sometimes

Having all of these assertions in the same example here is pure laziness (tm) on my part.
I'll try to add a few more commits, describing the edge cases I am testing a bit better.

Confessions of a non-believer

That being said, I don't believe in the one expectation per test rule.
I think a test should describe and test the expectations of a single specific scenario.

I would rather have 7 expectations in the same example, testing a single well described scenario,
than break those 7 expectations out into different examples, all handling a single path through the code.

Basically, I use as many expect calls as I find necessary to thoroughly test, but try my best
to only test a single code path in one example.

If the example fails, I now know which code path to look at.
If there are 7 different examples, with one expectation each, that break because of this
one code path, it's much harder to read and understand the rspec output.

A better tomorrow, perhaps

This being said, I know I have significant overlap in the specs I wrote for this app, and that
if a code path changes many of my specs will break.

Ideally I would like to write better, less redundant specs in the future.
no tests < few tests < lots of poorly thought out tests <<< toughtful tests with useful failure output

Copy link
Author

@madalindaniel92 madalindaniel92 Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: fixed by 033654a

Copy link

@alex-damian-negru alex-damian-negru left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, apologies for the delayed reviewed. Second of all, very nice solution 💯 - personally I really liked the sassy style in which the description & comments were made (including the flat earth stuff lol), and the haversine function thing was nice to find out. I like the out-of-the-box approach here. 😄

I feel like I learned a thing or two from reviewing this, so my thanks on that. Left a few comments as well. :)

@madalindaniel92
Copy link
Author

madalindaniel92 commented Sep 4, 2021

So Long, and Thanks for All the Fish

Thank you all for your time and helpful input. ❤️
I still have at least two commits to write and push in order to fix at least a few of the issues pointed out.

Sadly, I've already accepted a job offer this week to work for a different company.
I've always worked from an open office space, in a company with a more rigid hierarchy,
and so I wasn't sure if changing to remote would work for me.

In the future, maybe a year or two, I would love to apply again for a job with your company, and work with you all.

Apparently I'm now a JS front-end developer, ye-yey?! 🤔

My new job place is within 20 minutes walking distance from where I live, which I found convenient.
Also, the folks over there are in desperate need of developers right now, so they gave me an
answer really quickly. Because they're using Javascript that interview was something like:

"""
them: Do you now what an html document looks like?! Can you write a function in JS?!
me: Yup!
them: Perfect! Exactly what we are looking for. You start tomorrow! That over there is your desk.
To setup our projects, make sure you npm install <10000 packages or so>.
"""

The folks working over there seem nice though.
The Project Managers try to put a positive spin on things - like always.
The Senior Devs are always overworked and grumpy - but still take the time to help you out.
The QA folks are always fun to hang out with.

I'll get to learn more about JavaScript. And more about centering and aligning things with CSS.
Yesterday I managed to align a group of buttons using Flexbox!
(for someone that spent alot of time on the backend debugging SQL queries I'm proud that I can center a button now!)

Hoping for a better future for us all

As previously stated, in the future, maybe a year or two, I would love to re-apply for a job with your company,
and work with you all. You all seem really neat!

I also really like Elixir/Phoenix as a tech stack. Rust and Elm are pretty neat too.
I've nothing against Ruby, but these new languages also seem pretty interesting.

I'd be interested in writing interactive web apps in Elixir, especially using Phoenix.
It's design is inspired by Rails, but it also comes with nice web socket support.
Most of ActionCable is inspired by Phoenix Channels, and what dhh is trying to
do with Turbo is similar to what Phoenix Live View already does pretty well.

This separates building a location from validating it.
It also makes validation logic more intuitive and
similar to ActiveRecord.
When an URL to an incorrect host is used, such as
"http://example.asd", the remote file download raises
an uncaught SocketError.

This will turn that pesky error into a nice error message.
@alex-damian-negru
Copy link

@madalindaniel92 Aww, shucks. Well, I'm glad you found youself a nice-looking job, though I can't say I'm entirely enthusiastic since, seeing how fun it was to interact around this PR, I would have been very much delighted to work together as colleagues 😄. Still, Hopes Die Last, right?!

Hope we'll have the chance to work together in year's time then. All the best, bud! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants