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

Rupert bitly #33

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

Conversation

RupertSaxton
Copy link

Copy link
Member

@jaybobo jaybobo left a comment

Choose a reason for hiding this comment

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

Looks good. Check out my comments

validate :url_is_correct_format, :url_responds_to_http_request

def url_is_correct_format
unless address =~ /\A#{URI.regexp}\z/
Copy link
Member

Choose a reason for hiding this comment

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

two spaces.

require 'rails_helper'

RSpec.describe Url, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
Copy link
Member

Choose a reason for hiding this comment

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

how would you test the feature that you wrote?

end
end

def generate_shortened_url
Copy link
Member

Choose a reason for hiding this comment

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

In most cases, we'd use a random string generator with the length we desire instead of mutating the original address. It's cleaner, easier & more secure.

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.

Thanks!

end

def url_responds_to_http_request
url = URI.parse(address)
Copy link
Member

Choose a reason for hiding this comment

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

Could a bad actor use this logic to DOS another site possibly?

Copy link
Author

@RupertSaxton RupertSaxton Jan 25, 2018

Choose a reason for hiding this comment

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

@jaybobo Yeah, they could. Are there any specific safe practices I could look up? Most of my generic searches just send me to security companies.

@url.errors.full_messages
@url.errors.each { |error| puts error.full_messages }
@url.errors.each do |error|
@url.errors
Copy link

Choose a reason for hiding this comment

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

This file should probably go in .gitignore - no reason for it to be part of the history of the repo.


def generate_shortened_url
url = URI.parse(address)
if url.host.present?
Copy link

Choose a reason for hiding this comment

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

If you can move this check for host presence into a separate validator, then you can avoid having all of this code be nested inside an if-block.

Copy link
Author

Choose a reason for hiding this comment

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

I redid the method per Jay's recommendation of random string generation and let the validators worry about URL validity

while !self.slug && Url.find_by(slug: temp_shortened_host)
count += 1
temp_shortened_host = "#{shortened_host}#{count}"
end
Copy link

Choose a reason for hiding this comment

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

This is going to generate a database query for every pass through the loop. Why not use a LIKE query to grab all of the relevant items, and then find the one with the maximum counter on the end from there?

Copy link
Author

Choose a reason for hiding this comment

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

Good idea! I changed to random string generation and thus made like strings redundant. I did add an index to speed up the comparison and routing

# # (app/controllers/admin/products_controller.rb)
# resources :products
# end
get '/:slug', to: 'urls#show'
Copy link

Choose a reason for hiding this comment

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

I like it.


def create
@url = Url.new(url_params)
@url.click_count = 0
Copy link
Member

Choose a reason for hiding this comment

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

If you put a default value in the database, ActiveRecord will pick it up and you won't need this line.

Copy link
Author

Choose a reason for hiding this comment

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

Good call!

def show
@url = Url.find_by(slug: params[:slug])
@url.click_count += 1
@url.save
Copy link
Member

Choose a reason for hiding this comment

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

ActiveRecord has a method for this sort of thing.

There's a subtle bug as currently written. Do you see it?

Copy link
Author

Choose a reason for hiding this comment

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

Was it that there was no error handling for invalid slugs?

Copy link
Member

Choose a reason for hiding this comment

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

No, concurrent requests for the same slug would undercount the clicks.

Copy link
Author

Choose a reason for hiding this comment

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

Would you recommend locking? Or is there a better way to do this?

Copy link
Member

Choose a reason for hiding this comment

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

Oops. I thought the increment method did an atomic increment in the database. Looking at its implementation now, I see that it does the same thing your code did here: load the value into memory, add one, then save the new value.

A lock would certainly resolve this (and ActiveRecord has a with_lock method to make it easy), but I prefer a SQL update command without a lock.

Copy link
Author

Choose a reason for hiding this comment

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

I discovered the class method :increment_counter which does an atomic increment of the field given the id and have implemented it in my latest commit. Thanks for showing me this as I now have some understanding of concurrency issues and atomicity

Copy link
Member

Choose a reason for hiding this comment

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

Perfect!

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.

4 participants