-
Notifications
You must be signed in to change notification settings - Fork 37
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
[WIP] Convert image links to shortened URLs #546
[WIP] Convert image links to shortened URLs #546
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you're making changes as I review this :)
I'll just post what I have now (some or all are probably fixed now).
Will continue review with separate comments later.
app/services/tinyurl.rb
Outdated
@@ -0,0 +1,6 @@ | |||
class TinyURL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend changing the name of this class to something generic, such as ShortenUrl
. This is in case we need to change to another shortening service in the future.
@@ -35,7 +35,7 @@ | |||
%br | |||
%br | |||
= t('users.show.use_this_image_link_html') | |||
= company_h_brand_url(user, company_id: company.id) | |||
= :hort_company_h_brand_url(user, company.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be short_company_h_brand_url( .... )
@@ -56,8 +56,8 @@ | |||
height_id: 'pom-height') | |||
%br | |||
%br | |||
= t('users.show.use_this_image_link_html') | |||
= proof_of_membership_url(user) | |||
= t('users.show.use_this_image_link_htmlsers.show.use_this_image_link_html') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be:
= t('users.show.use_this_image_link_html')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More comments.
Please note that we'll need unit test(s) for the service class. Use VCR to mock the service response.
Also, will need tests for the 2 additional Company instance method(s) (see comments).
I'll have more comments on code as tests as we proceed, but that's enough for now.
app/services/tinyurl.rb
Outdated
@@ -0,0 +1,6 @@ | |||
class TinyURL | |||
def self.short(url) | |||
HTTParty.get("http://tinyurl.com/api-create.php?url=#{url}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method should rescue any exception(s) that might occur, write something to the log, and return nil
.
It looks like we can just rescue HTTParty::Error
, since all the exceptions inherit from that: https://github.com/jnunemaker/httparty/blob/master/lib/httparty/exceptions.rb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "log" that I refer to should be specific to the URL shortening service.
The log should only contain exceptions - no need to log successful execution.
For an example of logging exception, you could look at log_hips_activity
method in payments_controller.rb
. We use our ActivityLogger
class for this type of logging.
app/helpers/users_helper.rb
Outdated
|
||
def short_company_h_brand_url(user, company_id) | ||
TinyURL.short(company_h_brand_url(user, company_id: company_id)) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two methods should be Company instance methods (hence, defined in the model file).
These methods should first determine if we already have a shortened URL for the link - if so, just return that. If not, call the service class and save the result if not nil
.
Finally, if we don't have a shortened URL for the link (because it's not in the DB and the service call fails), then just return the unshortened URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistent with above comment, we need a migration to add the shortened URL's to the DB.
app/models/company.rb
Outdated
@@ -177,6 +177,12 @@ def se_mailing_csv_str | |||
AddressExporter.se_mailing_csv_str( main_address ) | |||
end | |||
|
|||
def get_or_create_short_h_brand_url(user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lint/UnusedMethodArgument: Unused method argument - user. If it's necessary, use _ or _user as an argument name to indicate that it won't be used. You can also write as get_or_create_short_h_brand_url(*) if you want the method to accept any arguments but don't care about them.
Sorry for late response. I did not have much time this week. For context: TinyURL does not send back many errors. It even doesn't check if url has a dot. I hope that's good way to test it. |
I see now how many things I did wrong. I will have to rewrite some stuff. Before that don't review anything but ShortenUrl. |
app/services/shorten_url.rb
Outdated
|
||
def self.short(url) | ||
response = HTTParty.get("http://tinyurl.com/api-create.php?url=#{url}") | ||
raise HTTParty::Error.new response if response.match? 'ERROR' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm note sure we'll ever see a response that matches that string. You should check the response.code against "success" codes, and raise the exception if it is not.
For example, I forced a failure by sending a bad URI:
response = HTTParty.get("http://tinyurl.com/api-create.ph?url=''")
=> "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" +
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" +
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" +
" <head>\n" +
" <title>404 - Not Found</title>\n" +
" </head>\n" +
" <body>\n" +
" <h1>404 - Not Found</h1>\n" +
" </body>\n" +
"</html>\n"
[4] pry(main)> response.inspect
=> "#<HTTParty::Response:0x7fd4ae91e850 parsed_response=\"<?xml version=\\\"1.0\\\" encoding=\\\"iso-8859-1\\\"?>\\n<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\"\\n \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\\n<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" xml:lang=\\\"en\\\" lang=\\\"en\\\">\\n <head>\\n <title>404 - Not Found</title>\\n </head>\\n <body>\\n <h1>404 - Not Found</h1>\\n </body>\\n</html>\\n\", @response=#<Net::HTTPNotFound 404 Not Found readbody=true>, @headers={\"date\"=>[\"Sat, 14 Jul 2018 18:03:21 GMT\"], \"content-type\"=>[\"text/html\"], \"transfer-encoding\"=>[\"chunked\"], \"connection\"=>[\"close\"], \"set-cookie\"=>[\"__cfduid=d071b4ebad4a29a20fd2ba0b0f2cd456a1531591401; expires=Sun, 14-Jul-19 18:03:21 GMT; path=/; domain=.tinyurl.com; HttpOnly\"], \"server\"=>[\"cloudflare\"], \"cf-ray\"=>[\"43a5e7d1967f2180-EWR\"]}>"
[5] pry(main)>
[6] pry(main)> response.code
=> 404
[7] pry(main)> response.message
=> "Not Found"
In hips_service.rb
, for example, we do this:
SUCCESS_CODES = [200, 201, 202].freeze
.
.
.
parsed_response = response.parsed_response
return parsed_response if SUCCESS_CODES.include?(response.code)
.
<handle HTTP error>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you don't need the .new
- the exception will be created for you when you raise
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pry(main)> response = HTTParty.get("http://tinyurl.com/api-create.php?url=''")
=> "http://tinyurl.com/6mfcgml"
You lost 'p' in 'php' before '?'. The two only errors I know of look like this.
[10] pry(main)> response = HTTParty.get("http://tinyurl.com/api-create.php?url=/")
=> "ERROR"
[11] pry(main)> response.code
=> 200
[12] pry(main)> response = HTTParty.get("http://tinyurl.com/api-create.php?url=")
=> "Error"
[13] pry(main)> response.code
=> 400
Second one I found out about a moment ago. I will change match to /error/i
I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that will work. What I was trying to get at is the situation where the website is down and/or the request times out - in that case we won't get an "Error" from the site, but we should get a response.code other than 200
.
So, maybe we should check for response matching /error/i
or response.code != 200
.
app/models/company.rb
Outdated
@@ -177,6 +179,19 @@ def se_mailing_csv_str | |||
AddressExporter.se_mailing_csv_str( main_address ) | |||
end | |||
|
|||
def get_or_create_short_h_brand_url(user_id=0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
We should not use a
user_id
argument, since that would imply that we would be OK with calling this method with an actualuser.id
- which we do not want to happen since we would then be creating different URLs for the same company for different users - which is not what we want to do. -
The caller of this method should not care whether or not we have to create the shortened URL or just retrieve it from the DB. So, maybe the name should just be
get_short_h_brand_url
.
app/models/company.rb
Outdated
def get_or_create_short_h_brand_url(user_id=0) | ||
found = self.short_h_brand_url | ||
return found unless found.nil? | ||
url = company_h_brand_url(user_id, company_id: self.id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass 0
as the user_id.
Note: this is an argument for moving the controller action for accessing the h-brand image from the users
controller to the companies
controller. When I first created that action I was not sure whether the company's h-brand image would contain anything specific to the user - which, of course, it doesn't. I'll create a story to make that change (this note does not affect this PR, however).
app/models/company.rb
Outdated
return found unless found.nil? | ||
url = company_h_brand_url(user_id, company_id: self.id) | ||
short_url = ShortenUrl.short(url) | ||
unless short_url.nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be simplified as
if short_url
app/models/user.rb
Outdated
@@ -117,6 +119,19 @@ def grant_membership(send_email: true) | |||
Arel.sql("lpad(membership_number, 20, '0')") | |||
end | |||
|
|||
def get_or_create_short_proof_of_membership_url |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comments as above.
app/services/shorten_url.rb
Outdated
log.record('error', error.message) | ||
end | ||
nil | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're raising a Runtime
error and then rescuing any exception.
-
I'd rather raise an
HTTParty::Error
since that is what we have - an error code and/or body message has been returned that indicates an error. -
In general, one should not rescue very broad categories of exceptions. Otherwise, we could be handling an exception that has nothing to do with this method (happening down in the stack).
-
I'd like to see more information logged - that's so important in helping us figure out problems in production.
I altered this code to:
def self.short(url)
response = HTTParty.get("http://tinyurl.com/api-create.php?url=#{url}")
raise HTTParty::Error if response.match?(/error/i) || !SUCCESS_CODES.include?(response.code)
response
rescue HTTParty::Error => error
ActivityLogger.open(SHORTEN_URL_LOG, 'TINYURL_API', 'shortening url', false) do |log|
log.record('error', "Exception: #{error.message}")
log.record('error', "Attempted URL: #{url}")
log.record('error', "Response body: #{response.body}")
log.record('error', "HTTP code: #{response.code}")
end
nil
end
I then called the method with: ShortenUrl.short('')
and ShortenUrl.short('/')
and this what I got in the log:
# Logfile created on 2018-07-20 07:42:14 -0400 by logger.rb/61378
[TINYURL_API] [shortening url] [info] Started at 2018-07-20 11:42:14 UTC
[TINYURL_API] [shortening url] [error] Exception: HTTParty::Error
[TINYURL_API] [shortening url] [error] Attempted URL:
[TINYURL_API] [shortening url] [error] Response body: Error
[TINYURL_API] [shortening url] [error] HTTP code: 400
[TINYURL_API] [shortening url] [info] Finished at 2018-07-20 11:42:14 UTC.
[TINYURL_API] [shortening url] [info] Duration: 0.0 seconds.
[TINYURL_API] [shortening url] [info] Started at 2018-07-20 11:42:44 UTC
[TINYURL_API] [shortening url] [error] Exception: HTTParty::Error
[TINYURL_API] [shortening url] [error] Attempted URL: /
[TINYURL_API] [shortening url] [error] Response body: ERROR
[TINYURL_API] [shortening url] [error] HTTP code: 200
[TINYURL_API] [shortening url] [info] Finished at 2018-07-20 11:42:44 UTC.
[TINYURL_API] [shortening url] [info] Duration: 0.0 seconds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this approach allows us to capture more information for HTTParty-specific errors.
For other exceptions, we could follow this with a second rescue clause, picking up other exceptions such as the one you have now.
That way we rescue all exceptions, but we maximize the information we can get from HTTParty exceptions.
config/initializers/shorten_url.rb
Outdated
@@ -0,0 +1 @@ | |||
TINYURL_LOG = 'log/shorten_url.log' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not used anymore(?)
Your spec error is due to the need for a separate VCR cassette that contains the error response. I changed the test to this:
That is, I specified a second cassette for the error response. (I also specified the entire URL for the successful test - might as well since that should be the same URL every time). BTW, these are good tests - I especially like the confirmation of opening the log file. |
app/services/shorten_url.rb
Outdated
rescue HTTParty::Error => error | ||
ActivityLogger.open(SHORTEN_URL_LOG, 'TINYURL_API', 'shortening url', false) do |log| | ||
log.record('error', "Exception: #{error.message}") | ||
log.record('error', "Attempted URL: #{url}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more change requested (I missed this before) - we should handle the situation that occurs if response
is nil.
This is because response
will be nil
if HTTParty raises the exception. If we raise it, then it will not be nil
.
Something like:
log.record('error', "Exception: #{error.message}")
if response
log.record('error', "Attempted URL: #{url}")
log.record('error', "Response body: #{response.body}")
log.record('error', "HTTP code: #{response.code}")
else
log.record('error', 'Exception raised by HTTParty')
end
…tps://github.com/tmszrbn/shf-project into convert-images-links-to-shortened-urls#157840742
@tmszrbn - For some reason, in the test environment, the default locale is not being reset back to 'sv' before each test. And, after a specific test(*) changes the locale to 'en', the router is then adding 'en' to all other routes (which means URL's now look like " .... /en/en/ ....", and this breaks subsequent tests. (*the test is: The tests work if line 64 in
I think this problem is limited to the test environment. However, I'm not sure, and I am also very nervous about setting default routing parameters - in dev and production - just for the sake of this one feature. It seems too intrusive and risky, and I am not certain we won't break something in production. In other words, I don't understand it enough in order to intelligently assess the risks and possible side effects (such as breaking tests as we've seen). So, I think we should restructure this to avoid using Rails url settings, and helper methods, outside of the view/controller environment. One way would revert back to your original idea of using view helper(s) method to initiate convert the regular URL to a shortened URL. That might look like:
Your basically have the code described above already built - we just need to place a helper method in then mix in order to prevent the need to pull in URL helpers (and set config params) as the model level. Of course, this was your first approach and I asked for a change that removed the helper method. So, my mistake. However, this is typical with how a lot of development proceeds - start to go down a path and, as you discover problems and risks, back up and adjust as needed. |
PT Story: https://www.pivotaltracker.com/story/show/157840742
Changes proposed in this pull request:
At the moment I do it only for
company_h_brand_url
just to keep it simpler when I don't know how can it be done for localhost.Cucumber tests fail but I didn't work on them yet - there is 28 of them and they are independent from my changes in
_company_h_brand.html.haml
andusers_helper.rb
.Ready for review:
@