JDStore 大赛复盘(重写)

为什么要复盘?

xdite说大家复盘做的不好,我相信有我一份。反思一下,其实是懒惰,反思和总结是痛苦的,总想要逃避。总是逃避这些困难却重要的事情,就是我之所以不牛逼的原因。

复盘的目的不是为了记日记或者流水账,不是交作业应付差事,不是为了跟别人炫耀,不是为了指导别人(那是教程,但是好的复盘自然会帮到别人)。复盘是为了通过回顾,反思,找到自己做的好和不好的地方,强化对的,改正错的,指导未来的自己做得更好。

就像每天都应该写的ORID,这是每日的复盘:记录客观事件->情绪->反思->下一步,不停的迭代循环,可惜我没有做到。

反思哪里做得不好,如何改进?

1.目标/动机不清晰

比赛结束后,Jimmy问了我一个让我思考了很久的问题:“你来全栈营的目的是什么?”。我当时的回答是:“嗯,就想学个新技能吧。。。哦,其实也想学会学习的方法,学了元学习课想实践一下。”。回答完我就感觉到,这是多么不清晰的目标啊,之所以说不清晰是因为目标的动机不明确,没有动机就没有办法给目标赋予意义,没有意义还谈什么执行力。

反观所有参赛名列前茅的同学都有非常明确的目标,或者说强烈的动机。刘传志在第一;潘鹏工作遇到天花板,寻找突破;jimmy,韵儿爸爸,anndo都是开始或者中途辞职学习。他们都是有强烈学习动机的,因此结果绝不会太差。而强烈的学习动机和目标一定是来自于清晰的思考,而不是盲目的选择。

其实如果当初我多问过自己几个why/what/how,就可以让自己的目标更清晰,挖掘出自己内心深处的动机,并且给目标赋予重大意义,比如:

  • 为什么想要学个新技能?是为了找工作, 创业,提升自信,提升工资,还是解决自己的某个问题?

答:工作7年职业遇到瓶颈,深刻的感觉工作已经没有成长,却又没有办法找到更好的突破点。如果学会编程,对于我目前的产品经理工作会是很大的加分项。

  • 为什么想要学会学习的方法?你之前的学习方法哪里不好,让你感到了什么阻碍,学会了学习的方法还要去学什么?

答:我曾经是个学霸,但是是中国高考教育制度下的学霸,所以理论上讲是被可能错误的学习方法戕害的最深的那一批人,因为我不但方法可能是错误的,还花费了大量的时间刻意练习这个可能错误的方法。我深刻的感觉到,进入了大学之后的人生像是失去了方向和目标,就这样浑浑噩噩过去了14年(两辈子啊我的妈),虽然算不上彻底失败,但是如果你用“高考就是你人生的巅峰啊”这样的话来戳一下我,我也只能哑口无言。而每个人都希望自己的人生越来越好,今天的自己好过昨天的,所以这句话对我具有很大的杀伤力,请你默默想就好了不要当面对我说。

从大学开始,我就一路变成学渣,上课觉得没意思就开始逃课(因为学校的教学法根本就是登山啊),考前熬夜临时抱佛脚(这倒像是拼图),竟然每次都考得还不错就更助长了我的气焰(说明老师说的迅速入门完整框架很有效,可惜考完书本就丢了,再也不会看了)。这样的恶性循环导致后面觉得越来越难,越来越懵逼,因此离开学校的时候深深的厌恶自己的电子学专业,后悔没有选择我貌似更喜欢的金融专业,后来终于明白兴趣是怎么产生的时候就知道其实学金融也是一样的,唯一的区别是我会厌恶金融而已。

离开学校进入工作,迫于生计/随大流,曾经自学过投资,CFA,机器学习,deep learning,java,r,每一样都买来一堆书慢慢啃,每一样都从入门到放弃,学了跟没学一样,加之大学也没学到啥,这14年我的个人技能积累几乎为0. 耗费的这些时间不如愉快玩耍好了。

明显能够看到,我不是不想学,我是不会学。而我却竟然一直都没有察觉到这一点,因为我明明是学霸啊!(傲慢就是全知遮蔽,多么痛的领悟)

下一步要做些什么? 写完这些,终于明白,我来全栈根本就是拯救已经浪费掉的14年人生的。我要学会学习,要学会学习再去学习自己想学的那些技能,摆脱这么多年来的傻上进,找回一点对自己的自信。瞬间我觉得动力满满,虽然只剩下一个月,我也一定要全力以赴的完成每一个作业和练习,就像刚刚开始一样。

这个道理可以用在什么其他地方?同样,今后在做任何决定之前,先花时间想清楚动机,确认自己足够清楚自己想要得到什么结果,找到内心的驱动力,再去行动,才会有执行力,否则无法全力以赴。

2. 傲慢

这个词对我来说在全栈出现过两次,第一次我以为是说别人,第二次我知道是说自己。

第一次:
xdite老师在课程最开始就有一篇文章,放下你的无效学习方式。这篇让我们打印出来并且放在电脑旁的文章,不知道有多少人记得,反正我是忘记了,准确的说是忘记具体内容了,回头再看一遍,会脊背发凉,道理写得清清楚楚,我也看过好几遍,但是回头再看发现自己很多都没有做到,不信你试试看。

xdite说:”成败的根本在于你是否因为绝望,而放下傲慢,而专心听从教练指示,走上最好的学习模式,建立最好的学习习惯。”。其实能看出,放下傲慢是多么难的一件事,一般人大抵需要先绝望才行。我竟然看的时候觉得:”嗯,我明白了,我一定不会傲慢的。“。这么轻松的就以为自己能够克制傲慢的天性,然后一头跌进傲慢的深坑,作业拖拖拉拉,重复练习次数不够,经常偷看其他教程补充“基础”,schedule上也经常出现一些不相关事项(比如研究投资)。最重要的是,完全没在想这就是老师所说的傲慢啊。

傲慢最妙的地方是,几乎没有人知道自己傲慢,也没有人认为自己傲慢,就好像没有人吵架时候会觉得自己错了一样。觉得自己傲慢就不会傲慢了,真的觉得自己错了也就不会吵架了,这是个悖论,需要强大的元认知去识别和纠正。

第二次(跟学习全栈本身无关)
全栈过程中,有过一个栈友推荐的好的工作机会,刚好符合我对于未来的规划,于是栈友热心帮忙递了简历。由于算是推荐的,很顺利的得到了面试机会,当然仅仅是电话面试。但是我竟然抱着“反正我也没在找工作,就是随便试试”的草率心态完成了面试,事先没有任何准备,不了解岗位需求,甚至没好好总结自己这一份工作做了什么,聊了一个小时之后就石沉大海。

后来栈友帮忙要反馈,人家非常认真的写了评语,其他的都在意料之中,因为自己准备不足,也都能感觉到问题出在哪,但是有个词很刺眼“傲娇”。这是我完全没有自我察觉的,就是所谓的盲区,很感谢面试的人肯写中肯的评语让我有机会看到自己身上存在的我都不知道的问题。而我反思了很久,终于知道傲娇其实就是来自于我不够绝望,如果我急需这份工作,特别渴望,就一定会认真准备,面试的时候整个人的状态会完全不一样。因此做事真的不应该有“随便”这个状态,这样很容易傲慢,不管是否真的绝望,一定要给自己制造绝望的感觉,才能拿出100%的努力。

3.忘记了身体这件事

jdstore比赛一结束就病了1周,虽然不是重病,但是已经达到影响生产力的地步,每天昏昏沉沉,毫无效率,身体处于非常疲劳时刻想睡的状态,却由于工作忙碌和宝宝需要照顾而无法好好休息,一直无法调整过来。复盘,重复练习jdstore课程或者学习新的课程都没能开始做,几乎是完全荒废的一周,心里很着急。回想起来这几乎是必然,因为比赛最后一周的状态是每天熬夜+早上5点被宝宝叫醒,宝宝中间生病导致我夜里需要多次哄睡,自己缺乏锻炼和休息,过度负载的身体果然就反抗起来。老师一次直播说过,程序员最怕生病,特别是发烧感冒这种,不要过度熬夜,以免生病反而低效,这条建议早应该拿出来排在第一优先级,没有了好的身体真的什么都没有办法做。

锻炼身体也很重要,不能因为忙就忽略这一点。最明显的感觉是,之前坚持每天keep运动20分钟或者去游个泳,一整天都会精神很好,而最近一阵出于课程跟不上的愧疚感放弃了锻炼,虽然省下来20分钟可以学习,但是真的一整天的状态都不如从前。

下一步要做些什么? 恢复每天清晨20分钟锻炼,保证11点入睡,调整身体状态,以最好的身体和精神状态继续全栈之旅。

这个道理可以用在什么其他地方?做事要有长远眼光,看短期是一个策略,看长期就会发现更重要的东西被自己忽略。考虑问题不能光看眼前利弊,比如这20分钟我可以学习,就放弃锻炼,编程到凌晨2点多做了1个功能上线或者解了1个bug很有成就感,看起来是符合短期收益的,但是长期不可持续,弊大于利。身体是最重要的本钱,没有理由在这件事上随意。

从同学身上学到了什么?

1.专注的力量

跟上一期同学“乌龙明月”的《超级行动力课程》学到的最重要的道理是:“最重要的事只有一件,贪心每一样都想拿到,就像同时追好几只兔子,一只也抓不到。” 。这次比赛,我观察到所有脱颖而出的作品,参赛的同学都投注了100%的专注,心无旁骛的死磕作品,死磕自己。专注的力量很可怕,专注的人自带光芒,我相信只要哪怕有一次这样死磕自己的经历,整个人都会改变。反思自己,我好像一直都很贪心,总是处于抓一窝兔子的状态,最后得到的非常有限。

2.刻意练习

在全战营比赛过程中,观察到一个规律,印证了刻意练习非常重要。不单是通过刻意练习学到的的技能本身有价值,而是这个方法一旦掌握就像是有了葵花宝典,似乎人生可以上升到另外一个高度。
我的观察是,非常牛b的人大多有一个共性,他们都在很小的时候通过某一项技能的训练熟悉了刻意练习的方法,潜意识中知道了大量重复练习形成的肌肉记忆有多么重要,并且对于重复练习的容忍度和耐心比常人要高很多。他们做起事来很疯狂,但就是这种疯狂决定了他们跟普通人差距。举例如下:

  1. xdite老师(练习跆拳道多年):xdite老师在一次直播中透露自己是如何刻意练习编程的,一年会做多少个project,听完让人汗颜,也瞬间明白自己不牛b的原因。
  2. 刘传同学(练习跆拳道多年):此次比赛中有多疯狂大家也看到。
  3. 全栈二期线下班的朱英楠(加拿大皇家音乐协会钢琴十级):多次创业,线下版最后几天作出简历黑客雏形,现在已经成为创业项目。 也许年少的经历和后来的发展并不具有因果关系,也许他们原本就是天才。但我宁愿相信,人生来没有很大不同,后天的际遇造就了不同。(否则我也不用努力了。。。)

下一步要做些什么? 现在我参加的铁血训练营正在带领大家进行刻意练习,这点很棒,下一步一定要好好按照要求,每天进行提取练习,才能真正学到东西。而不是大赛完了,就当学习结束了。

这个道理可以用在什么其他地方? 我似乎已经无法补上儿时的这一课。我的宝宝,我会希望她在成长过程中也爱上一个乐器或者某项技能,不管是什么,我希望她能够痴迷、享受、热爱并且通过这项技能学会刻意练习,受益终生。

3.教是最好的学

元学习课强调教是最好的学,因为只有经过提取练习,才能把记忆焊在大脑里。但是非常可惜,我没有实践,但是仍然通过观察别人的实践深刻的认同这个道理。所有非常棒的作品,都有很多教程分享,他们不但通过分享教程,帮助别人解决问题获得了大家的支持,更重要的是他们自己真的获得了深刻的成长。分享和教别人的这种体验,我相信已经成为他们人生中谁也拿不走的宝贵财富。我也会在今后的学习中实践这一个方法,之前理解不够深刻,所以没有行动。

我做对了什么?以后如何做得更好?

这次比赛很意外的拿到了优秀作品奖,激动之余,觉得自己应该也做对了什么,这些做对的地方以后要继续努力。

1.逼着自己两次都参赛

背景: 1.我的确有些忙。工作需要每天加班非常忙碌,有一个1岁多的宝宝需要陪伴,工作日每天能用于全栈的时间不可能超过2小时,有时甚至没有。另外jdstore的比赛中我的队友也非常忙,经常连周末都要值班,没有办法有时间做比赛。

但是好在我给自己定了底线,无论如何都必须参赛!

虽然有了这个目标在心里,客观情况却是两次比赛都在比赛开始一周后才完成教材的基础练习(比如rails 101的3遍,和jdstore的至少一遍)。如果持续这样工作和生活太过忙碌的节奏就一定不可能参赛,因此我唯一的选择就是果断停止工作,因此两次比赛的最后一周我都请了一整周的年假。要知道,以我工作的忙碌程度,请1整周年假老板已经要杀人,连续两个月都请一周的年假是要面临非常大的心理压力的,后来证明我请假后还债也还的很辛苦,不过我仍然觉得这是我做的最正确的决定。

也是由于“必须参赛”的自我要求,当“铁血训练营”的广告一打出,我就毫不犹豫的加入。后来铁血训练营提出”已参赛同学要寻找援助对象,帮助未参赛的同学“的计划时,我虽然比较自卑且内向,但是还是第一时间鼓起勇气找到我心目中最牛的队伍”Jimmy&Anndo“来援助我。Jimmy和Anndo的帮助和鼓励给了我极大的信心,是最后我和yoona的作品能够在时间极度有限的情况下还算完整,并且完成多次迭代的关键。

有了必须参赛的底限,让我在几乎没有条件能够参赛并且获奖的情况下创造出了条件,因此给自己在关键的地方定一个底线这件事真的很重要。

2.正确的方式提问,不做伸手党。

不做伸手党这个原则也帮到了我的成长。之前看过“How to ask questions the smart way”这篇文章,在程序员的世界里,要学会提问,才更有可能会第一时间得到帮助,而一个懒惰的伸手党,一定会损失自己的信用从而害到自己。

因此在学习过程中,我遇到任何问题,第一件事绝对不会是直接上slack提问助教,而是自己寻找答案。方法包括,认真看错误提示,使用binding.pry在rails console里面寻找可能的原因,google问题等等。但是一旦在这个问题上耗费太久,也绝对不会过度纠结浪费时间,可以听老师建议去睡觉,或者再去slack上提问。但是即使是slack上求助的过程中,也要求自己不断思考可能的问题,google老师给出的解法,通常都会在助教的几步提示之后就又自己找到了问题。在解决问题之后,都会及时记录一篇logdown,记录错误症状,解决思路和解决方法,如果解决方法并不能理解,也不会要求助教花时间解释,而是自己自行google找解答,这样的原则帮助我迅速提高了解决问题的能力,并且避免重复犯错,受益匪浅。这个原则我相信运用在今后其他的学习中甚至工作中都很有价值。

【JDStore分享】加入购物车产品数量调整功能

看到J&A Select的产品页面可以调整加入购物车产品数量的功能觉得非常实用:


于是请教了Jimmy代码部分,非常感谢耐心的讲解,此篇记录一下学习心得。

javascript和html代码对应关系:

先总体看一下javascript和html中id/attribute的对应关系:

修改app/views/products/show.html.erb

  <% if @product.quantity.present? && @product.quantity > 0 %>
      <div class="quantity-box">
        <div class="left-box">数量 :</div>
        <div class="left-box">
          <%= link_to("-", "", id: "quantity-down", class: "quantity-actions") %>
          <input type="text" name="quantity" value="1" id="quantity" max="<%= @product.quantity %>" class="quantity-input">
          <%= link_to("+", "", id: "quantity-up", class: "quantity-actions") %>
        </div>
      </div>
    <%end%>

修改 app/assets/javascripts/application.js

/*===== Products#show - 调整购买数量 =====*/
$(document).on('turbolinks:load', function() {
  /*===== 增加购买数量 =====*/
  $("#quantity-up").click(function(e) {
    var num = parseInt($("#quantity").val()); //num变量存入输入的数量
    var numMax = $("#quantity").attr("max"); //numMax变量存入最大数量max=@product.quantity(库存)
    if (num < numMax) { // 判断输入数量是否大于库存
      $("#quantity").val(num += 1); //不大于库存的情况下数量可以加1
    }
    e.preventDefault(); //返回
  });

  /*===== 减少购买数量 =====*/
  $("#quantity-down").click(function(e) {
    var num = parseInt($("#quantity").val()); //num变量存入输入的数量
    if (num > 1) { // 判断输入数量是否大于1
      $("#quantity").val(num -= 1); //大于1的情况下可以数量可以减1
    }
    e.preventDefault();
  });

  /*===== 检查购买数量不能超库存 =====*/
  $("#quantity").blur(function(e) {
    var num = parseInt($(this).val()); // 取到当前id(this=#quantity)的数量,也就是用户输入的数量
    var numMax = $(this).attr("max"); //取数量上限max=@product.quantity,不能超过库存
    if (num > numMax) { //当输入数量超过库存时,数量变为库存量
      num = numMax;
    }
    else if(num < 0) { //当输入数量小于0的时候,数量变为1
      num = 1;
    }
    $(this).val(num);
    e.preventDefault();
  });
});

修改css

(此处完全粘贴Jimmy代码,仅供参考,css请尽量自己写符合网站风格的代码)

.quantity-box {

    height: 20px;

  .quantity-input {
    width: 50px;
    text-align: center;
    border: 1px solid #ccc;
    border-radius: 2.5px;
    outline: none;
  }

  .quantity-input:focus {
    border: 1px solid #000;
  }

  .quantity-actions {
    padding: 2.5px 10px;
    border: 1px solid #ccc;
    border-radius: 2.5px;

  }

  .quantity-actions:hover {
    border: 1px solid #eb5424;
  }

  .left-box {
   float: left;
 }

}

其他注意

javascript不会报错,只会默默的不执行,因此出了问题不好debug。
建议atom中安装ruby-block,可以检查是否少了结尾标签:


另外如果真的不生效可以通过加入alert('x')在相应位置想检查问题的位置:

如果成功执行到这里会弹出提示框:

【报错记录】上传heroku,google font字体不生效

错误描述

本地看到的字体和heroku上面的字体不一致。
本地

heroku

我的设置


解决方法

把 加载字体 的引入方式从 app/assets/stylesheets/application.scss 的 @import
改为在app/views/layouts/application.html.erb 头部的 < head > 标签中嵌入(注意如果admin使用了单独layout模版,并且admin有自己的字体,则需要在相应模版中嵌入)

参考教程:http://sylviablog.logdown.com/posts/1895012

【报错记录】修改applicaiton.js后推heroku报错

  1. 错误截屏

  2. 我修改的地方:

  3. 如何解决
    执行
    rake assets:clean
    rake assets:precompile

之后commit change
然后再推heroku

  1. 关于 rake assets:clean/precompile命令的学习


还有这篇:
https://stackoverflow.com/questions/9335803/confusion-about-rake-assetsclean-cleanup-on-the-asset-pipeline-in-rails

大致意思就是,如果修改了需要compile才能生效的文件,比如js,就需要执行rake assets:clean 和rake assets:precompile才能在production上面生效。

【报错记录】上传heroku,七牛云设置出错,导致无法上传图片

错误症状

新建产品/新建分类的时候上传图片报错,同样的heroku run db:seed也报错,因为同样在seed里面传了图片。

debug方法

heroku logs
heroku logs | grep -i error


看到是qiniuaccess key setup有问题

值得怀疑的几个地方有:

  1. application.yml 的 accsee_key 和 secret_key 的缩进,必须相对2个空格,不能用TAB, 只能用空格。
  2. 如果按照教程https://fullstack.xinshengdaxue.com/posts/706 ,使用SendCloud服务发送邮件,还要检查下下面这里是否设置
  3. uploader文件设置 最好把 storage :qiniu 换成
    if Rails.env.production?
    storage :qiniu
    elsif Rails.env.development?
    storage :file
    end
    
    这样本地local development环境h就使用本机file存储,heroku production环境就是用七牛云存储。 另外这样也可以:
    if Rails.env.production?
    storage :qiniu
    else
    storage :file
    end
    

我的问题

【好奇笔记】rails routes中collection,member 的区别

RESTful风格的路由动词默认有7个(分别为:index, show, create, new, edit, update, destroy)。
有时我们需要自定义路由,有collection,member,new方式

:member 是对单个实体进行操作,创建路由格式是: /:controller/:id/:your_method
:collection 是对实体集合进行操作,创建路由格式是: /:controller/:your_method
:new 是新建一个实体,创建路由格式是: /:controller/:your_method/new

举例来说:
resources :products do
member do
post :add_to_cart
end
end

将会添加一个路由:POST请求/products/1/add_to_cart路由到ProductsController的add_to_cart action,同时也会新建add_to_cart_product_url 和 add_to_cart_product_path这两个helpers。

【好奇笔记】.nil? .blank? .present? 的用法

method名 分类 说明
.nil? ruby的method 判断对象是否存在(nil)。不存在的对象都是nil的
.empty? ruby的method 对象已经存在,判断是否为空字段
.blank? rails的method 相当于 object.nil?
.present? rails的method 是.blank?的反面,!obj.blank? == obj.present?

【学习笔记】Active Record基础(超简单)

参考资料:http://guides.rubyonrails.org/active_record_basics.html

Active Record负责处理MVC中的Model,将model映射为数据库的表格,无需写sql能够便捷操作访问数据库。

model命名规则

Rails会自动将你的model class名称变成复数来寻找对应的数据库表格(table/schema):

  • 当model class名称是一个单词时,table名称是复数形式(符合语法规则)
  • 当model class名称是几个单词时,model class名称需要符合这种格式:CamelCase(单词首字母大写),而相应的数据库table名称单词全部小写,用下划线隔开,最后单词复数。
Model / Class Table / Schema
Article articles
LineItem line_items
Deer deers
Mouse mice
Person people
Content center-align

因此我们常用的Product.new, Product.first等首字母大写的单词实际上是model class名(类名)。小写的都是实例(instance)。

列名

Foreign keys:外键:singularized_table_name_id (e.g., item_id, order_id)
Primary keys :主键,id
系统保留列名:created_at(一条记录创建时间),updated_at(一条记录最后更更新时间),lock_version(锁定方式,不需修改等),type(继承的类名),(association_name)_type,(table_name)_count.这些Column是optional,可以选择使用,但是是系统保留名称,自己不可以定义相同名称。

创建一个Active Record Model

实际上就是subclass一个ApplicationRecord class,语法如下:

class Product < ApplicationRecord
end

我们使用rails g model 。。。的时候,rails帮我们已经写好了。这种写法可以让Active Record帮我们建立一个Product model和数据库中products表格的对应关系。

CRUD(Create, Read, Update,Delete。。读写数据) with Active Record

Create

Create创建record并保存

user = User.create(name: "David", occupation: "Code Artist")

New创建record并保存

user = User.new
user.name = "David"
user.occupation = "Code Artist”
Read

所有user record
users = User.all

第一条user record
user = User.first

找到第一个name=David的record
david = User.find_by(name: 'David')

找到所有name=Davie的record,并且按照创建时间降序排列
users = User.where(name: 'David', occupation: 'Code Artist').order(created_at: :desc)

Update

找到record,修改,保存才会更新。

user = User.find_by(name: 'David')
user.name = 'Dave'
user.save

简单写法

user = User.find_by(name: 'David')
user.update(name: 'Dave')

一次更新好几个attribute

User.update_all "max_login_attempts = 3, must_change_password = 'true'"
Delete

找到record,删除

user = User.find_by(name: 'David')
user.destroy

Validations

class User < ApplicationRecord
validates :name, presence: true
end

user = User.new
user.save # 返回 false/true
user.save! # 返回报错结果: ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
所有!都会返回报错结果:
save!
update!
create!

Migrations

rails db:migrate
rails db:rollback
这两个操作不受限于数据库类型,可以在MySQL, PostgreSQL, Oracle以及其他类型上使用。
http://guides.rubyonrails.org/active_record_migrations.html

【好奇笔记】.build,.new,.save,.create的区别

.build=.new

Build is just an alias of new.
https://github.com/rails/rails/blob/959fb8ea651fa6638aaa7caced20d921ca2ea5c1/activerecord/lib/active_record/relation.rb#L84

.build/.new 不会在数据库中创建信息条目,也不会save, 只会在内存中创建一个新的对象(object),好让view可以用这个object显示点什么东西,经常用于form。

.create

.build/.new不同的是,数据回被save到数据库。
但是.create 并不简单的等于.build/.new+.save
因为.create不会像.save一样返回成不成功。

.save

将数据存入数据库,返回true/false(成功/不成功)

上面这些后面加上(.save!,.build!,.create!,.new!),会在执行遇到错误时返回错误详细原因。