[{"data":1,"prerenderedAt":2530},["ShallowReactive",2],{"post:\u002F2021\u002F01\u002F14\u002Flink-shortener-part-2":3},{"id":4,"title":5,"body":6,"date":2522,"description":15,"extension":2523,"meta":2524,"navigation":82,"path":2525,"seo":2526,"stem":2527,"subtitle":2528,"__hash__":2529},"posts\u002F2021\u002F01\u002F14\u002Flink-shortener-part-2.md","URL shortener - Part 2",{"type":7,"value":8,"toc":2520},"minimark",[9,17,20,109,112,130,133,136,848,851,1038,1045,1099,1102,1130,1141,1144,1224,1227,1234,1237,1244,1259,1442,1445,1456,1459,1567,1579,1589,1592,1607,1610,1627,1630,1666,1669,1702,1705,1745,1748,1751,1768,1771,1910,1913,1967,1974,1977,2011,2014,2017,2020,2023,2028,2031,2049,2052,2067,2070,2089,2100,2151,2154,2468,2471,2476,2479,2484,2486,2491,2494,2497,2513,2516],[10,11,12],"p",{},[13,14],"img",{"alt":15,"src":16},"","https:\u002F\u002Fmybigplunge.com\u002Fwp-content\u002Fuploads\u002F2018\u002F04\u002Furl-shortener-theplungedaily.jpg",[10,18,19],{},"Last time we left off with a bunch of failing specs, so lets begin. Our most pressing issue right now is that we're trying to test our requests against routes that don't exist. So that's a good place to start.",[21,22,26],"pre",{"className":23,"code":24,"language":25,"meta":15,"style":15},"language-ruby shiki shiki-themes github-dark","Rails.application.routes.draw do\n  resources :links\n  resources :redirect\n\n  get '\u002Fs\u002F:slug', to: 'redirects#index'\nend\n","ruby",[27,28,29,60,69,77,84,103],"code",{"__ignoreMap":15},[30,31,34,38,42,46,48,51,53,56],"span",{"class":32,"line":33},"line",1,[30,35,37],{"class":36},"sDLfK","Rails",[30,39,41],{"class":40},"s95oV",".",[30,43,45],{"class":44},"svObZ","application",[30,47,41],{"class":40},[30,49,50],{"class":44},"routes",[30,52,41],{"class":40},[30,54,55],{"class":44},"draw",[30,57,59],{"class":58},"snl16"," do\n",[30,61,63,66],{"class":32,"line":62},2,[30,64,65],{"class":40},"  resources ",[30,67,68],{"class":36},":links\n",[30,70,72,74],{"class":32,"line":71},3,[30,73,65],{"class":40},[30,75,76],{"class":36},":redirect\n",[30,78,80],{"class":32,"line":79},4,[30,81,83],{"emptyLinePlaceholder":82},true,"\n",[30,85,87,90,94,97,100],{"class":32,"line":86},5,[30,88,89],{"class":40},"  get ",[30,91,93],{"class":92},"sU2Wk","'\u002Fs\u002F:slug'",[30,95,96],{"class":40},", ",[30,98,99],{"class":36},"to:",[30,101,102],{"class":92}," 'redirects#index'\n",[30,104,106],{"class":32,"line":105},6,[30,107,108],{"class":58},"end\n",[10,110,111],{},"Now we can take a look at our new routes",[21,113,117],{"className":114,"code":115,"language":116,"meta":15,"style":15},"language-shell shiki shiki-themes github-dark","$ rails routes\n","shell",[27,118,119],{"__ignoreMap":15},[30,120,121,124,127],{"class":32,"line":33},[30,122,123],{"class":44},"$",[30,125,126],{"class":92}," rails",[30,128,129],{"class":92}," routes\n",[10,131,132],{},"Just beautiful. Now we have a list of all of the endpoints available to us. Not that they do anything mind you, but patience is a virtue.",[10,134,135],{},"...A virtue I don't have, here's the links controller",[21,137,139],{"className":23,"code":138,"language":25,"meta":15,"style":15},"class LinksController \u003C ApplicationController\n  before_action :set_link, only: %i[show update destroy show]\n\n  def index\n    @links = Link.all\n    json_response(@links)\n  end\n\n  def show\n    json_response(@link, :ok)\n  end\n\n  def create\n    @link = Link.new(link_params)\n    @link.update_attribute(:slug, generate_slug(link_params[:slug]))\n\n    return json_response(@link, :created) if @link.save\n\n    json_response({ message: @link.errors.first }, :unprocessable_entity)\n  end\n\n  def update\n    @link.update(link_params.except(:slug))\n    @link.update_attribute(:slug, generate_slug(link_params[:slug])) if link_params[:slug]\n\n    return json_response(@link, :ok) if @link.save\n\n    json_response({ message: @link.errors.full_messages }, 422)\n  end\n\n  def destroy\n    @link.delete\n    json_response({ message: Message.deleted_link }, :ok)\n  end\n\n  private\n\n  def set_link\n    @link = Link.find(link_params[:id])\n\n    json_response({ message: Message.not_found('link') }, :not_found) unless @link.present?\n  end\n\n  def link_params\n    params.permit(:slug, :url, :id)\n  end\n\n  def generate_slug(slug_param = nil)\n    slug = slug_param || SecureRandom.uuid[0..5]\n\n    # if slug is taken add random characters to the end\n    Link.find_by(slug: slug) ? slug.concat(SecureRandom.uuid[0..2]) : slug\n\n    # if slug is still taken call self again\n    Link.find_by(slug: slug) ? generate_slug(slug_param) : slug\n  end\nend\n",[27,140,141,155,171,175,183,199,207,213,218,226,240,245,250,258,276,304,309,335,340,369,374,379,387,408,440,445,464,469,494,499,504,512,520,543,548,553,559,564,572,594,599,636,641,646,654,678,683,688,706,744,749,756,805,810,816,838,843],{"__ignoreMap":15},[30,142,143,146,149,152],{"class":32,"line":33},[30,144,145],{"class":58},"class",[30,147,148],{"class":44}," LinksController",[30,150,151],{"class":40}," \u003C ",[30,153,154],{"class":44},"ApplicationController\n",[30,156,157,160,163,165,168],{"class":32,"line":62},[30,158,159],{"class":40},"  before_action ",[30,161,162],{"class":36},":set_link",[30,164,96],{"class":40},[30,166,167],{"class":36},"only:",[30,169,170],{"class":36}," %i[show update destroy show]\n",[30,172,173],{"class":32,"line":71},[30,174,83],{"emptyLinePlaceholder":82},[30,176,177,180],{"class":32,"line":79},[30,178,179],{"class":58},"  def",[30,181,182],{"class":44}," index\n",[30,184,185,188,191,194,196],{"class":32,"line":86},[30,186,187],{"class":40},"    @links ",[30,189,190],{"class":58},"=",[30,192,193],{"class":36}," Link",[30,195,41],{"class":40},[30,197,198],{"class":44},"all\n",[30,200,201,204],{"class":32,"line":105},[30,202,203],{"class":44},"    json_response",[30,205,206],{"class":40},"(@links)\n",[30,208,210],{"class":32,"line":209},7,[30,211,212],{"class":58},"  end\n",[30,214,216],{"class":32,"line":215},8,[30,217,83],{"emptyLinePlaceholder":82},[30,219,221,223],{"class":32,"line":220},9,[30,222,179],{"class":58},[30,224,225],{"class":44}," show\n",[30,227,229,231,234,237],{"class":32,"line":228},10,[30,230,203],{"class":44},[30,232,233],{"class":40},"(@link, ",[30,235,236],{"class":36},":ok",[30,238,239],{"class":40},")\n",[30,241,243],{"class":32,"line":242},11,[30,244,212],{"class":58},[30,246,248],{"class":32,"line":247},12,[30,249,83],{"emptyLinePlaceholder":82},[30,251,253,255],{"class":32,"line":252},13,[30,254,179],{"class":58},[30,256,257],{"class":44}," create\n",[30,259,261,264,266,268,270,273],{"class":32,"line":260},14,[30,262,263],{"class":40},"    @link ",[30,265,190],{"class":58},[30,267,193],{"class":36},[30,269,41],{"class":40},[30,271,272],{"class":58},"new",[30,274,275],{"class":40},"(link_params)\n",[30,277,279,282,285,288,291,293,296,299,301],{"class":32,"line":278},15,[30,280,281],{"class":40},"    @link.",[30,283,284],{"class":44},"update_attribute",[30,286,287],{"class":40},"(",[30,289,290],{"class":36},":slug",[30,292,96],{"class":40},[30,294,295],{"class":44},"generate_slug",[30,297,298],{"class":40},"(link_params[",[30,300,290],{"class":36},[30,302,303],{"class":40},"]))\n",[30,305,307],{"class":32,"line":306},16,[30,308,83],{"emptyLinePlaceholder":82},[30,310,312,315,318,320,323,326,329,332],{"class":32,"line":311},17,[30,313,314],{"class":58},"    return",[30,316,317],{"class":44}," json_response",[30,319,233],{"class":40},[30,321,322],{"class":36},":created",[30,324,325],{"class":40},") ",[30,327,328],{"class":58},"if",[30,330,331],{"class":40}," @link.",[30,333,334],{"class":44},"save\n",[30,336,338],{"class":32,"line":337},18,[30,339,83],{"emptyLinePlaceholder":82},[30,341,343,345,348,351,353,356,358,361,364,367],{"class":32,"line":342},19,[30,344,203],{"class":44},[30,346,347],{"class":40},"({ ",[30,349,350],{"class":36},"message:",[30,352,331],{"class":40},[30,354,355],{"class":44},"errors",[30,357,41],{"class":40},[30,359,360],{"class":44},"first",[30,362,363],{"class":40}," }, ",[30,365,366],{"class":36},":unprocessable_entity",[30,368,239],{"class":40},[30,370,372],{"class":32,"line":371},20,[30,373,212],{"class":58},[30,375,377],{"class":32,"line":376},21,[30,378,83],{"emptyLinePlaceholder":82},[30,380,382,384],{"class":32,"line":381},22,[30,383,179],{"class":58},[30,385,386],{"class":44}," update\n",[30,388,390,392,395,398,401,403,405],{"class":32,"line":389},23,[30,391,281],{"class":40},[30,393,394],{"class":44},"update",[30,396,397],{"class":40},"(link_params.",[30,399,400],{"class":44},"except",[30,402,287],{"class":40},[30,404,290],{"class":36},[30,406,407],{"class":40},"))\n",[30,409,411,413,415,417,419,421,423,425,427,430,432,435,437],{"class":32,"line":410},24,[30,412,281],{"class":40},[30,414,284],{"class":44},[30,416,287],{"class":40},[30,418,290],{"class":36},[30,420,96],{"class":40},[30,422,295],{"class":44},[30,424,298],{"class":40},[30,426,290],{"class":36},[30,428,429],{"class":40},"])) ",[30,431,328],{"class":58},[30,433,434],{"class":40}," link_params[",[30,436,290],{"class":36},[30,438,439],{"class":40},"]\n",[30,441,443],{"class":32,"line":442},25,[30,444,83],{"emptyLinePlaceholder":82},[30,446,448,450,452,454,456,458,460,462],{"class":32,"line":447},26,[30,449,314],{"class":58},[30,451,317],{"class":44},[30,453,233],{"class":40},[30,455,236],{"class":36},[30,457,325],{"class":40},[30,459,328],{"class":58},[30,461,331],{"class":40},[30,463,334],{"class":44},[30,465,467],{"class":32,"line":466},27,[30,468,83],{"emptyLinePlaceholder":82},[30,470,472,474,476,478,480,482,484,487,489,492],{"class":32,"line":471},28,[30,473,203],{"class":44},[30,475,347],{"class":40},[30,477,350],{"class":36},[30,479,331],{"class":40},[30,481,355],{"class":44},[30,483,41],{"class":40},[30,485,486],{"class":44},"full_messages",[30,488,363],{"class":40},[30,490,491],{"class":36},"422",[30,493,239],{"class":40},[30,495,497],{"class":32,"line":496},29,[30,498,212],{"class":58},[30,500,502],{"class":32,"line":501},30,[30,503,83],{"emptyLinePlaceholder":82},[30,505,507,509],{"class":32,"line":506},31,[30,508,179],{"class":58},[30,510,511],{"class":44}," destroy\n",[30,513,515,517],{"class":32,"line":514},32,[30,516,281],{"class":40},[30,518,519],{"class":44},"delete\n",[30,521,523,525,527,529,532,534,537,539,541],{"class":32,"line":522},33,[30,524,203],{"class":44},[30,526,347],{"class":40},[30,528,350],{"class":36},[30,530,531],{"class":36}," Message",[30,533,41],{"class":40},[30,535,536],{"class":44},"deleted_link",[30,538,363],{"class":40},[30,540,236],{"class":36},[30,542,239],{"class":40},[30,544,546],{"class":32,"line":545},34,[30,547,212],{"class":58},[30,549,551],{"class":32,"line":550},35,[30,552,83],{"emptyLinePlaceholder":82},[30,554,556],{"class":32,"line":555},36,[30,557,558],{"class":58},"  private\n",[30,560,562],{"class":32,"line":561},37,[30,563,83],{"emptyLinePlaceholder":82},[30,565,567,569],{"class":32,"line":566},38,[30,568,179],{"class":58},[30,570,571],{"class":44}," set_link\n",[30,573,575,577,579,581,583,586,588,591],{"class":32,"line":574},39,[30,576,263],{"class":40},[30,578,190],{"class":58},[30,580,193],{"class":36},[30,582,41],{"class":40},[30,584,585],{"class":44},"find",[30,587,298],{"class":40},[30,589,590],{"class":36},":id",[30,592,593],{"class":40},"])\n",[30,595,597],{"class":32,"line":596},40,[30,598,83],{"emptyLinePlaceholder":82},[30,600,602,604,606,608,610,612,615,617,620,623,626,628,631,633],{"class":32,"line":601},41,[30,603,203],{"class":44},[30,605,347],{"class":40},[30,607,350],{"class":36},[30,609,531],{"class":36},[30,611,41],{"class":40},[30,613,614],{"class":44},"not_found",[30,616,287],{"class":40},[30,618,619],{"class":92},"'link'",[30,621,622],{"class":40},") }, ",[30,624,625],{"class":36},":not_found",[30,627,325],{"class":40},[30,629,630],{"class":58},"unless",[30,632,331],{"class":40},[30,634,635],{"class":44},"present?\n",[30,637,639],{"class":32,"line":638},42,[30,640,212],{"class":58},[30,642,644],{"class":32,"line":643},43,[30,645,83],{"emptyLinePlaceholder":82},[30,647,649,651],{"class":32,"line":648},44,[30,650,179],{"class":58},[30,652,653],{"class":44}," link_params\n",[30,655,657,660,663,665,667,669,672,674,676],{"class":32,"line":656},45,[30,658,659],{"class":40},"    params.",[30,661,662],{"class":44},"permit",[30,664,287],{"class":40},[30,666,290],{"class":36},[30,668,96],{"class":40},[30,670,671],{"class":36},":url",[30,673,96],{"class":40},[30,675,590],{"class":36},[30,677,239],{"class":40},[30,679,681],{"class":32,"line":680},46,[30,682,212],{"class":58},[30,684,686],{"class":32,"line":685},47,[30,687,83],{"emptyLinePlaceholder":82},[30,689,691,693,696,699,701,704],{"class":32,"line":690},48,[30,692,179],{"class":58},[30,694,695],{"class":44}," generate_slug",[30,697,698],{"class":40},"(slug_param ",[30,700,190],{"class":58},[30,702,703],{"class":36}," nil",[30,705,239],{"class":40},[30,707,709,713,716,719,722,725,727,730,733,736,739,742],{"class":32,"line":708},49,[30,710,712],{"class":711},"s9osk","    slug",[30,714,715],{"class":58}," =",[30,717,718],{"class":40}," slug_param ",[30,720,721],{"class":58},"||",[30,723,724],{"class":36}," SecureRandom",[30,726,41],{"class":40},[30,728,729],{"class":44},"uuid",[30,731,732],{"class":40},"[",[30,734,735],{"class":36},"0",[30,737,738],{"class":40},"..",[30,740,741],{"class":36},"5",[30,743,439],{"class":40},[30,745,747],{"class":32,"line":746},50,[30,748,83],{"emptyLinePlaceholder":82},[30,750,752],{"class":32,"line":751},51,[30,753,755],{"class":754},"sAwPA","    # if slug is taken add random characters to the end\n",[30,757,759,762,764,767,769,772,775,778,781,784,786,789,791,793,795,797,799,802],{"class":32,"line":758},52,[30,760,761],{"class":36},"    Link",[30,763,41],{"class":40},[30,765,766],{"class":44},"find_by",[30,768,287],{"class":40},[30,770,771],{"class":36},"slug:",[30,773,774],{"class":40}," slug) ",[30,776,777],{"class":58},"?",[30,779,780],{"class":40}," slug.",[30,782,783],{"class":44},"concat",[30,785,287],{"class":40},[30,787,788],{"class":36},"SecureRandom",[30,790,41],{"class":40},[30,792,729],{"class":44},[30,794,732],{"class":40},[30,796,735],{"class":36},[30,798,738],{"class":40},[30,800,801],{"class":36},"2",[30,803,804],{"class":40},"]) : slug\n",[30,806,808],{"class":32,"line":807},53,[30,809,83],{"emptyLinePlaceholder":82},[30,811,813],{"class":32,"line":812},54,[30,814,815],{"class":754},"    # if slug is still taken call self again\n",[30,817,819,821,823,825,827,829,831,833,835],{"class":32,"line":818},55,[30,820,761],{"class":36},[30,822,41],{"class":40},[30,824,766],{"class":44},[30,826,287],{"class":40},[30,828,771],{"class":36},[30,830,774],{"class":40},[30,832,777],{"class":58},[30,834,695],{"class":44},[30,836,837],{"class":40},"(slug_param) : slug\n",[30,839,841],{"class":32,"line":840},56,[30,842,212],{"class":58},[30,844,846],{"class":32,"line":845},57,[30,847,108],{"class":58},[10,849,850],{},"and redirects",[21,852,854],{"className":23,"code":853,"language":25,"meta":15,"style":15},"  class RedirectsController \u003C ApplicationController\n    before_action :set_link\n\n    def index\n      @link.update_attribute(:clicked, @link.clicked + 1)\n      redirect_to @link.url\n    end\n\n    private\n\n    def set_link\n      @link = Link.find_by(slug: link_params[:slug])\n\n      json_response({ message: Message.not_found('link') }, :not_found) unless @link.present?\n    end\n\n    def link_params\n      params.permit(:slug)\n    end\n  end\n",[27,855,856,868,876,880,887,913,921,926,930,935,939,945,968,972,1003,1007,1011,1017,1030,1034],{"__ignoreMap":15},[30,857,858,861,864,866],{"class":32,"line":33},[30,859,860],{"class":58},"  class",[30,862,863],{"class":44}," RedirectsController",[30,865,151],{"class":40},[30,867,154],{"class":44},[30,869,870,873],{"class":32,"line":62},[30,871,872],{"class":40},"    before_action ",[30,874,875],{"class":36},":set_link\n",[30,877,878],{"class":32,"line":71},[30,879,83],{"emptyLinePlaceholder":82},[30,881,882,885],{"class":32,"line":79},[30,883,884],{"class":58},"    def",[30,886,182],{"class":44},[30,888,889,892,894,896,899,902,905,908,911],{"class":32,"line":86},[30,890,891],{"class":40},"      @link.",[30,893,284],{"class":44},[30,895,287],{"class":40},[30,897,898],{"class":36},":clicked",[30,900,901],{"class":40},", @link.",[30,903,904],{"class":44},"clicked",[30,906,907],{"class":58}," +",[30,909,910],{"class":36}," 1",[30,912,239],{"class":40},[30,914,915,918],{"class":32,"line":105},[30,916,917],{"class":40},"      redirect_to @link.",[30,919,920],{"class":44},"url\n",[30,922,923],{"class":32,"line":209},[30,924,925],{"class":58},"    end\n",[30,927,928],{"class":32,"line":215},[30,929,83],{"emptyLinePlaceholder":82},[30,931,932],{"class":32,"line":220},[30,933,934],{"class":58},"    private\n",[30,936,937],{"class":32,"line":228},[30,938,83],{"emptyLinePlaceholder":82},[30,940,941,943],{"class":32,"line":242},[30,942,884],{"class":58},[30,944,571],{"class":44},[30,946,947,950,952,954,956,958,960,962,964,966],{"class":32,"line":247},[30,948,949],{"class":40},"      @link ",[30,951,190],{"class":58},[30,953,193],{"class":36},[30,955,41],{"class":40},[30,957,766],{"class":44},[30,959,287],{"class":40},[30,961,771],{"class":36},[30,963,434],{"class":40},[30,965,290],{"class":36},[30,967,593],{"class":40},[30,969,970],{"class":32,"line":252},[30,971,83],{"emptyLinePlaceholder":82},[30,973,974,977,979,981,983,985,987,989,991,993,995,997,999,1001],{"class":32,"line":260},[30,975,976],{"class":44},"      json_response",[30,978,347],{"class":40},[30,980,350],{"class":36},[30,982,531],{"class":36},[30,984,41],{"class":40},[30,986,614],{"class":44},[30,988,287],{"class":40},[30,990,619],{"class":92},[30,992,622],{"class":40},[30,994,625],{"class":36},[30,996,325],{"class":40},[30,998,630],{"class":58},[30,1000,331],{"class":40},[30,1002,635],{"class":44},[30,1004,1005],{"class":32,"line":278},[30,1006,925],{"class":58},[30,1008,1009],{"class":32,"line":306},[30,1010,83],{"emptyLinePlaceholder":82},[30,1012,1013,1015],{"class":32,"line":311},[30,1014,884],{"class":58},[30,1016,653],{"class":44},[30,1018,1019,1022,1024,1026,1028],{"class":32,"line":337},[30,1020,1021],{"class":40},"      params.",[30,1023,662],{"class":44},[30,1025,287],{"class":40},[30,1027,290],{"class":36},[30,1029,239],{"class":40},[30,1031,1032],{"class":32,"line":342},[30,1033,925],{"class":58},[30,1035,1036],{"class":32,"line":371},[30,1037,212],{"class":58},[10,1039,1040,1041,1044],{},"Notice the next helper ",[27,1042,1043],{},"json_response",". This nifty little method will do just what it says on the tin. It will send a json response containing a message and a response code.",[21,1046,1048],{"className":23,"code":1047,"language":25,"meta":15,"style":15},"module Response\n  def json_response(object, status = :ok)\n    render json: object, status: status\n  end\nend\n",[27,1049,1050,1058,1074,1091,1095],{"__ignoreMap":15},[30,1051,1052,1055],{"class":32,"line":33},[30,1053,1054],{"class":58},"module",[30,1056,1057],{"class":44}," Response\n",[30,1059,1060,1062,1064,1067,1069,1072],{"class":32,"line":62},[30,1061,179],{"class":58},[30,1063,317],{"class":44},[30,1065,1066],{"class":40},"(object, status ",[30,1068,190],{"class":58},[30,1070,1071],{"class":36}," :ok",[30,1073,239],{"class":40},[30,1075,1076,1079,1082,1085,1088],{"class":32,"line":71},[30,1077,1078],{"class":40},"    render ",[30,1080,1081],{"class":36},"json:",[30,1083,1084],{"class":40}," object, ",[30,1086,1087],{"class":36},"status:",[30,1089,1090],{"class":40}," status\n",[30,1092,1093],{"class":32,"line":79},[30,1094,212],{"class":58},[30,1096,1097],{"class":32,"line":86},[30,1098,108],{"class":58},[10,1100,1101],{},"and finally reference this in our application controller so we can access it in our controllers",[21,1103,1105],{"className":23,"code":1104,"language":25,"meta":15,"style":15},"class ApplicationController \u003C ActionController::API\n  include Response\nend\n",[27,1106,1107,1119,1126],{"__ignoreMap":15},[30,1108,1109,1111,1114,1116],{"class":32,"line":33},[30,1110,145],{"class":58},[30,1112,1113],{"class":44}," ApplicationController",[30,1115,151],{"class":40},[30,1117,1118],{"class":44},"ActionController::API\n",[30,1120,1121,1124],{"class":32,"line":62},[30,1122,1123],{"class":58},"  include",[30,1125,1057],{"class":36},[30,1127,1128],{"class":32,"line":71},[30,1129,108],{"class":58},[10,1131,1132,1133,1140],{},"Lets see what this puppy can do, I'm using a handy little tool called ",[1134,1135,1139],"a",{"href":1136,"rel":1137},"https:\u002F\u002Fhttpie.io\u002F",[1138],"nofollow","httpie"," to make requests. with zero effort. I will happily say goodbye to curl.",[10,1142,1143],{},"now that's just cool. The rest of the endpoints should work too.",[21,1145,1147],{"className":114,"code":1146,"language":116,"meta":15,"style":15},"# GET \u002Flinks\n$ http :3000\u002Flinks\n# POST \u002Flinks\n$ http POST :3000\u002Flinks url=google.ie slug=google\n# PUT \u002Flinks\u002F:link_id\n$ http PUT :3000\u002Flinks\u002F1 url=google.com\n# DELETE \u002Flinks\u002F:link_id\n$ http DELETE :3000\u002Flinks\u002F1\n",[27,1148,1149,1154,1164,1169,1187,1192,1207,1212],{"__ignoreMap":15},[30,1150,1151],{"class":32,"line":33},[30,1152,1153],{"class":754},"# GET \u002Flinks\n",[30,1155,1156,1158,1161],{"class":32,"line":62},[30,1157,123],{"class":44},[30,1159,1160],{"class":92}," http",[30,1162,1163],{"class":92}," :3000\u002Flinks\n",[30,1165,1166],{"class":32,"line":71},[30,1167,1168],{"class":754},"# POST \u002Flinks\n",[30,1170,1171,1173,1175,1178,1181,1184],{"class":32,"line":79},[30,1172,123],{"class":44},[30,1174,1160],{"class":92},[30,1176,1177],{"class":92}," POST",[30,1179,1180],{"class":92}," :3000\u002Flinks",[30,1182,1183],{"class":92}," url=google.ie",[30,1185,1186],{"class":92}," slug=google\n",[30,1188,1189],{"class":32,"line":86},[30,1190,1191],{"class":754},"# PUT \u002Flinks\u002F:link_id\n",[30,1193,1194,1196,1198,1201,1204],{"class":32,"line":105},[30,1195,123],{"class":44},[30,1197,1160],{"class":92},[30,1199,1200],{"class":92}," PUT",[30,1202,1203],{"class":92}," :3000\u002Flinks\u002F1",[30,1205,1206],{"class":92}," url=google.com\n",[30,1208,1209],{"class":32,"line":209},[30,1210,1211],{"class":754},"# DELETE \u002Flinks\u002F:link_id\n",[30,1213,1214,1216,1218,1221],{"class":32,"line":215},[30,1215,123],{"class":44},[30,1217,1160],{"class":92},[30,1219,1220],{"class":92}," DELETE",[30,1222,1223],{"class":92}," :3000\u002Flinks\u002F1\n",[10,1225,1226],{},"with our endpoints looking fly, we're almost done. One very important thing we need to consider is versioning. Our controller works great, but do did horses in the 1800s. We can make a pretty safe bet that in the future we'll want to try something new. And changing our api endpoints could break functionality for other people using our api. Lets talk versioning.",[10,1228,1229,1230,1233],{},"A great way to make sure we always have room to expand our functionality without removing what's currently there is versioning. Lets say our current api is ",[27,1231,1232],{},"v1"," and make it the default version. Then we can create a second version, and only use it when we ask for it.",[10,1235,1236],{},"lets give it a go.",[10,1238,1239,1240,1243],{},"lets create a class called ",[27,1241,1242],{},"ApiVersion"," that will check requests coming in for the version, and act accordingly.",[21,1245,1247],{"className":114,"code":1246,"language":116,"meta":15,"style":15},"$ touch app\u002Flib\u002Fapi_version.rb\n",[27,1248,1249],{"__ignoreMap":15},[30,1250,1251,1253,1256],{"class":32,"line":33},[30,1252,123],{"class":44},[30,1254,1255],{"class":92}," touch",[30,1257,1258],{"class":92}," app\u002Flib\u002Fapi_version.rb\n",[21,1260,1262],{"className":23,"code":1261,"language":25,"meta":15,"style":15},"class ApiVersion\n  attr_reader :version, :default\n\n  def initialize(version, default = false)\n    @version = version\n    @default = default\n  end\n\n  # check whether version is specified or is default\n  def matches?(request)\n    check_headers(request.headers) || default\n  end\n\n  private\n\n  def check_headers(headers)\n    # check version from Accept headers; expect custom media type `links`\n    accept = headers[:accept]\n    accept&.include?(\"application\u002Fvnd.links.#{version}+json\")\n  end\nend\n\n",[27,1263,1264,1271,1284,1288,1305,1315,1325,1329,1333,1338,1348,1365,1369,1373,1377,1381,1391,1396,1411,1434,1438],{"__ignoreMap":15},[30,1265,1266,1268],{"class":32,"line":33},[30,1267,145],{"class":58},[30,1269,1270],{"class":44}," ApiVersion\n",[30,1272,1273,1276,1279,1281],{"class":32,"line":62},[30,1274,1275],{"class":58},"  attr_reader",[30,1277,1278],{"class":36}," :version",[30,1280,96],{"class":40},[30,1282,1283],{"class":36},":default\n",[30,1285,1286],{"class":32,"line":71},[30,1287,83],{"emptyLinePlaceholder":82},[30,1289,1290,1292,1295,1298,1300,1303],{"class":32,"line":79},[30,1291,179],{"class":58},[30,1293,1294],{"class":44}," initialize",[30,1296,1297],{"class":40},"(version, default ",[30,1299,190],{"class":58},[30,1301,1302],{"class":36}," false",[30,1304,239],{"class":40},[30,1306,1307,1310,1312],{"class":32,"line":86},[30,1308,1309],{"class":40},"    @version ",[30,1311,190],{"class":58},[30,1313,1314],{"class":40}," version\n",[30,1316,1317,1320,1322],{"class":32,"line":105},[30,1318,1319],{"class":40},"    @default ",[30,1321,190],{"class":58},[30,1323,1324],{"class":40}," default\n",[30,1326,1327],{"class":32,"line":209},[30,1328,212],{"class":58},[30,1330,1331],{"class":32,"line":215},[30,1332,83],{"emptyLinePlaceholder":82},[30,1334,1335],{"class":32,"line":220},[30,1336,1337],{"class":754},"  # check whether version is specified or is default\n",[30,1339,1340,1342,1345],{"class":32,"line":228},[30,1341,179],{"class":58},[30,1343,1344],{"class":44}," matches?",[30,1346,1347],{"class":40},"(request)\n",[30,1349,1350,1353,1356,1359,1361,1363],{"class":32,"line":242},[30,1351,1352],{"class":44},"    check_headers",[30,1354,1355],{"class":40},"(request.",[30,1357,1358],{"class":44},"headers",[30,1360,325],{"class":40},[30,1362,721],{"class":58},[30,1364,1324],{"class":40},[30,1366,1367],{"class":32,"line":247},[30,1368,212],{"class":58},[30,1370,1371],{"class":32,"line":252},[30,1372,83],{"emptyLinePlaceholder":82},[30,1374,1375],{"class":32,"line":260},[30,1376,558],{"class":58},[30,1378,1379],{"class":32,"line":278},[30,1380,83],{"emptyLinePlaceholder":82},[30,1382,1383,1385,1388],{"class":32,"line":306},[30,1384,179],{"class":58},[30,1386,1387],{"class":44}," check_headers",[30,1389,1390],{"class":40},"(headers)\n",[30,1392,1393],{"class":32,"line":311},[30,1394,1395],{"class":754},"    # check version from Accept headers; expect custom media type `links`\n",[30,1397,1398,1401,1403,1406,1409],{"class":32,"line":337},[30,1399,1400],{"class":711},"    accept",[30,1402,715],{"class":58},[30,1404,1405],{"class":40}," headers[",[30,1407,1408],{"class":36},":accept",[30,1410,439],{"class":40},[30,1412,1413,1415,1418,1421,1423,1426,1429,1432],{"class":32,"line":342},[30,1414,1400],{"class":40},[30,1416,1417],{"class":58},"&.",[30,1419,1420],{"class":44},"include?",[30,1422,287],{"class":40},[30,1424,1425],{"class":92},"\"application\u002Fvnd.links.",[30,1427,1428],{"class":92},"#{version}",[30,1430,1431],{"class":92},"+json\"",[30,1433,239],{"class":40},[30,1435,1436],{"class":32,"line":371},[30,1437,212],{"class":58},[30,1439,1440],{"class":32,"line":376},[30,1441,108],{"class":58},[10,1443,1444],{},"so, according to the Media Type Specification, you can define your own media types using the vendor tree i.e. application\u002Fvnd.example.resource+json.",[10,1446,1447,1448,1451,1452,1455],{},"We're going to follow this specification for our api. Here we define a custom vendor media type ",[27,1449,1450],{},"application\u002Fvnd.links.{version_number}+json"," giving clients the ability to choose which API version they require. No need to add ",[27,1453,1454],{},"\u002Fv2\u002F"," to the url. Pretty cool right.",[10,1457,1458],{},"Now we can go ahead and update our routes to have multiple versions",[21,1460,1462],{"className":23,"code":1461,"language":25,"meta":15,"style":15},"Rails.application.routes.draw do\n\n  # namespace the controllers without affecting the URI\n  scope module: :v1, constraints: ApiVersion.new('v1', true) do\n    resources :links\n    resources :redirect\n\n    get '\u002Fs\u002F:slug', to: 'redirects#index'\n  end\nend\n",[27,1463,1464,1482,1486,1491,1529,1536,1542,1546,1559,1563],{"__ignoreMap":15},[30,1465,1466,1468,1470,1472,1474,1476,1478,1480],{"class":32,"line":33},[30,1467,37],{"class":36},[30,1469,41],{"class":40},[30,1471,45],{"class":44},[30,1473,41],{"class":40},[30,1475,50],{"class":44},[30,1477,41],{"class":40},[30,1479,55],{"class":44},[30,1481,59],{"class":58},[30,1483,1484],{"class":32,"line":62},[30,1485,83],{"emptyLinePlaceholder":82},[30,1487,1488],{"class":32,"line":71},[30,1489,1490],{"class":754},"  # namespace the controllers without affecting the URI\n",[30,1492,1493,1496,1499,1502,1504,1507,1510,1512,1514,1516,1519,1521,1524,1526],{"class":32,"line":79},[30,1494,1495],{"class":40},"  scope ",[30,1497,1498],{"class":36},"module:",[30,1500,1501],{"class":36}," :v1",[30,1503,96],{"class":40},[30,1505,1506],{"class":36},"constraints:",[30,1508,1509],{"class":36}," ApiVersion",[30,1511,41],{"class":40},[30,1513,272],{"class":58},[30,1515,287],{"class":40},[30,1517,1518],{"class":92},"'v1'",[30,1520,96],{"class":40},[30,1522,1523],{"class":36},"true",[30,1525,325],{"class":40},[30,1527,1528],{"class":58},"do\n",[30,1530,1531,1534],{"class":32,"line":86},[30,1532,1533],{"class":40},"    resources ",[30,1535,68],{"class":36},[30,1537,1538,1540],{"class":32,"line":105},[30,1539,1533],{"class":40},[30,1541,76],{"class":36},[30,1543,1544],{"class":32,"line":209},[30,1545,83],{"emptyLinePlaceholder":82},[30,1547,1548,1551,1553,1555,1557],{"class":32,"line":215},[30,1549,1550],{"class":40},"    get ",[30,1552,93],{"class":92},[30,1554,96],{"class":40},[30,1556,99],{"class":36},[30,1558,102],{"class":92},[30,1560,1561],{"class":32,"line":220},[30,1562,212],{"class":58},[30,1564,1565],{"class":32,"line":228},[30,1566,108],{"class":58},[10,1568,1569,1570,1572,1573,1576,1577,41],{},"We've set the version constraint at the namespace level. Handily, this will be applied to all resources within it. We've also defined ",[27,1571,1232],{}," as the ",[27,1574,1575],{},"default"," version for cases where the version is not provided, the API will default to ",[27,1578,1232],{},[10,1580,1581,1582,1585,1586,1588],{},"In the event we were to add new versions, they would have to be defined above the default version since Rails will just cycle through all routes from top to bottom searching for one that matches(till method ",[27,1583,1584],{},"matches?"," resolves to ",[27,1587,1523],{},").",[10,1590,1591],{},"lets give our controllers a new home",[21,1593,1595],{"className":114,"code":1594,"language":116,"meta":15,"style":15},"$ mkdir app\u002Fcontrollers\u002Fv1\n",[27,1596,1597],{"__ignoreMap":15},[30,1598,1599,1601,1604],{"class":32,"line":33},[30,1600,123],{"class":44},[30,1602,1603],{"class":92}," mkdir",[30,1605,1606],{"class":92}," app\u002Fcontrollers\u002Fv1\n",[10,1608,1609],{},"And move them in",[21,1611,1613],{"className":114,"code":1612,"language":116,"meta":15,"style":15},"$ mv app\u002Fcontrollers\u002F{links_controller.rb,redirects_controller.rb} app\u002Fcontrollers\u002Fv1\n",[27,1614,1615],{"__ignoreMap":15},[30,1616,1617,1619,1622,1625],{"class":32,"line":33},[30,1618,123],{"class":44},[30,1620,1621],{"class":92}," mv",[30,1623,1624],{"class":92}," app\u002Fcontrollers\u002F{links_controller.rb,redirects_controller.rb}",[30,1626,1606],{"class":92},[10,1628,1629],{},"That's not all, lets define the controller in the v1 namespace",[21,1631,1633],{"className":23,"code":1632,"language":25,"meta":15,"style":15},"module V1\n  class linksController \u003C ApplicationController\n  # [...]\n  end\nend\n",[27,1634,1635,1642,1653,1658,1662],{"__ignoreMap":15},[30,1636,1637,1639],{"class":32,"line":33},[30,1638,1054],{"class":58},[30,1640,1641],{"class":44}," V1\n",[30,1643,1644,1646,1649,1651],{"class":32,"line":62},[30,1645,860],{"class":58},[30,1647,1648],{"class":44}," linksController",[30,1650,151],{"class":40},[30,1652,154],{"class":44},[30,1654,1655],{"class":32,"line":71},[30,1656,1657],{"class":754},"  # [...]\n",[30,1659,1660],{"class":32,"line":79},[30,1661,212],{"class":58},[30,1663,1664],{"class":32,"line":86},[30,1665,108],{"class":58},[10,1667,1668],{},"and the redirectsController",[21,1670,1672],{"className":23,"code":1671,"language":25,"meta":15,"style":15},"module V1\n  class RedirectsController \u003C ApplicationController\n  # [...]\n  end\nend\n",[27,1673,1674,1680,1690,1694,1698],{"__ignoreMap":15},[30,1675,1676,1678],{"class":32,"line":33},[30,1677,1054],{"class":58},[30,1679,1641],{"class":44},[30,1681,1682,1684,1686,1688],{"class":32,"line":62},[30,1683,860],{"class":58},[30,1685,863],{"class":44},[30,1687,151],{"class":40},[30,1689,154],{"class":44},[30,1691,1692],{"class":32,"line":71},[30,1693,1657],{"class":754},[30,1695,1696],{"class":32,"line":79},[30,1697,212],{"class":58},[30,1699,1700],{"class":32,"line":86},[30,1701,108],{"class":58},[10,1703,1704],{},"Now lets take a beat and see what works and what doesn't",[21,1706,1708],{"className":114,"code":1707,"language":116,"meta":15,"style":15},"# get links from API v1\n$ http :3000\u002Flinks Accept:'application\u002Fvnd.links.v1+json' \n# attempt to get from API v2\n$ http :3000\u002Flinks Accept:'application\u002Fvnd.links.v2+json'\n",[27,1709,1710,1715,1729,1734],{"__ignoreMap":15},[30,1711,1712],{"class":32,"line":33},[30,1713,1714],{"class":754},"# get links from API v1\n",[30,1716,1717,1719,1721,1723,1726],{"class":32,"line":62},[30,1718,123],{"class":44},[30,1720,1160],{"class":92},[30,1722,1180],{"class":92},[30,1724,1725],{"class":92}," Accept:'application\u002Fvnd.links.v1+json'",[30,1727,1728],{"class":40}," \n",[30,1730,1731],{"class":32,"line":71},[30,1732,1733],{"class":754},"# attempt to get from API v2\n",[30,1735,1736,1738,1740,1742],{"class":32,"line":79},[30,1737,123],{"class":44},[30,1739,1160],{"class":92},[30,1741,1180],{"class":92},[30,1743,1744],{"class":92}," Accept:'application\u002Fvnd.links.v2+json'\n",[10,1746,1747],{},"In case we attempt to access a nonexistent version, the API will default to v1 since we set it as the default version. For testing purposes, let's define v2.",[10,1749,1750],{},"Generate a v2 links controller",[21,1752,1754],{"className":114,"code":1753,"language":116,"meta":15,"style":15},"$ rails g v2\u002Flinks\n",[27,1755,1756],{"__ignoreMap":15},[30,1757,1758,1760,1762,1765],{"class":32,"line":33},[30,1759,123],{"class":44},[30,1761,126],{"class":92},[30,1763,1764],{"class":92}," g",[30,1766,1767],{"class":92}," v2\u002Flinks\n",[10,1769,1770],{},"define a namespace",[21,1772,1774],{"className":23,"code":1773,"language":25,"meta":15,"style":15},"#config\u002Froutes.rb\nRails.application.routes.draw do\n  # For details on the DSL available within this file, see http:\u002F\u002Fguides.rubyonrails.org\u002Frouting.html\n\n  # module the controllers without affecting the URI\n  scope module: :v2, constraints: ApiVersion.new('v2') do\n    resources :links, only: :index\n  end\n\n  scope module: :v1, constraints: ApiVersion.new('v1', true) do\n    # [...]\n  end\n  # [...]\nend\n",[27,1775,1776,1781,1799,1804,1808,1813,1841,1855,1859,1863,1893,1898,1902,1906],{"__ignoreMap":15},[30,1777,1778],{"class":32,"line":33},[30,1779,1780],{"class":754},"#config\u002Froutes.rb\n",[30,1782,1783,1785,1787,1789,1791,1793,1795,1797],{"class":32,"line":62},[30,1784,37],{"class":36},[30,1786,41],{"class":40},[30,1788,45],{"class":44},[30,1790,41],{"class":40},[30,1792,50],{"class":44},[30,1794,41],{"class":40},[30,1796,55],{"class":44},[30,1798,59],{"class":58},[30,1800,1801],{"class":32,"line":71},[30,1802,1803],{"class":754},"  # For details on the DSL available within this file, see http:\u002F\u002Fguides.rubyonrails.org\u002Frouting.html\n",[30,1805,1806],{"class":32,"line":79},[30,1807,83],{"emptyLinePlaceholder":82},[30,1809,1810],{"class":32,"line":86},[30,1811,1812],{"class":754},"  # module the controllers without affecting the URI\n",[30,1814,1815,1817,1819,1822,1824,1826,1828,1830,1832,1834,1837,1839],{"class":32,"line":105},[30,1816,1495],{"class":40},[30,1818,1498],{"class":36},[30,1820,1821],{"class":36}," :v2",[30,1823,96],{"class":40},[30,1825,1506],{"class":36},[30,1827,1509],{"class":36},[30,1829,41],{"class":40},[30,1831,272],{"class":58},[30,1833,287],{"class":40},[30,1835,1836],{"class":92},"'v2'",[30,1838,325],{"class":40},[30,1840,1528],{"class":58},[30,1842,1843,1845,1848,1850,1852],{"class":32,"line":209},[30,1844,1533],{"class":40},[30,1846,1847],{"class":36},":links",[30,1849,96],{"class":40},[30,1851,167],{"class":36},[30,1853,1854],{"class":36}," :index\n",[30,1856,1857],{"class":32,"line":215},[30,1858,212],{"class":58},[30,1860,1861],{"class":32,"line":220},[30,1862,83],{"emptyLinePlaceholder":82},[30,1864,1865,1867,1869,1871,1873,1875,1877,1879,1881,1883,1885,1887,1889,1891],{"class":32,"line":228},[30,1866,1495],{"class":40},[30,1868,1498],{"class":36},[30,1870,1501],{"class":36},[30,1872,96],{"class":40},[30,1874,1506],{"class":36},[30,1876,1509],{"class":36},[30,1878,41],{"class":40},[30,1880,272],{"class":58},[30,1882,287],{"class":40},[30,1884,1518],{"class":92},[30,1886,96],{"class":40},[30,1888,1523],{"class":36},[30,1890,325],{"class":40},[30,1892,1528],{"class":58},[30,1894,1895],{"class":32,"line":242},[30,1896,1897],{"class":754},"    # [...]\n",[30,1899,1900],{"class":32,"line":247},[30,1901,212],{"class":58},[30,1903,1904],{"class":32,"line":252},[30,1905,1657],{"class":754},[30,1907,1908],{"class":32,"line":260},[30,1909,108],{"class":58},[10,1911,1912],{},"Since this is test controller, we'll define an index controller method with a dummy response.",[21,1914,1916],{"className":23,"code":1915,"language":25,"meta":15,"style":15},"module V2\n  class LinksController \u003C ApplicationController\n    def index\n      json_response({ message: 'Its always important to version an API' })\n    end\n  end\nend\n",[27,1917,1918,1925,1935,1941,1955,1959,1963],{"__ignoreMap":15},[30,1919,1920,1922],{"class":32,"line":33},[30,1921,1054],{"class":58},[30,1923,1924],{"class":44}," V2\n",[30,1926,1927,1929,1931,1933],{"class":32,"line":62},[30,1928,860],{"class":58},[30,1930,148],{"class":44},[30,1932,151],{"class":40},[30,1934,154],{"class":44},[30,1936,1937,1939],{"class":32,"line":71},[30,1938,884],{"class":58},[30,1940,182],{"class":44},[30,1942,1943,1945,1947,1949,1952],{"class":32,"line":79},[30,1944,976],{"class":44},[30,1946,347],{"class":40},[30,1948,350],{"class":36},[30,1950,1951],{"class":92}," 'Its always important to version an API'",[30,1953,1954],{"class":40}," })\n",[30,1956,1957],{"class":32,"line":86},[30,1958,925],{"class":58},[30,1960,1961],{"class":32,"line":105},[30,1962,212],{"class":58},[30,1964,1965],{"class":32,"line":209},[30,1966,108],{"class":58},[10,1968,1969,1970,1973],{},"Note: ruby will generate the shorthand for our class structure\n",[27,1971,1972],{},"class V2::LinksController \u003C ApplicationController","\nyou can use either or, I prefer the long hand.",[10,1975,1976],{},"Great! now lets fire out some tests",[21,1978,1979],{"className":114,"code":1707,"language":116,"meta":15,"style":15},[27,1980,1981,1985,1997,2001],{"__ignoreMap":15},[30,1982,1983],{"class":32,"line":33},[30,1984,1714],{"class":754},[30,1986,1987,1989,1991,1993,1995],{"class":32,"line":62},[30,1988,123],{"class":44},[30,1990,1160],{"class":92},[30,1992,1180],{"class":92},[30,1994,1725],{"class":92},[30,1996,1728],{"class":40},[30,1998,1999],{"class":32,"line":71},[30,2000,1733],{"class":754},[30,2002,2003,2005,2007,2009],{"class":32,"line":79},[30,2004,123],{"class":44},[30,2006,1160],{"class":92},[30,2008,1180],{"class":92},[30,2010,1744],{"class":92},[10,2012,2013],{},"looking good.",[2015,2016],"hr",{},[10,2018,2019],{},"This is where I was going to call it a day, but I've had my coffee and am feeling generous. So lets do a BONUS ROUND!",[10,2021,2022],{},"I can see that we return the slug and the url but not them together. We could make a record in the table and save them combined, but then again we already have them. So what else can we do?",[10,2024,2025],{},[13,2026],{"alt":15,"src":2027},"https:\u002F\u002Fi.imgflip.com\u002F4ts9wz.jpg",[10,2029,2030],{},"Lets go ahead and add the rails serializer gem",[21,2032,2034],{"className":23,"code":2033,"language":25,"meta":15,"style":15},"gem 'active_model_serializers', '~> 0.10.0'\n",[27,2035,2036],{"__ignoreMap":15},[30,2037,2038,2041,2044,2046],{"class":32,"line":33},[30,2039,2040],{"class":40},"gem ",[30,2042,2043],{"class":92},"'active_model_serializers'",[30,2045,96],{"class":40},[30,2047,2048],{"class":92},"'~> 0.10.0'\n",[10,2050,2051],{},"bundle it",[21,2053,2055],{"className":114,"code":2054,"language":116,"meta":15,"style":15},"$ bunde install\n",[27,2056,2057],{"__ignoreMap":15},[30,2058,2059,2061,2064],{"class":32,"line":33},[30,2060,123],{"class":44},[30,2062,2063],{"class":92}," bunde",[30,2065,2066],{"class":92}," install\n",[10,2068,2069],{},"Generate the serializer for the link model",[21,2071,2073],{"className":114,"code":2072,"language":116,"meta":15,"style":15},"$ rails g serializer link\n",[27,2074,2075],{"__ignoreMap":15},[30,2076,2077,2079,2081,2083,2086],{"class":32,"line":33},[30,2078,123],{"class":44},[30,2080,126],{"class":92},[30,2082,1764],{"class":92},[30,2084,2085],{"class":92}," serializer",[30,2087,2088],{"class":92}," link\n",[10,2090,2091,2092,2095,2096,2099],{},"This creates a new directory ",[27,2093,2094],{},"app\u002Fserializers"," and adds a new file ",[27,2097,2098],{},"link_serializer.rb",". Let's define the link serializer with the data that we want it to contain.",[21,2101,2103],{"className":23,"code":2102,"language":25,"meta":15,"style":15},"class LinkSerializer \u003C ActiveModel::Serializer\n  attributes :id, :url, :slug, :created_at, :updated_at, :short\nend\n",[27,2104,2105,2117,2147],{"__ignoreMap":15},[30,2106,2107,2109,2112,2114],{"class":32,"line":33},[30,2108,145],{"class":58},[30,2110,2111],{"class":44}," LinkSerializer",[30,2113,151],{"class":40},[30,2115,2116],{"class":44},"ActiveModel::Serializer\n",[30,2118,2119,2122,2124,2126,2128,2130,2132,2134,2137,2139,2142,2144],{"class":32,"line":62},[30,2120,2121],{"class":40},"  attributes ",[30,2123,590],{"class":36},[30,2125,96],{"class":40},[30,2127,671],{"class":36},[30,2129,96],{"class":40},[30,2131,290],{"class":36},[30,2133,96],{"class":40},[30,2135,2136],{"class":36},":created_at",[30,2138,96],{"class":40},[30,2140,2141],{"class":36},":updated_at",[30,2143,96],{"class":40},[30,2145,2146],{"class":36},":short\n",[30,2148,2149],{"class":32,"line":71},[30,2150,108],{"class":58},[10,2152,2153],{},"What's that there? I don't remember our link model model having an attribute called short. Well look again!",[21,2155,2157],{"className":23,"code":2156,"language":25,"meta":15,"style":15},"# frozen_string_literal: true\n\nclass Link \u003C ApplicationRecord\n  before_validation :format_url\n\n  validates_presence_of :url\n  validates_presence_of :slug\n\n  validates_uniqueness_of :slug\n\n  validates :url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https])\n  validates_numericality_of :clicked\n\n  validates_length_of :url, within: 3..255, on: :create\n  validates_length_of :slug, within: 1..255, on: :create\n\n  def format_url\n    return false unless url.present?\n\n    self.url = \"http:\u002F\u002F#{url}\" unless url[%r{\\Ahttp:\u002F\u002F}] || url[%r{\\Ahttps:\u002F\u002F}]\n  end\n\n  def short\n    url = Rails.env.development? ? 'localhost:3000\u002Fs\u002F' : 'example.com\u002F'\n\n    url + slug\n  end\nend\n",[27,2158,2159,2164,2168,2179,2187,2191,2199,2206,2210,2217,2221,2254,2262,2266,2294,2316,2320,2327,2341,2345,2398,2402,2406,2413,2445,2449,2460,2464],{"__ignoreMap":15},[30,2160,2161],{"class":32,"line":33},[30,2162,2163],{"class":754},"# frozen_string_literal: true\n",[30,2165,2166],{"class":32,"line":62},[30,2167,83],{"emptyLinePlaceholder":82},[30,2169,2170,2172,2174,2176],{"class":32,"line":71},[30,2171,145],{"class":58},[30,2173,193],{"class":44},[30,2175,151],{"class":40},[30,2177,2178],{"class":44},"ApplicationRecord\n",[30,2180,2181,2184],{"class":32,"line":79},[30,2182,2183],{"class":40},"  before_validation ",[30,2185,2186],{"class":36},":format_url\n",[30,2188,2189],{"class":32,"line":86},[30,2190,83],{"emptyLinePlaceholder":82},[30,2192,2193,2196],{"class":32,"line":105},[30,2194,2195],{"class":40},"  validates_presence_of ",[30,2197,2198],{"class":36},":url\n",[30,2200,2201,2203],{"class":32,"line":209},[30,2202,2195],{"class":40},[30,2204,2205],{"class":36},":slug\n",[30,2207,2208],{"class":32,"line":215},[30,2209,83],{"emptyLinePlaceholder":82},[30,2211,2212,2215],{"class":32,"line":220},[30,2213,2214],{"class":40},"  validates_uniqueness_of ",[30,2216,2205],{"class":36},[30,2218,2219],{"class":32,"line":228},[30,2220,83],{"emptyLinePlaceholder":82},[30,2222,2223,2226,2228,2230,2233,2236,2239,2242,2244,2247,2249,2252],{"class":32,"line":242},[30,2224,2225],{"class":40},"  validates ",[30,2227,671],{"class":36},[30,2229,96],{"class":40},[30,2231,2232],{"class":36},"format:",[30,2234,2235],{"class":36}," URI",[30,2237,2238],{"class":40},"::",[30,2240,2241],{"class":36},"DEFAULT_PARSER",[30,2243,41],{"class":40},[30,2245,2246],{"class":44},"make_regexp",[30,2248,287],{"class":40},[30,2250,2251],{"class":92},"%w[http https]",[30,2253,239],{"class":40},[30,2255,2256,2259],{"class":32,"line":247},[30,2257,2258],{"class":40},"  validates_numericality_of ",[30,2260,2261],{"class":36},":clicked\n",[30,2263,2264],{"class":32,"line":252},[30,2265,83],{"emptyLinePlaceholder":82},[30,2267,2268,2271,2273,2275,2278,2281,2283,2286,2288,2291],{"class":32,"line":260},[30,2269,2270],{"class":40},"  validates_length_of ",[30,2272,671],{"class":36},[30,2274,96],{"class":40},[30,2276,2277],{"class":36},"within:",[30,2279,2280],{"class":36}," 3",[30,2282,738],{"class":40},[30,2284,2285],{"class":36},"255",[30,2287,96],{"class":40},[30,2289,2290],{"class":36},"on:",[30,2292,2293],{"class":36}," :create\n",[30,2295,2296,2298,2300,2302,2304,2306,2308,2310,2312,2314],{"class":32,"line":278},[30,2297,2270],{"class":40},[30,2299,290],{"class":36},[30,2301,96],{"class":40},[30,2303,2277],{"class":36},[30,2305,910],{"class":36},[30,2307,738],{"class":40},[30,2309,2285],{"class":36},[30,2311,96],{"class":40},[30,2313,2290],{"class":36},[30,2315,2293],{"class":36},[30,2317,2318],{"class":32,"line":306},[30,2319,83],{"emptyLinePlaceholder":82},[30,2321,2322,2324],{"class":32,"line":311},[30,2323,179],{"class":58},[30,2325,2326],{"class":44}," format_url\n",[30,2328,2329,2331,2333,2336,2339],{"class":32,"line":337},[30,2330,314],{"class":58},[30,2332,1302],{"class":36},[30,2334,2335],{"class":58}," unless",[30,2337,2338],{"class":40}," url.",[30,2340,635],{"class":44},[30,2342,2343],{"class":32,"line":342},[30,2344,83],{"emptyLinePlaceholder":82},[30,2346,2347,2350,2352,2355,2357,2360,2363,2366,2368,2371,2375,2379,2382,2385,2387,2389,2391,2393,2396],{"class":32,"line":371},[30,2348,2349],{"class":36},"    self",[30,2351,41],{"class":40},[30,2353,2354],{"class":44},"url",[30,2356,715],{"class":58},[30,2358,2359],{"class":92}," \"http:\u002F\u002F",[30,2361,2362],{"class":92},"#{url}",[30,2364,2365],{"class":92},"\"",[30,2367,2335],{"class":58},[30,2369,2370],{"class":40}," url[",[30,2372,2374],{"class":2373},"sns5M","%r{",[30,2376,2378],{"class":2377},"sRjNt","\\A",[30,2380,2381],{"class":2373},"http:\u002F\u002F}",[30,2383,2384],{"class":40},"] ",[30,2386,721],{"class":58},[30,2388,2370],{"class":40},[30,2390,2374],{"class":2373},[30,2392,2378],{"class":2377},[30,2394,2395],{"class":2373},"https:\u002F\u002F}",[30,2397,439],{"class":40},[30,2399,2400],{"class":32,"line":376},[30,2401,212],{"class":58},[30,2403,2404],{"class":32,"line":381},[30,2405,83],{"emptyLinePlaceholder":82},[30,2407,2408,2410],{"class":32,"line":389},[30,2409,179],{"class":58},[30,2411,2412],{"class":44}," short\n",[30,2414,2415,2418,2420,2423,2425,2428,2430,2433,2436,2439,2442],{"class":32,"line":410},[30,2416,2417],{"class":711},"    url",[30,2419,715],{"class":58},[30,2421,2422],{"class":36}," Rails",[30,2424,41],{"class":40},[30,2426,2427],{"class":44},"env",[30,2429,41],{"class":40},[30,2431,2432],{"class":44},"development?",[30,2434,2435],{"class":58}," ?",[30,2437,2438],{"class":92}," 'localhost:3000\u002Fs\u002F'",[30,2440,2441],{"class":40}," : ",[30,2443,2444],{"class":92},"'example.com\u002F'\n",[30,2446,2447],{"class":32,"line":442},[30,2448,83],{"emptyLinePlaceholder":82},[30,2450,2451,2454,2457],{"class":32,"line":447},[30,2452,2453],{"class":40},"    url ",[30,2455,2456],{"class":58},"+",[30,2458,2459],{"class":40}," slug\n",[30,2461,2462],{"class":32,"line":466},[30,2463,212],{"class":58},[30,2465,2466],{"class":32,"line":471},[30,2467,108],{"class":58},[10,2469,2470],{},"Now lets see if we can get our link",[10,2472,2473],{},[13,2474],{"alt":15,"src":2475},"\u002Fimages\u002Fposts\u002F2021\u002F01\u002FScreen-Shot-2021-01-14-at-11.49.33.png",[10,2477,2478],{},"well",[10,2480,2481],{},[13,2482],{"alt":15,"src":2483},"https:\u002F\u002Fmedia.giphy.com\u002Fmedia\u002FR8afzAVZ1zHjy\u002Fgiphy.gif",[2015,2485],{},[2487,2488,2490],"h4",{"id":2489},"conclusion","Conclusion",[10,2492,2493],{},"We now have a rails 6 api, complete with a full test suite, serializers and a beautifully versioned api. Not too shabby.",[10,2495,2496],{},"We've come a long way, and we've achieved so much. Stay tuned for the next part where I either:",[2498,2499,2500,2504,2507,2510],"ul",{},[2501,2502,2503],"li",{},"Implement Users & JWT authentication",[2501,2505,2506],{},"Dockerize the application",[2501,2508,2509],{},"Create a front end using Vue.js",[2501,2511,2512],{},"Leave this repo to collect dust in github",[10,2514,2515],{},"Email me your votes.",[2517,2518,2519],"style",{},"html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sns5M, html code.shiki .sns5M{--shiki-default:#DBEDFF}html pre.shiki code .sRjNt, html code.shiki .sRjNt{--shiki-default:#85E89D;--shiki-default-font-weight:bold}",{"title":15,"searchDepth":62,"depth":62,"links":2521},[],"2021-01-14 11:57","md",{},"\u002F2021\u002F01\u002F14\u002Flink-shortener-part-2",{"title":5,"description":15},"2021\u002F01\u002F14\u002Flink-shortener-part-2","The url shortener part 2 aka: url shortner reloaded aka: url shortener Tokyo Drift.","TS-lsuqpBSHliBZuwNYmIiVSwRJXy7dY263KzB3HM-4",1791634646001]