tag:renny.ren,2005:/feed?locale=ch任峻宏的小站2024-02-25T18:00:41+08:00https://assets.renny.ren/assets/avatar-aee4eab6be76620e714f3b9f059e80fcb82743462e4a66c5245c317893d659ae.icohttps://assets.renny.ren/assets/avatar-2-81bb0ac90ac481458e9cbda179aaaab39d0857c4a18c91a314fb8ded78fc11a2.jpgtag:renny.ren,2005:Article/452024-02-25T17:43:54+08:002024-02-25T18:00:41+08:00https://renny.ren/ch/articles/45Debugging Memory Leaks in a Next.js Application<p>Recently when I deploying one of my Next.js applications to production, after running the command <code>yarn build</code>, it stucks on <code>Creating an optimized production build ...</code> forever:</p><figure class="image image_resized" style="width:57.37%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/b1.png"></figure><p>This is kind of weird as it works well on my local machine but hangs here almost everytime I run it on production.</p><p>Checked online monitor and I noticed the high CPU and memory usage:</p><p><img class="image_resized" style="width:41.72%;" src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/b2.png"><img class="image_resized" style="width:41.73%;" src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/b3.png"></p><p>I've tried many ways to solve this problem:</p><ul><li>stop dev server and run <code>yarn build</code> again</li><li>remove .next folder</li><li>remove node_modules</li><li>remove yarn.lock and run <code>yarn install</code> again</li><li>upgrad node to 20.10.0</li><li>upgrad next.js to 14.1.0</li></ul><p>None of them worked, it just stuck on <code>Creating an optimized production build ...</code> forever, this issue drives me crazy!</p><p>To further diagnose the problem, I decided to deploy my application to another server on DigitalOcean to see if the issue was specific to the Aliyun ECS machine.</p><p>Unfortunately I got the same result. But additionally I got an error shows on the screen:</p><p><code>FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory</code> </p><pre><code class="language-plaintext"><--- Last few GCs --->
[1120666:0x6ef96b0] 87503 ms: Mark-Compact (reduce) 462.5 (486.3) -> 461.9 (485.8) MB, 690.73 / 0.00 ms (average mu = 0.260, current mu = 0.170) allocation failure; GC in old space requested
[1120666:0x6ef96b0] 88403 ms: Mark-Compact (reduce) 462.3 (486.3) -> 462.0 (486.5) MB, 762.13 / 0.00 ms (average mu = 0.207, current mu = 0.153) allocation failure; GC in old space requested
<--- JS stacktrace --->
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----
1: 0xcc0a72 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/usr/bin/node]
2: 0x1054530 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
3: 0x1054817 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/usr/bin/node]
4: 0x1273a55 [/usr/bin/node]
5: 0x128a578 [/usr/bin/node]
6: 0x1261a7e v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]
7: 0x1262d64 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]
8: 0x123f986 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/usr/bin/node]
9: 0x12314e4 v8::internal::FactoryBase<v8::internal::Factory>::AllocateRawWithImmortalMap(int, v8::internal::AllocationType, v8::internal::Tagged<v8::internal::Map>, v8::internal::AllocationAlignment) [/usr/bin/node]
10: 0x123380f v8::internal::FactoryBase<v8::internal::Factory>::NewRawTwoByteString(int, v8::internal::AllocationType) [/usr/bin/node]
11: 0x155dd05 v8::internal::String::SlowFlatten(v8::internal::Isolate*, v8::internal::Handle<v8::internal::ConsString>, v8::internal::AllocationType) [/usr/bin/node]
12: 0x10623fd v8::String::Utf8Length(v8::Isolate*) const [/usr/bin/node]
13: 0xdc1baf [/usr/bin/node]
14: 0x1a6aadd [/usr/bin/node]
Compiler server unexpectedly exited with code: null and signal: SIGABRT</code></pre><p>OK. Perhaps there is something to do with memory leak?</p><p>I searched online, maybe <span style="background-color:rgb(255,255,255);color:rgb(12,13,14);">the memory allocated to Node.js is insufficient.</span></p><p><span style="background-color:rgb(255,255,255);color:rgb(12,13,14);">I then use this command to</span> see the current value of <code>max-old-space-size</code>:</p><p><code>node -e ‘console.log(v8.getHeapStatistics().heap_size_limit/(1024*1024))’</code></p><p>the result is only 512MB.</p><p>OK, I increased the memory to 2GB:</p><p><code>export NODE_OPTIONS="--max-old-space-size=2048"</code></p><p>then run <code>yarn build</code> again to see if it works now.</p><p>After waiting for about 1min, the build process exited with the following error:</p><p><code>Compiler server unexpectedly exited with code: null and signal: SIGKILL </code></p><p>S**t! Why is this happening? I have no idea cause there is no more other logs to check.</p><p> </p><p>Eventually, I had to take the build process on my local machine. </p><p>I use Docker to build on my local and push to production to deploy the application. Unfortunately this is the only workaround I've found for now.</p><p>Many Next.js users faced the problem:</p><p><a href="https://github.com/vercel/next.js/discussions/60147">https://github.com/vercel/next.js/discussions/60147</a> </p><p><a href="https://github.com/vercel/next.js/issues/32314">https://github.com/vercel/next.js/issues/32314</a></p><p><a href="https://github.com/vercel/next.js/issues/54708">https://github.com/vercel/next.js/issues/54708</a></p><p>Some of them were able to solve it by the approaches I mentioned above, but none of those worked for me.</p><p> </p><h4>Reference</h4><p><a href="https://www.toptal.com/nodejs/debugging-memory-leaks-node-js-applications">https://www.toptal.com/nodejs/debugging-memory-leaks-node-js-applications</a></p>2024-02-25T18:00:41Z任峻宏tag:renny.ren,2005:Article/442023-09-18T15:46:26+08:002023-09-23T23:15:37+08:00https://renny.ren/ch/articles/44Troubleshooting: Resolving Devise Issue in an API-only Application<p>今天想给应用快速撸一个用户系统,注册登录,没想到搞了半天。上了 devise, 发现 current_user 在 sign_in 之后还是 <code>nil</code>. sign_in 本身是正常的,但是这个 current_user 只能 per request 有效。</p><p>原因:session-based authentication 在 API mode Rails application 中无法使用</p><p>解决思路:</p><ul><li>可尝试换用 token-based authentication <code>devise-jwt</code> (<a href="https://github.com/waiting-for-dev/devise-jwt">https://github.com/waiting-for-dev/devise-jwt</a>) 或 http authentication</li><li>切换回 regular rails mode,可能改动较大。在项目上级重新 rails new 覆盖原文件夹,然后处理冲突 (参考 <a href="https://stackoverflow.com/questions/36669981/how-do-you-convert-a-rails-5-api-app-to-a-rails-app-that-can-act-as-both-api-and">https://stackoverflow.com/questions/36669981/how-do-you-convert-a-rails-5-api-app-to-a-rails-app-that-can-act-as-both-api-and</a>)</li><li>在现有程序基础上引入相应的 middleware</li></ul><pre><code class="language-ruby">config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore</code></pre><p> </p><p>方法1 需要重新写代码,成本相对高,放弃</p><p>方法3 尝试了引入之后还是不行,以为是方向错了,实际可用以下方法检查:</p><p><code>rake middleware</code></p><p>还可在浏览器查看 cookie, 正常 sign_in, sign_out 之后应该会有 cookie change, 如果没有,那说明 application cookie set 有问题</p><p>方法2 尝试了之后发现还是不行,才明白不是 API-only 的问题,看浏览器里面 cookie 根本没有正常 set</p><p>后来发现是只解决了一半,还有一个跨域的问题,因为这次我这个应用是彻底前后端分离,不在一个端口,所以需要使用 <code>credentials</code> (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials">https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials</a>) 在前端请求的时候 来处理这种 cross-origin requests. 不然 cookie 在发送请求的时候就不会发过去</p><p> </p><p>当然了,rack-cors 也要配置一下,允许 credentials:</p><pre><code class="language-ruby">Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "127.0.0.1:3000"
resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true // Add this line
end
end</code></pre><p> </p><p>另外今天还被自己坑了一把,之前没用 devise 的时候,自己为了测试方便写了如下代码:</p><pre><code class="language-ruby">def current_user
@user = User.first
end</code></pre><p>我说怎么 sign out 了,current_user 还一直在,登出就是登不出去,devise 请求 检查半天... 被自己坑惨</p><p> </p><p>哦对了还有个问题,我项目里前端请求用的是 rails/request.js (<a href="https://github.com/rails/request.js">https://github.com/rails/request.js</a>) 这个库,还不支持请求的时候带 <code>credentials</code> option,我看已经有人 MR 了但是还没有 release, 先用着 axios 或者 fetch 都行。</p>2023-09-23T23:15:37Z任峻宏tag:renny.ren,2005:Article/432023-08-16T21:07:13+08:002023-08-16T23:27:23+08:00https://renny.ren/ch/articles/43How to Customize a Right-Click Menu in React<p>When building web applications, sometimes you might need to provide a customized right-click context menu to enhance the user experience. In this article, I'm going to explore how to create a custom right-click menu in a React application.</p><p>Before starting, it's worth mentioning that there are some existing libraries available that offer solutions for context menu (like <a href="https://github.com/vkbansal/react-contextmenu"><span style="color:hsl(210,75%,60%);"><u>react-contextmenu</u></span></a><span style="background-color:rgb(247,247,248);color:rgb(55,65,81);"> </span>or<span style="background-color:rgb(247,247,248);color:rgb(55,65,81);"> </span><a href="https://github.com/fkhadra/react-contexify"><u>react-contexify</u></a> or<span style="color:hsl(210,75%,60%);"> </span><a href="https://github.com/agjs/react-context-menu"><span style="color:hsl(210,75%,60%);"><u>react-conte</u></span></a><span style="color:hsl(210,75%,60%);"><u>xt-menu</u></span>). <span style="color:rgb(52,53,65);">If you don't want to use the library and</span><span style="background-color:rgb(247,247,248);color:rgb(55,65,81);"> </span>want to roll up your sleeves to create a customized solution tailored to your application's unique needs, let's move on!</p><h3>What is React Context Menu?</h3><p>A context menu, often referred to as a right-click menu, is a pop-up menu that appears when a user right-clicks on an element. </p><p><span style="background-color:rgb(255,255,255);color:rgb(27,27,27);">the <code>oncontextmenu</code> event is typically triggered by clicking the right mouse button. In the context of React, we can utilize the <code>onContextMenu</code> event to capture the right-click action. </span></p><p><span style="background-color:rgb(255,255,255);color:rgb(27,27,27);">For further details, you can refer to the </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event"><span style="color:hsl(210,75%,60%);"><u>MDN Web Docs</u></span></a><span style="background-color:rgb(255,255,255);color:rgb(27,27,27);"> or </span><a href="https://www.w3schools.com/jsref/event_oncontextmenu.asp"><span style="color:hsl(210,75%,60%);"><u>w3schools Doc</u></span></a></p><h3><span style="background-color:rgb(255,255,255);color:rgb(27,27,27);">Disable the Default Right-Click Menu</span></h3><p>To prevent the default context menu from showing up, we can use the <code>preventDefault()</code> method on the event object:</p><pre><code class="language-javascript">function Home() {
const handleContextMenu = (e) => {
e.preventDefault(); // prevent the default behavior when right-clicked
console.log("right click");
};
return (
<div onContextMenu={handleContextMenu}>
{/* Your content here */}
</div>
);
}
export default Home;</code></pre><h3>Creating a Customized Menu</h3><p>First, let's create a new component called <code>CustomMenu</code>. This component will render the menu items and handle the actions when a menu item is clicked.</p><p>This component takes two props: <code>handleMenuItemClick</code> for handling menu item clicks and <code>menuPosition</code> for positioning the menu based on the mouse coordinates.</p><pre><code class="language-javascript">import React from 'react';
function CustomMenu({ handleMenuItemClick, menuPosition }) {
const menuItems = ['foo', 'bar', 'item3'];
const handleClick = (item) => {
handleMenuItemClick(item);
};
return (
<div className="custom-menu rounded shadow bg-white absolute" style={{ left: menuPosition.x, top: menuPosition.y }}>
{menuItems.map((item) => (
<div className="cursor-pointer rounded px-4 py-2 hover:bg-slate-400" key={item} onClick={() => handleClick(item)}>
{item}
</div>
))}
</div>
);
}
export default CustomMenu;</code></pre><h3>Implementing the Custom Right-Click Menu</h3><p>Now, let's integrate the custom context menu into our main component.</p><p>When the user right-clicks, we'll set the <code>menuVisible</code> state to <code>true</code> and also store the mouse coordinates to determine the menu position.</p><pre><code class="language-javascript">import React, { useState } from 'react';
import CustomMenu from './CustomMenu';
function Home() {
const [menuVisible, setMenuVisible] = useState(false);
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
const handleContextMenu = (e) => {
e.preventDefault();
setMenuVisible(true);
// Store the mouse coordinates
const mouseX = e.clientX;
const mouseY = e.clientY;
setMenuPosition({ x: mouseX, y: mouseY });
// Add an event listener to handle clicks outside the menu
document.addEventListener('click', handleOutsideClick);
};
const handleOutsideClick = (e) => {
// Check if the click is outside the menu
if (!e.target.closest('.custom-menu')) {
setMenuVisible(false);
document.removeEventListener('click', handleOutsideClick);
}
};
const handleMenuItemClick = (item) => {
switch (item) {
case 'foo':
// Perform the foo action
break;
case 'bar':
// Perform the bar action
break;
default:
break;
}
setMenuVisible(false);
};
return (
<div onContextMenu={handleContextMenu}>
{/* Your other content here */}
{menuVisible && <CustomMenu handleMenuItemClick={handleMenuItemClick} menuPosition={menuPosition} />}
</div>
);
}
export default Home;</code></pre><p> </p><p>Now, let's take a look at the result:</p><figure class="image image_resized" style="width:97.43%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/iShot_2023-08-16_21.03.33.gif"></figure><hr><p>Feel free to customize the design and behavior of the custom menu to fit your application's style and requirements.</p><p>With this approach, you can create context menus that provide specific actions and options, making your application more user-friendly and efficient.</p>2023-08-16T23:27:23Z任峻宏tag:renny.ren,2005:Article/422023-06-16T19:05:34+08:002023-06-16T19:05:34+08:00https://renny.ren/ch/articles/42Using ActionCable and React to Create a Simple Chat App<blockquote><p>Online example: <a href="https://ac.aiichat.cn">https://ac.aiichat.cn</a></p><p>Code repository: <a href="https://github.com/renny-ren/action-chat">https://github.com/renny-ren/action-chat</a></p></blockquote><h3>Introduction</h3><p>In this article, I'm going to explore how to leverage the power of Rails ActionCable and React to build a web chat application. ActionCable provides a straightforward way to incorporate real-time features into Rails applications, while React serves as a powerful and flexible frontend framework. By combining these technologies, we can create an interactive and dynamic chat application. Let's dive into the details of how to implement this solution.</p><h3>Setting up the Environment</h3><p>Before we dive into the implementation details, make sure you have the following prerequisites in place:<br>- Ruby on Rails: Ensure that you have Rails installed on your local machine.<br>- React: Familiarize yourself with React and have a basic understanding of how React components and states work.</p><h3>Creating the ActionCable Channel</h3><p>To begin with, let's create an ActionCable channel for our chat application. In your terminal, run the following command to generate the channel:</p><pre><code class="language-ruby">rails g channel chat_channel</code></pre><p>This command will generate a <code><strong>chat_channel.rb</strong></code> file under the <code><strong>app/channels</strong></code> directory. Open this file and update it as follows:</p><pre><code class="language-ruby">class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "ChatChannel"
end
def unsubscribed
# Any cleanup needed when the channel is unsubscribed
end
def receive(data)
@message = user.messages.create(body: data["body"])
ActionCable.server.broadcast("ChatChannel", JSON.parse(@message.to_json))
end
end</code></pre><p><code><strong>stream_from</strong></code> method inside the <code><strong>subscribed</strong></code> specifies the channel you want to subscribe to and receive updates from. It means that whenever there is a new message sent to the <code>ChatChannel</code>, the clients subscribed to this channel will receive the message in real-time.</p><h3>Creating the ActionCable Consumer</h3><p>To establish a connection between the client and the server, we need to create an ActionCable consumer in our React application. Add the following code snippet where you want to initiate the connection:</p><p>app/javascript/channels/consumer.js</p><pre><code class="language-javascript">// Create a consumer object
const consumer = ActionCable.createConsumer();</code></pre><p>This code initializes an ActionCable consumer object, which acts as the bridge between the client and the server.</p><h3>Subscribing to the Channel</h3><p>Now, let's subscribe to the chat channel we created earlier. Use the following code snippet to subscribe to the channel:</p><pre><code class="language-javascript">// Subscribe to a channel
const subscription = consumer.subscriptions.create("ChatChannel");</code></pre><p>Ensure that the channel name passed matches the name of the channel you defined in your Rails application.</p><h3>Unsubscribing from the Channel</h3><p>If at any point you want to unsubscribe from the chat channel, you can call the <code><strong>unsubscribe</strong></code> method on the subscription object:</p><pre><code class="language-javascript">subscription.unsubscribe();</code></pre><p>This will effectively terminate the connection and stop receiving updates from the channel.</p><h3>Disconnecting the Consumer</h3><p>Finally, when you're done using the consumer object, it's good practice to disconnect it. Use the following code snippet to disconnect the consumer:</p><pre><code class="language-javascript">consumer.disconnect();</code></pre><p>This will release any resources associated with the consumer and prevent unnecessary connections.</p><p> </p><p>Online example: <a href="https://ac.aiichat.cn">https://ac.aiichat.cn</a></p><p>Code repository: <a href="https://github.com/renny-ren/action-chat">https://github.com/renny-ren/action-chat</a></p><h3>Reference</h3><p>Refer to the following resources for more detailed information and examples:</p><p><a href="https://javascript.plainenglish.io/integrating-actioncable-with-react-9f946b61556e">Integrating ActionCable with React</a></p><p><a href="https://dev.to/nkemjiks/simple-chatroom-with-rails-6-and-actioncable-3bc3">Simple chatroom with Rails 6 and ActionCable</a></p><p><a href="https://github.com/renatopanda/rails-sse-and-websockets">rails-sse-and-websockets GitHub Repository</a></p>2023-06-16T19:05:34Z任峻宏tag:renny.ren,2005:Article/412023-05-19T20:31:23+08:002023-06-20T22:06:43+08:00https://renny.ren/ch/articles/41纪念左耳朵耗子<figure class="image image_resized" style="width:72.05%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/0515.png"></figure><p><br>没有想到突然以这样的方式看到皓叔,在百度的热搜榜。</p><p>前几天看到这个噩耗时,我正在地铁上,非常震惊。一开始是不敢相信,然后感到错愕,从我上地铁到下地铁,我整个人是木在那里,一直在刷相关的消息,朋友圈已经刷屏了。</p><p>好几天过去了,今天忙完,看到了<a href="https://github.com/megaease/Remembering-Haoel">这个仓库</a>,里面记录了大家对左耳朵耗子的点滴回忆,我把这个仓库大家写的文字基本都看了一遍,然后自己也来写一写。</p><h3>初识</h3><p>大概是 2016 年吧,我还没毕业,那时偶然间刷到了酷壳的文章,很受启发,当时就收藏了。</p><p>后来又刷到了,我说这篇文章怎么写这么好,一看又是皓叔出品,果然是精品啊。</p><p>皓叔很多文章一看就是经过深度思考总结出来的,像《X-Y PROBLEM》,《谈谈我的“三观”》这些,都是很经典,一方面我发现找到了共鸣,有一些观点,有一些我认为的但是没有说出来的,皓叔说出来了,简直不能认同更多;另一方面让人感觉醍醐灌顶,深受启发。</p><h3>一面之缘</h3><p>我是普通野生程序员一枚,在前司上班时候,有一天,我们 leader 说邀请到了左耳朵耗子来给我们分享,我当时看了非常激动,期待着他的到来,没想到突然就能见到自己敬仰的人,想听听大佬会带来什么样的分享。</p><p>第二天皓叔来了,一身朴素的装扮,沉稳的技术人形象,背着个包,和在网上看到的照片上一模一样的衣服,甚至可以说是和修理工人有几分相似。</p><figure class="image image_resized" style="width:34.71%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/t0.png"></figure><p>白发苍苍,我看过他说的,那段时间家庭的事,父亲的事,一下头发就白了。</p><p>分享的时候我就坐他旁边,听得津津有味。我还记得他说他这人很喜欢总结,确实,在他分享过程中不难看出他自己独特的思考,他对于事情的认真的态度,以及钻研的精神,这些都是令我十分敬佩的。</p><p>就像 Kevin 说的,他聊了很多,他喜欢对比阿里和亚麻的企业文化,我当时还截屏了,这张截图一直保留到现在。</p><figure class="image image_resized" style="width:64.43%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/t1.png"></figure><p>分享完皓叔就要赶去机场了,空隙间和他聊了几句,还记得他说:“你们现在都叫我皓叔了啊…”</p><p>我想,虽然按辈分讲可以叫叔了,但看了大家写的,他应该不太喜欢这个称呼,哈哈。</p><h3>结尾</h3><p>我与皓哥其实没有太多交集,大部分时候我都是默默地看,看他发的文章,看他朋友圈的分享,偶尔点个赞,看他在推特上和别人深夜对线。<br>但我从他身上学到了很多,他的认知、他的思考、他的态度。我很佩服皓哥的钻研精神、分享精神和乐于助人的精神。</p><p>长期坚持深耕底层技术,不容易;博客坚持分享二十年,不容易;他说帮助他人收获最大的是自己,他说至少 45 岁之前不会写书因为觉得自己积累不够,他还痛恨那些出烂书的人,有自己的痛恨手册。</p><p>关注多年,就我个人主观片面的了解而言,皓哥是一个有原则、棱角分明、犀利、热爱技术、谦逊、对自己有高标准高要求的人。</p><p>虽然有时候他的一些言论我也无法认同,有时候感觉他怼人怼得太直接了,有时候觉得他太犀利了,但一个人不可能什么都是对的,这不妨碍其他的内容给人的正向激励和影响。<br>所以最近看到一些无脑的评论,我实在无法理解,还有各种黑他的,哈哈,不过这也正说明了他的影响力。</p><p>对于一些人来讲,可能无法理解失去这样一位前辈对我们是什么样的打击。</p><p>天妒英才,惋惜之余,我相信皓哥留下的文章和作品将会继续影响、激励着中国的程序员们。</p><p>最后贴上这句在任何平台都能看到座右铭:</p><figure class="image"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/t2.png"></figure><p><br> </p>2023-06-20T22:06:43Z任峻宏tag:renny.ren,2005:Article/402023-05-01T01:02:37+08:002023-05-30T23:49:42+08:00https://renny.ren/ch/articles/40Using SSE to Implement ChatGPT in Rails<p><span class="text-small"><i>中文版本 (Chinese version): </i></span><a href="https://ruby-china.org/topics/43052"><span class="text-small"><i>https://ruby-china.org/topics/43052</i></span></a></p><h3>Introduction</h3><p>When using ChatGPT, you may notice that the response is not returned all at once after completion, but rather in chunks, as if the response was being typed out:</p><figure class="image"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/stream3.gif"></figure><h3>About SSE</h3><p>If we check <a href="https://platform.openai.com/docs/api-reference/chat/create">OpenAI API document</a>, we can find that there's a param called <code>stream</code> for the create chat completion API.</p><blockquote><p><span style="color:rgb(110,110,128);">If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only </span><a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format"><strong>server-sent events</strong></a><span style="color:rgb(110,110,128);"> as they become available, with the stream terminated by a </span><code>data: [DONE]</code><span style="color:rgb(110,110,128);"> message.</span></p></blockquote><p>So what is SSE?</p><p>Basically, SSE, short for “Server-Sent Event”, is a simple way to stream events from a server. It is used for sending real-time updates from a server to a client over a single HTTP connection. With SSE, the server can push data to the client as soon as it becomes available, without the need for the client to constantly poll the server for updates.</p><p>SSE can be implemented through the HTTP protocol:</p><ol><li>The client make a GET request to the server: <code>https://www.host.com/stream</code></li><li>The client sets <code>Connection: keep-alive</code> to establish a long-lived connection</li><li>The server sets a <code>Content-Type: text/event-stream</code> response header</li><li>The server starts sending events that look like this:</li></ol><pre><code class="language-plaintext">event: add
data: This is the first message, it
data: has two lines.</code></pre><h3>Difference between SSE and WebSocket</h3><p>It looks like SSE is similar to WebSocket? they are both used for real-time communication between a server and a client, but there are some differences between them.</p><ol><li><span style="background-color:rgb(255,255,255);color:rgb(31,35,40);">SSE provides unidirectional communication only (server -> client). WebSockets on the other hand give us real-time bidirectional communication.</span></li><li>SSE is an HTTP-based technology, while WebSocket is a TCP-based technology. This means that SSE is built on top of the HTTP protocol and uses long polling techniques to achieve real-time communication, while WebSocket directly sends and receives data over the TCP connection, which can achieve faster real-time communication.</li><li>Another difference is in how they handle re-connections. SSE automatically attempts to reconnect to the server if the connection is lost, whereas WebSocket requires the client to initiate a new connection if the connection is lost.</li></ol><p><span style="background-color:rgb(255,255,255);color:rgb(0,0,0);">In conclusion, SSE seems like a simpler alternative to websockets if you only need to have the server send events. </span>WebSocket, on the other hand, is more powerful and can be used in more complex scenarios, such as real-time chat applications or multi-player games.</p><h3>Workflow</h3><p>Now let's talk about how to use OpenAI's API to receive Server-Sent Events (SSE) on your server, and forward those events to your client using SSE.</p><figure class="image"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/wf2.png"></figure><p>Here is the workflow for implementing SSE in Rails to use ChatGPT:</p><ol><li>The client creates an SSE <code>EventSource</code> to server endpoint with SSE configured.</li><li>The server receives the request and sends a request to OpenAI API using the <code>stream: true</code> parameter.</li><li>The server listens for server-side events from the OpenAI API connection created in step 2. For each event received, the server can forward that message to the client. This keeps our API secret because all the communication to OpenAI happens on our server.</li><li>After the client receives the entire response, OpenAI sends a special message to let us know to close the connection. The <code>[Done]</code> message signals that we can close the SSE connection to OpenAI, and our client can close the connection to our server.</li></ol><h3>Use Rails as server API</h3><p>After understanding SSE and the workflow, we start coding the entire process.</p><ul><li>client setup</li></ul><pre><code class="language-javascript">const fetchResponse = () => {
const evtSource = new EventSource(`/v1/completions/live_stream?prompt=${prompt}`)
evtSource.onmessage = (event) => {
if (event) {
const response = JSON.parse(event.data)
setMessage(response)
} else {
evtSource.close()
}
}
evtSource.onerror = () => {
evtSource.close()
}
}</code></pre><p>We uses the <code>EventSource</code> API to establish a server-sent event connection. And the <code>onmessage</code> event will be triggered when a message is received from the server.</p><p> </p><ul><li>server setup</li></ul><pre><code class="language-ruby">class CompletionsController < ApplicationController
include ActionController::Live
def live_stream
response.headers["Content-Type"] = "text/event-stream"
response.headers["Last-Modified"] = Time.now.httpdate
sse = SSE.new(response.stream, retry: 300)
ChatCompletion::LiveStreamService.new(sse, live_stream_params).call
ensure
sse.close
end
end</code></pre><p>We include the <code>ActionController::Live</code> module to enable live streaming.</p><p>As we mentioned above, the <code>content-type</code> response headers should be set to <code>text/event-stream</code>.</p><p>Please note that the stream response in Rails 7 does not work by default due to a <a href="https://github.com/rack/rack/issues/1619">rack issue</a>, you can check for more details on this issue.</p><p><span style="background-color:rgb(255,255,255);color:rgb(31,35,40);">it took me hours to find out the issue is related to rack…</span><span style="background-color:rgb(255,255,255);color:rgb(101,109,118);"> </span>Rails includes <code>Rack::ETag</code> by default, which will buffer the live response.</p><p>anyway, this line is necessary if your rack version is <code>2.2.x</code>:</p><p><code>response.headers["Last-Modified"] = Time.now.httpdate</code></p><p> </p><ul><li>OpenAI API</li></ul><pre><code class="language-ruby">module ChatCompletion
class LiveStreamService
def call
client.create_chat_completion(request_body) do |chunk, overall_received_bytes, env|
data = chunk[/data: (.*)\n\n$/, 1]
send_message(data)
end
end
def send_message(data)
response = JSON.parse(data)
if response.dig("choices", 0, "delta", "content")
@result = @result + response.dig("choices", 0, "delta", "content")
end
sse.write(status: 200, content: @result)
end
private
def client
@client ||= OpenAI::Client.new(OPENAI_API_KEY)
end
end
end</code></pre><p>The code above uses an <a href="https://github.com/renny-ren/openai_ruby">OpenAI gem</a> to send request to OpenAI API, it's a simple Ruby wrapper and support streaming response.</p><p>BTW, If you're using Hotwire in Rails, you can check <a href="https://gist.github.com/alexrudall/cb5ee1e109353ef358adb4e66631799d">this guide</a>.</p><p> </p><p><span class="text-big"><strong>That's all! Thanks for reading!</strong></span></p><p>demo website: <a href="https://aiichat.cn">aiichat.cn</a></p>2023-05-30T23:49:42Z任峻宏tag:renny.ren,2005:Article/392022-04-17T19:33:56+08:002022-04-17T19:44:33+08:00https://renny.ren/ch/articles/39Refresh Myself<p>I think we cannot keep doing the same thing for a long time, otherwise our concentration, focus and efficiency will decrease.</p><p>So during the weekend, I prefer to arrange multiple different things in a period of time, not simultaneously, just switch to another task when I get bored with what I'm doing right now. For example, after playing the piano for an hour, I would try to read a few pages of a book, or practice calligraphy for a while, that helps me refresh myself.</p><p>Moreover, the time that one person can work flat out each day is very limited (I mean in the flow state).</p><p>As a human being, after all, we are not like machines, it's unrealistic that expect us to work with high efficiency all day long.</p><p>Ironically, trying way too hard sometimes has the opposite effect. It's like I put an enormous pressure on myself every now and then, but the result turns out to be just the opposite of my wish.</p><p>So, as long as we can make progress every single day, that is still good.</p><p>"I am a slow walker, but I never walk backwards."</p>2022-04-17T19:44:33Z任峻宏tag:renny.ren,2005:Article/382022-04-10T22:50:03+08:002022-04-11T00:48:04+08:00https://renny.ren/ch/articles/38战痘<p>真正下定决心要治我这个痘痘是去年5月份。</p><p>十多年来,从初中开始,我脸上的痘痘一直没断过,你要说是青春痘吧,青春期早就过了。</p><p>这个问题,周围的人都说过我很多次了,家人、朋友、同学等,甚至初中时还因此获得了相关的外号。</p><p>但对这些我都一直不以为然,一方面我一直没太在意这方面的调侃,另一方面我向来是极度忽视自己的外表方面,觉得就让它长吧,我看能长多久,总有天会消失的吧。</p><p>直到后来有天我看了看镜子,好像确实不行,至少给别人第一印象不好啊,多少还是得整一下吧。刚好那段时间在家有空,于是我开始了我的治疗之路。</p><p>直接去三甲医院皮肤科,医生诊断为重度囊肿型痤疮,本来我还以为搞点药啥的就行了,后来发现不是这么简单的事。医生说你这十多年的问题,我哪能保证几天给你治好呢,不可能的是吧。</p><p>对,想来也是。</p><p>接下来,每周都往医院跑,因为这个治疗是一个长期的过程。</p><p>总的来说,各方面成本都挺高的,前前后后用了十来种药,大量的时间成本,花费了几大千,也导致了我生活上的许多不便,还诱发了其他问题……</p><p>好在最后的结果差强人意,这个历程就不详细描述了,总之我也算是久病成医,总结了一些经验了。</p><p>最大的感受就是,治疗近一年来,我最近发现我从一个无辣不欢的人变成了微辣都吃不住了。同时也真羡慕那些皮肤好的人,因为和他们相比,我需要花费大量的时间、精力和开销,才能接近正常。</p><figure class="image image_resized" style="width:37.77%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/IMG_20220405_180324.jpg"></figure>2022-04-11T00:48:04Z任峻宏tag:renny.ren,2005:Article/372022-03-06T21:57:03+08:002022-03-06T21:57:03+08:00https://renny.ren/ch/articles/37逆向的能量——生活需要批评者<p>在日常生活和工作中,有两种能量,一种是正向的:激励;另一种是逆向的:批评。</p><h3 style="text-align:center;"><strong>1</strong></h3><p>先说说正向的。</p><p><span style="background-color:rgb(255,255,255);color:rgb(18,18,18);">美国心理学家</span><a href="https://www.zhihu.com/search?q=%E8%B5%AB%E8%8C%A8%E4%BC%AF%E6%A0%BC&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2331765014%7D"><span style="background-color:rgb(255,255,255);color:rgb(18,18,18);">赫茨伯格</span></a><span style="background-color:rgb(255,255,255);color:rgb(18,18,18);">提出了一个双因素理论:激励-保健理论。该理论认为有两种不同的因素影响人们的工作行为:</span></p><ol><li>保健因素,与环境和条件有关,如<span style="background-color:rgb(255,255,255);color:rgb(18,18,18);">工作环境、工资薪水、公司政策、个人生活、管理监督、人际关系等,</span>能防止人产生不满意感的一类因素;</li><li>激励因素,如<span style="background-color:rgb(255,255,255);color:rgb(18,18,18);">成就、承认、工作本身、责任、发展机会等,</span>能促使人们产生满意感的一类因素,<span style="background-color:rgb(255,255,255);color:rgb(18,18,18);">当激励因素缺乏时,人们就会缺乏进取心,对工作无所谓。</span></li></ol><p>可以看出,前者是消除不满,后者是产生满足感,这是不一样的。<span style="background-color:rgb(255,255,255);color:rgb(51,51,51);">不具备保健因素时将引起强烈的不满,但具备时并不一定会调动强烈的积极性。</span></p><p><span style="background-color:rgb(255,255,255);color:rgb(51,51,51);">好了,上面都是摘抄的资料,从中可以看出,激励对我们来说还是非常重要的。</span></p><p>这也是我为什么喜欢一些鸡汤的原因,鸡汤对于我来说,至少在当下那一刻,可以给我提供一些能量,我相信人都是需要被鼓励的。</p><p>其实说白了鸡汤就是把一些你已经知道的东西、道理,换个方式给你表达出来,然后你听了觉得很有道理、有同感,觉得受到了肯定、激励。</p><p><span style="background-color:rgb(255,255,255);color:rgb(51,51,51);">我最近在学英语的过程中遇到了一位非常好的老师,我第一次看到我曾经与众不同的学习英语的观点有一位老师讲了出来,并且讲得非常好。这对我来说是很大的一个正能量。</span></p><p><span style="background-color:rgb(255,255,255);color:rgb(51,51,51);">"世有伯乐,然后有千里马。千里马常有,而伯乐不常有。" 我很赞同这句话,一个能识别人才的人,比人才本身更难得、更可贵。</span></p><h3 style="text-align:center;">2</h3><p>正向的激励是一种能量,逆向的批评也是一种能量。</p><p>如果一个人整天都被表扬、赞赏,时间长了就容易飘、容易迷失自我。永远不要觉得自己有多了不起,好大坨烟锅巴踩不熄, 当你飘的时候,劈你的雷就在来的路上。</p><p>还记得初中政治讲了一个词叫:诤友,即<span style="background-color:rgb(255,255,255);color:rgb(51,51,51);">能够直率坦言、敢于指出你缺点错误的朋友。如果身边有这样的朋友能正确指出我的问题,我肯定会感激的,因为那是在帮助我进步。</span></p><p>在前公司,我们每个周都会做 postmortem, 即固定一个时间,放下电脑,拿着纸和笔,大家坐在一起复盘,说说自己过去一周有哪些地方做得不好,还需要改进。我觉得这种精神是值得鼓励的,“吾日三省吾身”,才能成为更好的自己。</p><p>所以批评与自我批评是很重要的。我也意识到:如果一个人公开承认自己的错误,人们会觉得他更加值得信任,这是人的本性。</p><p>在 Netflix CEO Reed 的《No Rules Rules》一书中写到:</p><blockquote><p>当你取得成功的时候,要轻描淡写地带过,或者让别人来说。</p><p>当你犯了错误的时候,一定要清楚而响亮地说出来。这样,其他人就可以从你的错误中学习,从你的错误中获益。</p></blockquote><p>我不明白为什么有的人会拒绝别人给他提任何意见、建议,一旦有说他不好或是哪里不对的观点,他的第一反应就是为自己辩解、反驳,先给自己树立一个屏障,然后去对抗别人。拒绝别人的同时,也是拒绝了一个进步、学习的机会,多么可惜。</p><h3 style="text-align:center;">3</h3><p>最后总结给自己的话:如果有人夸你,那你开心笑笑就好,别太得意;如果有人批评你、骂你,你想想是为什么,是不是自己哪里做得不够好,如果对方说得有道理,就借鉴一下,努力改进,如果是无脑喷,左耳朵进右耳朵出就行,别影响自己的心情。</p><p>不骄不躁,不卑不亢,砥砺前行!</p>2022-03-06T21:57:03Z任峻宏tag:renny.ren,2005:Article/362022-02-27T21:18:41+08:002022-03-06T20:50:05+08:00https://renny.ren/ch/articles/36人活着必须要有目标<h4>1. 没有目标的人生毫无意义</h4><p>没有目标的人生,就像航行在大海中的船失去了方向。</p><p>我觉得不管目标大与小,但必须得有。如果没有目标,人会很容易失去方向,而且自己还不知道,就是说你不知道自己每天在做什么,慢慢地就迷失了自我。</p><p>过段时间后回望才发现,啊,原来我浪费了这么多光阴。</p><p>另外,在做事情的时候如果没有目标,就容易缺乏紧迫感,反正做到哪算哪呗,也没有一个验证结果、检验的步骤,最后时间花费了,却不知道获得了什么。</p><blockquote><p>最糟糕的时间管理,就是将毫无意义的事情做得十分圆满。</p></blockquote><h4>2. 实现目标要有具体可实施的计划</h4><p>光有目标当然不行,立了 flag 就结束的例子比比皆是。为了实现目标,接下来得有具体的计划。</p><p>说到计划,我这个人总是喜欢计划。我习惯每年用一本挂历挂在墙上,几乎每天都要列明天的计划,每个月搞个月计划,每年制定目标,对于未来5年有比较清晰的规划,等等。</p><p>上次我看到有个二十多岁的人说他规划自己的人生已经规划到了70岁,我觉得这就有点夸张了,毕竟人生中变数太多(能不能活到70都不一定呢)</p><p>话说回来,为什么我明知计划赶不上变化,还是这么喜欢计划呢。</p><p>一方面可以说是一种心里安慰吧,我喜欢井井有条的生活,我喜欢列好一件件事,然后完成一件打一个勾。总觉得未来不够清晰,就像人都讨厌不确定性一样,总想把它消灭。</p><p>当然,如果有一天真的把所有不确定性都消灭,一切都变为确定,那人生也就没有意思了。</p><p>我是一个讨厌所谓“稳定”、“循规蹈矩”的人,正是因为有各种各样的变化,才让我们的人生变得丰富多彩,而面对这些波澜,你得有自己的应对方式,才能处变不惊。</p><h4>3. 做一个实干的人</h4><p>不是说啥事都得计划一通,有时候,想到的事情就应该马上去做,不要拖延。</p><p>老是计划,却不行动,最后的结果就是 to do list 永远一大堆,做都做不完。</p><p>比如在上网的时候,加入收藏夹的东西就等于吃灰去了,再也没打开看过,这样的情况相信大家都遇到过。</p><p>所以 just do it 是正确的原则,万事开头难也是真的。</p><p>很多时候会发现,如果真正开始着手做一件事,其实已经成功了一半了,至少你开始了。</p><p>而类似加入收藏夹这种事,只是一个心里安慰而已,有没有这个动作并没有半毛钱区别。</p><p>所以,说到底还是得做一个实干的人,just do it!</p>2022-03-06T20:50:05Z任峻宏tag:renny.ren,2005:Article/352022-02-20T19:19:41+08:002022-02-20T19:46:23+08:00https://renny.ren/ch/articles/35《第一人称单数》读书笔记<p>我很少读小说,这次被简介吸引了一下,毕竟村上春树还是挺出名的,《挪威的森林》我也没有读过,于是买了这本《第一人称单数》来试试。</p><p>这是本短篇小说集,确实很短,里面一共8个故事组成,花不了多少时间就可以读完了。</p><p>这八个故事,可以说是没有什么关联的,讲的都是些没有缘由的事情,读的时候总有种奇怪的感觉,但是读完后回味比较长。</p><p>村上春树已经七十多岁了,这些应该是他年轻时的一些回忆,进行了加工而成,有一定的文学艺术在里面。可以看出写的都是平常生活中的小事,但读起来并不觉得无聊。</p><p> 文中有写到青春的迷茫,有写到都市人无处排解的孤独和无奈(能体会到日本社会环境),还有对男女欲望、对性直白的描写,感觉这是比较少见的,我又去搜了下《挪威的森林》介绍,也有类似的内容,难怪之前听说村上的作品比较成熟。</p><p>我觉得前面几篇故事是相对比较精彩的。</p><p>开篇第一句话:</p><blockquote><p>我要写的,是一个女人的故事。不过,我对她的了解几乎可以说是<strong>一点也没有</strong>,就连她的名字和长相也想不起来。</p></blockquote><p>这篇讲述了两个再也没有相见的男女的一段故事。几十年来,反复拉开的抽屉、被珍藏的褪色的诗歌集,还有,不得而知的她的后续。</p><blockquote><p>我想我们 </p><p>不会再相见了</p><p>又想我们</p><p>不可能不会再相见</p><p>还会再见面吗</p><p>还是就这样</p><p>结束了呢</p></blockquote><p>摘抄一个金句:</p><blockquote><p>喜欢一个人,就好比是得了什么,不在医保范畴内的精神疾病。</p></blockquote><p>这是第一篇故事《在石枕上》。</p><p>后面还讲了会说话的猴子,讲了作者收到的奇怪的邀请函,讲了关于记忆断片等等故事。</p><p>透过文中的描写,感觉我一个局外人也可以在脑海里很具象地浮现出作者当时的场景,仿佛身临其境。</p><p>读完之后,故事里带给人的情感回味也能引起一些共鸣吧。</p><p>总的来说,这本书没事当作听故事看看还是不错的。</p><p> </p><figure class="image image_resized" style="width:22.5%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/IMG_20220220_190403.jpg"></figure>2022-02-20T19:46:23Z任峻宏tag:renny.ren,2005:Article/342022-02-13T16:12:29+08:002022-02-13T16:12:29+08:00https://renny.ren/ch/articles/34卸载英雄联盟<p>上个月底的某一天,凌晨2:48分,我又一次把英雄联盟卸载了。</p><p>这是我第二次卸载。上次是在21年的3月份卸载的,然后今年1月初又下载了。</p><p>原因是,我发现我做很多事情都容易上瘾,不卸载的话,我总是会想去玩,而且一玩就停不下来。</p><p>按理说我自制力并不差,我也做了不少逆人性的事情,但是为什么每次玩英雄联盟就停不下来呢?</p><p>说好听点,我把它总结为对胜利的渴望和对卓越的追求。</p><p>输了想再来,我一定要赢一把!这是对胜利的渴望。比如连跪10把,会一直认为下一把一定能赢了,一定要再玩一把。</p><p>赢了想再来,我今天一定要打个5连胜!这是对卓越的追求。赢多少局才会满足呢?两连胜?五连胜?十连胜?不,我发现永无止境。</p><p>这么看来,我似乎是一个很在乎结果的人(至少在玩英雄联盟这件事上是的),玩游戏本来是为了放松自己、为了开心,然而我成了不在乎过程,就是想赢一把,再赢一把。玩到凌晨两三点了,人都困得快睡着了,还想着要赢一把,所以这到底是我在玩游戏还是游戏玩我呢?</p><p>其实不仅仅是打游戏,我似乎做很多事情都是这样。</p><p>弹琴能弹到凌晨三四点、写字写通宵、写代码兴奋了写通宵,这些都是有过的。</p><p>简单来说,当我做自己喜欢的事情的时候,是容易陷进去的,做一件事就认认真真做一件事,有始有终,不搞出点名堂来誓不罢休。</p><p>当然了,我也不是任何时候都是这个状态,如果是和朋友或者是和其他人一起,应该是在乎过程更多一些。而自己一个人玩英雄联盟的时候,就喜欢单排,搞点有成就感的事情。</p><p>另一方面,英雄联盟一局游戏的时间比较长,一旦玩了半天输了,沉没成本就比较大,就总是和自己过意不去。</p><p>大概算一下,我已经打了数千把,平均每把就按20分钟算,4000把就是8万分钟,也就是说我至少是花了1000多个小时在这上面的。然而我通过打英雄联盟收获了什么呢?并没有多少收获,除了玩的过程中的快乐,剩下的就是玩完之后一次次的忏悔。</p><p>与其这样不如直接卸载了,就不要去碰它,把这些大把的时间花在别的更有意义的事情上,提升自己。</p><p> </p>2022-02-13T16:12:29Z任峻宏tag:renny.ren,2005:Article/332021-12-29T03:11:37+08:002021-12-30T00:07:30+08:00https://renny.ren/ch/articles/33我的 2021 年总结<p>还记得在<a href="https://renny.ren/ch/articles/32">去年总结</a>的最后我写道</p><blockquote><p>“<span style="background-color:rgb(255,255,255);color:rgb(36,41,46);">相信明天不会比今天更糟糕,当然,如果还能更糟糕,那今天的糟糕又算什么呢</span>”</p></blockquote><p>结果没想到的是,确实,今年可以说是比去年更糟糕。</p><p>我小时候在单亲家庭长大,我爸又长期不在家,所以在那几年,爷爷奶奶在我成长过程中扮演了重要的角色,虽然我平时和他们也没什么话说,没有共同语言,但我心里是有他们的;虽然曾经那些早上催我起床、晚上催我睡觉的唠叨,以及上大学后比别人更频繁的电话、频繁地催我找女朋友,甚至让我感觉厌烦,但我从内心里知道,那是他们对我的关心,他们对我的好,一点一滴我都记在心里。</p><p>今年,两位亲人都相继离开,也让我充分感受到生命的脆弱。</p><p>面对死亡,是什么感受呢?经历之后的我总结一下,其实有几个阶段:</p><ul><li>首先是懵圈。某一天你突然接到电话,人没了,第一反应真不是多悲伤,因为脑子是一片空白的,懵。</li><li>第二是难受。我用了“难受”这个词,只是因为我找不到别的更合适的形容词了,这个词也不见得能准确地形容,但它可能是最接近的词了(英文有个词叫 agony,大概差不多)。这种难受不同于一般的悲伤,它不一定是那种夺眶而出的激烈,但确是一种十分十分低落的情绪,我也是第一次体会到。</li><li>然后是接受。在经历了痛苦之后,会慢慢开始接受,接受亲人已经离开的现实。</li><li>最后是麻木+持续且间接的情绪低落。同样的事情见多了,人就会麻木了,死亡也是一样。对于那些老人来说,他们不是不悲伤,只是他们已经见得太多了。你会麻木,但同时一旦遇到某个场景、某些特定时候,你还是会时不时地想起、难受。我曾想,什么时候能走出来呢?我看到一句话我觉得说得对,答案是永远都走不出来。是的,说走出来的,其实只是慢慢地隐藏在了心底,成了一个别人进不来、你也出不去的死角。</li></ul><p>亲眼看着奶奶在我面前离开,那一刻会觉得,世间没有什么重要的事了,身边一切都静止了。</p><p>我害怕传播负能量,所以,这里就告一段落吧。</p><p>今年,在各方的催找对象的情况下,对于爱情我还是保持一个十分保守的态度。我开始更多地向内求,我的爱好也越来越内敛,在原来弹琴的基础上,发展成了“琴棋书”,此外还有看书、健身、学外语等等,忙得可谓不亦乐乎。</p><p>我翻了下今年定的目标,大体都实现了,总的来说还是差强人意的,至少我不曾虚度光阴,不负自己。</p><p>这一年,那些一个个练琴、写字、看书到凌晨的夜晚,还有曾通宵写代码的我,让我看到了自己内心不灭的火焰。</p><p>这一年,那一次次的坚持,一次次的健身,锻炼后的大汗淋漓,让我感受到了运动的快乐,虽是体力上的劳累,但也是脑力上的放松。</p><p>这一年,我尝试了一些新的东西,也培养了一些新的爱好,还养成了一些新的好习惯。当然,也养成了坏习惯,那就是熬夜。我发现我的睡觉时间变得越来越晚,我越来越觉得时间不够用,因为我想做的事情太多太多。</p><p>这一年,我有了充分的独处时间,让我有更多的时间提升自己的认知、沉淀自己。我一个人尝试了很多事情,我非常享受这些时光,我在想,这样的独处时光其实是非常难得的,以后说不定还没这么多机会呢。</p><p>我越来越意识到,适当把目光多放在自己身上是没有错的。因为人就是这么的复杂,“世上唯一不变,是人都善变”,有时回望过去的自己,都是能看到明显的变化的。我自己都还没琢磨透自己呢,一天天懵懵懂懂的,刚好写到这里突然发现,还有10分钟就满26岁了。</p><p>一年又一年,时光飞逝。</p><p>突然不知道写啥了,今年就写到这里吧。</p><p>我又要开始展望下一年了,我就不信,明年还有什么更糟糕的东西?</p><p>话说这两年,经历了初恋分手,经历了从未在一起和最终没在一起,经历了工作上的碰壁,经历了亲人的离开,生死之外无大事,我还有什么好怕的?</p><p>行到水穷处,坐看云起时。到了深渊,今天以后的每一天,都只会越来越好。</p><p>Life is 10% what happens to you and 90% how you react to it. (人生有 10% 是发生在你身上的事,剩下 90% 取决于你的反应)</p><p>共勉!</p><p> </p><figure class="image image_resized" style="width:50.75%;"><img src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/t2.png"></figure>2021-12-30T00:07:30Z任峻宏tag:renny.ren,2005:Article/302021-07-24T14:38:08+08:002021-07-24T14:47:51+08:00https://renny.ren/ch/articles/30记我的爷爷<p>上周五,2021年7月16日,下午两点过,我接到电话,得知爷爷走了。</p>
<p>第一反应是懵的,因为之前没有任何准备,突然听到这么一个消息,大脑一片空白,不敢相信。</p>
<p>直到我看到照片,看到视频,看到门前摆满了花圈、坐满了人,我才意识到,爷爷是真的走了。</p>
<p>我知道这一天总会到来,但我没有想到,会这么快。</p>
<p>在这之前,爷爷身体不好,卧病在床已多日,�但不久前我们通电话的时候,他还似乎一切正常。所以突如其来的噩耗,让我感到一丝惊讶。</p>
<p>今年三月份,爷爷骨头断裂,在医院痛得不行,我东拼西凑拿出了一万五,爷爷做了手术。没想到不到四个月,爷爷就走了。</p>
<p>我也问过我自己,这个钱花得值不值,答案是没有什么值不值的,在那种情况下,�做手术能缓解病痛,�所以我毅然决然地支持。</p>
<p>我想,如果爷爷当时没做手术,是不是可能活得更久呢?我不知道。但我知道的是,如果当时我没拿出钱,而几个月后爷爷走了,这一定会成为我一辈子的遗憾。我不想后悔。</p>
<p> </p>
<p>坐夜的晚上,我守在门前,一个通宵。真的有一种难受,连眼泪都不会流。</p>
<p>半夜4点的时候,狗竟然也没睡,不知道从哪里跑出来了,朝着我大叫不停。我发现它在找吃的,也许是饿急了吧,这几天可能也没人管他。我去给它找了点吃的,洗干净,扔给它。它很快就吃了,果然是饿坏了。吃完之后,它也不睡觉,还去屋里转了转,似乎它也知道爷爷走了。</p>
<p>很快就5点了,我们一路护送爷爷上了山、下葬。</p>
<p>回到家之后,我睡了一会,醒来后,我稍微回过神了,一幕幕场景浮现在我眼前:</p>
<p>- 那时候快递还不那么发达,没有配送服务,快递走邮政 EMS 寄到了邮局,爷爷陪我去取快递</p>
<p>- 以前我买了个篮球架,爷爷背着篮球架回家,我们一起组装好</p>
<p>- 初中时,早上叫我起床上学,晚上催我睡觉,给我做饭。经常问我想吃什么,几点放学,要不要回来吃饭(最爱吃爷爷做的小土豆)</p>
<p>- 上次见面离开时,爷爷颤抖的手递给我两颗糖果</p>
<p>- 教他用手机,怎么看照片,怎么看新闻</p>
<p>- 上一次通话,爷爷说我今年运气会很好。我问他身体好点了没,他说没什么问题,不用担心我。</p>
<p>- 爷爷最后一次给我打电话,我在忙没有接到,回过去的时候,他也没有接到。</p>
<p>……</p>
<p>我的脑海里闪过了太多片段,眼泪忍不住地流。我已经记不得上次流泪是什么时候了,至少是很多年前了。</p>
<p>轻描淡写的文字无法表达我的感受,这个世界上也没有真正的感同身受。道理大家都懂,但只有真正亲身经历的人,才会有体会。</p>
<p> </p>
<p>我知道,沉溺在悲伤里,只会越陷越深的。时间会冲淡一切,并不是时间本身会冲淡一切,而是在一段时间的长河中,大大小小的事情会把以前的事情冲淡。</p>
<p>生死之外无大事,大事都经历过了,人生又还有什么好怕的呢?</p>
<p>要做一个向前看的人,乐观生活,坚强地面对人生。</p>
<p>亲人永远活在心中,这样就不会有真正意义的死去。把他们当做努力的动力,生活的信仰,让他们为你骄傲。</p>
<p> </p>
2021-07-24T14:47:51Z任峻宏tag:renny.ren,2005:Article/312021-07-17T15:05:35+08:002022-03-07T13:22:45+08:00https://renny.ren/ch/articles/31Migrate from Webpacker to Vite 从入门到放弃<h3>前言</h3><p>听闻 <a href="https://vitejs.dev/">Vite</a> 速度很快,最近开始考虑把项目里面的 webpack 打包换成 Vite</p><p>虽然折腾了一波之后最后放弃了,还是记录一下过程,算是一篇踩坑记录</p><p><br>由于是 Rails 项目,我就直接使用封装好的 <a href="https://github.com/ElMassimo/vite_ruby">vite_ruby</a> 开始搞了</p><p>看了下官方文档,感觉很清晰,再根据 <a href="https://github.com/ElMassimo/pingcrm-vite/pull/1">这个步骤</a>,常规操作搞一波,该 install 的 install 完,再替换下 javascript_packs_tag 什么的,试了一下启动,果然飞快,感觉要大功告成了:</p><p><img class="image_resized" style="width:300px;" src="https://l.ruby-china.com/photo/rennyallen/8a6bdc8a-8884-4fc4-9c8c-8b1ca99bf03e.png!large" alt=""></p><p> </p><p>- 再一看发现样式都没有出来,css 文件没有加载,得用 <code><%= vite_stylesheet_tag 'application.scss' %></code> 加载一下,另外如果是 scss 格式的话必须显示指定一下</p><p>- 接下来会发现大量这样的错误:</p><p><img class="image_resized" style="width:500px;" src="https://l.ruby-china.com/photo/rennyallen/b3211d78-9c85-4912-a8c6-3c0d060e8572.png!large" alt=""></p><p>原因很简单,我们项目里用了 React 自然有大量的 JSX 语法,但是我们很多文件后缀是 .js 命名的,而在 JS 文件里的 JSX 语法是被 Vite 认为不支持的</p><blockquote><p>Vite assumes that js files contain only js valid syntax. But jsx has another AST and therefore is only supported in jsx files</p></blockquote><p>尤大大也说了 (<a href="https://twitter.com/youyuxi/status/1362050255009816577">https://twitter.com/youyuxi/status/1362050255009816577</a>),本来 JS 文件大多数情况下是不需要完全的 AST transform 的,如果要支持 JS 文件里的 JSX 语法的话,相当于所有文件都需要被 full-AST-processed. 这样其实不是一个好的做法</p><p>那怎么解决呢?找了三个方案:<br>1. 替换所有 .js 文件为 .jsx 或者 .tsx<br>2. 使用 plugin (<a href="https://github.com/dravenww/vite-plugin-react-js-support">https://github.com/dravenww/vite-plugin-react-js-support</a>) 增加对 JS 文件中的语法支持<br>3. 使用 esbuilder 配置 loader 来解析文件,原理和方案2一样</p><p>最后我选了方案1,虽然要替换几百个文件看起来改动很大,但这个应该才是正确的做法,本来就不该命名为 .js</p><p>跑了个 shell 批量给替换了:<code>find app/javascript/components -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.tsx"' {} \;</code></p><p> </p><p>- JSX 的问题搞定了,发现出现了另一个问题,大量的文件在 import 的时候找不到,之前用相对路径写的全都不能用了</p><p>例如 <code>import XX from 'components/form/xxxx'</code> 都得替换成 <code>import XX from '~/components/form/xxxx'</code></p><p>这个好解决,在 vite.config.ts 里配置一下 resolve path 就好了:</p><pre><code class="language-javascript">import { defineConfig } from 'vite'
import { resolve } from 'path'
import RubyPlugin, { projectRoot } from 'vite-plugin-ruby'
export default defineConfig({
plugins: [
RubyPlugin()
],
resolve: {
alias: [
{ find: 'components', replacement: resolve(__dirname, 'app/javascript/components') },
{ find: 'shared', replacement: resolve(__dirname, 'app/javascript/shared') },
{ find: 'actions', replacement: resolve(__dirname, 'app/javascript/actions') },
{ find: 'stores', replacement: resolve(__dirname, 'app/javascript/stores') }
]
}
})</code></pre><p> </p><p>- 下一个问题,Webpack 里的 require.context 在 Vite 里是不能用的,需要用 <a href="https://vitejs.dev/guide/features.html#glob-import">glob-import</a> 来做</p><p>比如 <code>const componentRequireContext = require.context('components', true)</code> 需要改成 <code>const componentRequireContext = import.meta.globEager('../components/**')</code></p><p>另外其他用到 require 的地方也得改成 import</p><p>否则就会出现 <a href="https://github.com/vitejs/vite/issues/2161">require is not defined</a> 的报错</p><p>因为 Vite 是完全依靠 ESM 原生能力的,也就是说它只认 import, 而 require 是 CJS 里的方法。我们的代码最终被送到浏览器里执行,浏览器本身就没定义这个方法,所以就报错了。</p><p>这里和 Webpack 不一样,Webpack 事先会编译打包好,到浏览器的时候已经把 require 转成浏览器能识别的方式了。</p><p>所以这里还是有点难受的,好在我这项目里 require 的地方不多,勉强把这个坑踩过去了。</p><p><br>- 最后终于把所有报错解决了,也跑起来了,发现有点不对,页面显示基本都不完整,很多东西都没显示出来。</p><p>这里要说的是,我们项目里很多地方不是 React 全局接管路由的,也就是说不是纯前端渲染,而是用了 <a href="https://github.com/reactjs/react-rails">react_rails</a> 的方式来渲染的前端组件,问题就出在这儿,这种方式渲染的内容都没有显示出来。</p><p>猜测是这种方式引用的前端组件没有被 Vite 识别到。</p><p>于是翻到了社区的讨论:</p><p>vite_ruby 这边作者表示自己没有用过 react_rails, 可能不太兼容, 建议 react_rails 作者去支持组件注册 <a href="https://github.com/ElMassimo/vite_ruby/discussions/86">https://github.com/ElMassimo/vite_ruby/discussions/86</a></p><p>而看到 react_rails 这边对于 Vite 的支持也没有明确进展 <a href="https://github.com/reactjs/react-rails/issues/1134">https://github.com/reactjs/react-rails/issues/1134</a></p><p>玩到这里,我就基本放弃了,溜了溜了。</p><h3>结语</h3><p>如果是对于新项目,确实可以尝试 Vite,速度会有提升;如果对于依赖太多的老项目,迁移起来可能会比较麻烦。</p>2022-03-07T13:22:45Z任峻宏tag:renny.ren,2005:Article/282021-04-25T20:04:33+08:002021-04-26T19:34:23+08:00https://renny.ren/ch/articles/28《人生算法》读书笔记<section>
<section>
<section>
<p style="text-align: center;"><strong>1</strong></p>
</section>
</section>
</section>
<section>
<p><span style="line-height:2;">先说说我思考过好几次的问题:人为什么要阅读?读书到底有啥用?</span></p>
</section>
<section>
<p><span style="line-height:2;">这个问题我其实一直没想明白,直到我看到了这么一句话:</span></p>
</section>
<section>
<p><span style="line-height:2;">一本书读完,可能很快就忘干净了,就像竹篮打水,是一场空。</span></p>
</section>
<section><span style="line-height:2;">但是竹篮经过一次次水的洗礼,会一次比一次干净。一个人每天看书,可能记不住什么,但是在潜意识里会明白,什么是对,什么是错。</span></section>
<section>
<p><span style="line-height:2;">我觉得说得太好了。昨天我又看到个视频,说的是阅读就像你人每天吃进去的食物一样,你3天前吃的是什么?你已经不记得了,但是它成为了你的养分。这说的其实是同一个意思。</span></p>
<p><span style="line-height:2;">另外,越阅读,才越发现自己的无知,越发现自己需要学习,又给自己提供了动力。</span></p>
</section>
<section>
<p> </p>
</section>
<section>
<section>
<section>
<p style="text-align: center;"><strong>2</strong></p>
</section>
</section>
</section>
<section><span style="line-height:2;">尽管我是一个很有耐心的人,但我很难静下心来阅读一篇长文,就是手机上的公众号那种长文,我仿佛有“阅读长文恐惧症”,看到那种文章我就只想快点翻到最下面看看评论结束。总结起来,其实有这么几点原因:</span></section>
<ul>
<li>
<p><span style="line-height:2;">文章看着太长了,滚动条那么短,一眼翻不到头,目标太大。其实这和做事情是一个道理,不拆解成多个小目标的话根本没法玩</span></p>
</li>
<li>
<p><span style="line-height:2;">在用手机的时候是一个相对比较放松的状态,并没想着要专注地阅读,所以突然遇到一篇长文,是很难花大量时间去阅读的</span></p>
</li>
</ul>
<section> </section>
<section><span style="line-height:2;">相比之下,读纸质书就不同了,读书、写作可以真正让我静下心来,让内心得到安放,不那么浮躁。</span></section>
<section> </section>
<section><span style="line-height:2;">回到《人生算法》这本书,结构非常清晰,还把内容分成了九段心法和十八关算法,每章就是一关,每一关的内容也不多,就那么几页,读起来也不费劲,每天闯两关,目标很清晰,颇有一种打怪升级的感觉。</span></section>
<section>
<section>
<section>
<p> </p>
<p style="text-align: center;"><strong>3</strong></p>
</section>
</section>
</section>
<p data-lake-id="3f6fdfdf509c95ad36ed013c9f2ab157"><span style="line-height:2;">书的开篇从一个游戏引入:</span></p>
<p data-lake-id="23fda3900c7ee92a75f740790e3054e7"><span style="line-height:2;">想象一下,你现在中了一个大奖,你面前有两个按钮:按下第一个按钮,你可以马上拿走100万美元;按下第二个按钮,你有50%的概率获得一亿美元,也有50%的可能什么也拿不到。这两个按钮,只能选一个,你会选哪个?<img height="1" src="//assets.renny.ren/ckeditor_assets/pictures/50/original_image-20210425200049-1.gif" width="1" /></span></p>
<p data-lake-id="23fda3900c7ee92a75f740790e3054e7" style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/51/content_QQ20210425-0.jpg" style="width: 400px; height: 300px;" /></p>
<p data-lake-id="00a1f3f024b65af36d9f2d3c66b49e82"><span style="line-height:2;">哈哈,这个题作者在书里提供了几种思考方案,很有意思。</span></p>
<p data-lake-id="9de2929cb252931bded103853558e3a6"><span style="line-height:2;">这本书里面确实讲了很多道理,但是都不空洞,结合了很多实际的案例、实验或是生活中贴切的小事。同时内容还覆盖了金融、概率统计、数学、物理学、心理学、哲学等等多个领域,光是新名词我就学习了好多。</span></p>
<p data-lake-id="6366cd0a4704a1661917527dc954d45d"><span style="line-height:2;">不过正如上面所说,如果现在要我一五一十地讲出来这本书写了些啥还是有些困难的,不过我记录了一些关键点,下面随便摘抄列举一下:</span></p>
<ul data-lake-id="932291bdf2dc63789c7b3631ecfd6772" lake-indent="0" start="-">
<li>
<p>复利</p>
</li>
</ul>
<p data-lake-id="f70ac7ade939d90493df37749a9df94d"><span style="line-height:2;">一张纸在对折6次之后再想进行对折就非常困难了,理论上来讲,如果对折27次,纸张的厚度可达1.3万米,远远超过了珠穆朗玛峰的高度。这就是复利的魔力。</span></p>
<p data-lake-id="ed3cd1f5b90343ac5a7ad330137632a2"><span style="line-height:2;">“人生就像滚雪球,重要的是找到很湿的雪和很长的坡”</span></p>
<p data-lake-id="ed3cd1f5b90343ac5a7ad330137632a2"> </p>
<ul>
<li>用垄断实现复利优势</li>
</ul>
<p data-lake-id="8d4e8d51174a73375c45db7e436c16c9"><span style="line-height:2;">作者讲了一件有趣的事情:不管你觉得自己内心多么丰富,你在其他大部分人心目中,可能就是一个标签。比如,那个特别能聊天的人,那个搞投资的人,那个卖房子的人,等等。这个标签是你独一无二的价值。</span></p>
<p data-lake-id="8d4e8d51174a73375c45db7e436c16c9"> </p>
<ul data-lake-id="58749c0c51f05f804b66eae1a51b5e9a" lake-indent="0" start="-">
<li>
<p>回到过去能改变命运吗</p>
</li>
</ul>
<p data-lake-id="934711fe960ebf70b6b62089d71967f4"><span style="line-height:2;">如果你现在有一个机会,可以搭乘时光机穿梭回到过去,改变你做过的任意一个决定,你觉得自己的命运会因此而改变吗?</span></p>
<p data-lake-id="f365ed54ac2fef9c434f9285b1ebc52d"><span style="line-height:2;">一个城市每年大约发生100起凶杀案,如果可以搭乘时光机回到过去,提前把这100个嫌犯抓住,是不是这座城市就不会有凶杀案了?</span></p>
<p data-lake-id="f4259c5d8838621f133c750631ac673b"><span style="line-height:2;">作者的回答是:不会。</span></p>
<p data-lake-id="f4259c5d8838621f133c750631ac673b"> </p>
<p data-lake-id="b149bb9254dfddcf1c445a5d58a63143"><span style="line-height:2;">这章我记录了两个关键词:一个是大数定律,一个是稀释。</span></p>
<p data-lake-id="ed02b5bec31b336e35b1ad845f0e59cb"><span style="line-height:2;">简单来说,大数定律应该是统计学的一个词吧,讲的是一个随机事件多次重复发生,它的结果所呈现的长期稳定性。书中举了几个例子。</span></p>
<p data-lake-id="a0f72d92193a2caa7a30974aec71d559"><span style="line-height:2;">另外稀释就是大数对小数会有稀释作用,不断地抛硬币,正面朝上的概率会不断趋近于50%</span></p>
<p data-lake-id="a0f72d92193a2caa7a30974aec71d559"> </p>
<p data-lake-id="f80ce34d2986edf0e51b6a5a80fac0a6"><span style="line-height:2;">“我们在人生中犯一两个错误的时候,不要纠结,不要总想着修正它,你应该继续做正确的事。”</span></p>
<p data-lake-id="87f6a5b91d8096181ed782152c02bd22"><span style="line-height:2;">“当我们以一生为期限,复盘命运的时候,我们的命运就无法取决于一两次选择,因为它取决于我们自身的系统。性格决定行为方式,行为方式决定命运。”这也是前面的问题答案是否定的原因。</span></p>
<p data-lake-id="87f6a5b91d8096181ed782152c02bd22"> </p>
<ul data-lake-id="3416be03ace160d236f60e3c80b855eb" lake-indent="0" start="-">
<li>
<p>灰度认知、黑白决策、疯子行动</p>
</li>
<li>
<p>双我思维</p>
</li>
<li>
<p>浑球儿思维</p>
</li>
<li>
<p>获得好姻缘的算法</p>
</li>
<li>
<p>防爆思维</p>
</li>
<li>
<p>时间权和概率权</p>
</li>
<li>
<p>时间贴现与延迟满足</p>
</li>
<li>
<p>人生的三个旋钮</p>
</li>
</ul>
<p> </p>
<p><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/52/content_QQ20210425-1.jpg" style="width: 300px; height: 400px;" /> <img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/53/content_QQ20210426-0.jpg" style="width: 450px; height: 338px;" /></p>
<p data-lake-id="c16e194e9dc5d1871c57b101a0457d7f">总的来说,《人生算法》这本书就是一次自我意识的塑造之旅,很耐人寻味,强烈推荐。 </p>
<p data-lake-id="c16e194e9dc5d1871c57b101a0457d7f" style="text-align: center;"> </p>
2021-04-26T19:34:23Z任峻宏tag:renny.ren,2005:Article/272021-01-24T17:23:30+08:002021-01-24T17:49:45+08:00https://renny.ren/ch/articles/27《不拘一格》读书笔记<p>“员工可以自己决定休假,没有限期,不需要审批”</p>
<p>“取消差旅和报销制度,员工可以自己做决策”</p>
<p>“我们招聘最好的员工, 仅仅做到称职也要拿钱走人”</p>
<p>“按市场最高价支付工资,不搞KPI,末位淘汰是我们最为排斥的规定”</p>
<p>“取消奖金制度,直接把奖金加到工资里”</p>
<p>“永远保持坦诚。身为领导者,不能让你的下属对你的决策感到不解和诧异”</p>
<p>以上这些比较独特的观点来自于市值千亿的 Netflix 公司的文化手册。</p>
<p><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/46/content_332.jpeg" /></p>
<p> </p>
<p>No Rules Rules《不拘一格》据说是 Netflix CEO 哈斯廷斯出的第一本书。刚出版不久我就下单了,因为看了个简介视频,里面的内容吸引了我,一家千亿市值的公司的企业文化到底是怎么样的呢?我就来吃瓜看看。</p>
<p>这本书的结构很清晰,是一本讲管理的书籍,但是完全不是那种讲理论知识的,而是哈斯廷斯以及一名记者,结合公司多年来的实际发展经历,以及对公司员工的采访,针对具体的问题而总结出的解决方案,所以像我这种完全不懂管理的人也可以看得津津有味。</p>
<p>与其说是在讲管理,不如说是在讲故事,在记录,记录的就是 Netflix 公司发展过程中遇到的各种问题和思考,比较特别的是他们愿意把这些全都写出来,包括企业文化、公司内部的管理章程、制度和手册等,我想这确实也与他们的「坦诚」的价值观相对应。</p>
<p>书中的很多观点正如书名一样,没有规则的规则,真的是比较颠覆常规的方法,可以引发很多思考。比如上下班打卡究竟考核的是什么呢?奖金有用吗?奖金低的人和奖金高的人谁的任务会完成得更好?KPI到底有用吗,应该怎么使用?什么时候应该制定规则,什么时候要选择自由?</p>
<p>再比如公司取消了报销审批制度,就有员工会开始虚假报销,或者大手大脚的花钱,甚至从报销中牟利,这种情况是怎么处理的呢,如何防止类似的情况发生呢?这些都在书中给出了答案,故事情节可谓跌宕起伏。</p>
<p>其实整本书基本是围绕三个点来讲的:</p>
<p>- 提高人才密度</p>
<p>- 提高坦诚度</p>
<p>- 取消管控</p>
<p> </p>
<p>这三点就像一个飞轮,做完之后又来:</p>
<p>- 进一步提高人才密度</p>
<p>- 进一步提高谈成都</p>
<p>- 取消更多管控</p>
<p>……</p>
<p>「自由与责任」可以说是贯穿整本书的线索,读完后就会发现,“取消休假审批,想休多久就休多久”,“取消报销审批,取消管控”,“鼓励员工参加其他面试,鼓励员工知道自己的市场价,支付市场最高工资,支付丰厚的遣散费”,这些听起来很吸引人的点,这一切的自由,都是有对应的责任的。选择「自由与责任」,还是选择「规则与流程」,这其实只是两种不同的管理思路,它们的适用场景也有所不同。</p>
<p>当然了,书里也有一些具有普适性的方法,比如在提高坦诚度的一章中,鼓励大家以积极的态度说出你真实的想法,其中就提出了「4A 反馈准则」,我觉得这个准则非常好:</p>
<ol>
<li>
<p>目的在于帮助 (Aim to assist)</p>
</li>
<li>
<p>反馈应具有可行性 (Actionable)</p>
</li>
<li>
<p>感激与赞赏 (Appreciate)</p>
</li>
<li>
<p>接受或拒绝 (Accept or discard)</p>
</li>
</ol>
<p>其实很简单,对于提供反馈的人来说,第一点,目的在于帮助,即你的态度应该是积极的,不是为了发泄,不是为了中伤别人,也不是为了自己捞取资本;第二点,反馈应具有可行性,而不是提供一个模糊的说法。</p>
<p>而对于接受反馈的人来说,“我们在受到批评时都会为自己辩护或寻找借口,这是人类的本能,我们都会条件反射地进行自我保护,当你收到反馈时,你需要有意识地反抗这种本能”,毕竟如果别人真诚地给你提出反馈,帮助你成长,你应该是报以感激的心态,不是吗?最后,接收到了反馈,不是说每条都需要照办,应该选择接收或拒绝。</p>
<p>像这样的方法和案例在书中还有很多。</p>
<p>总的来说,这本书给我们提供了一种新的管理思路,对于我这种吃瓜群众来说,也可以当故事听听,还是很推荐大家阅读的。</p>
<p><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/47/content_333.jpeg" /></p>
<p><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/48/content_334.jpeg" /></p>
<p> </p>
2021-01-24T17:49:45Z任峻宏tag:renny.ren,2005:Article/262021-01-20T00:02:34+08:002021-01-20T00:46:10+08:00https://renny.ren/ch/articles/26我为什么很少写博客<p>如图所示,过于真实。</p>
<p><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/45/content_EqUUHV4UUAA7HiC.jpeg" style="width: 800px; height: 1060px;" /></p>
<p style="text-align: center;"><span style="color:#999999;">(图片来自网络)</span></p>
<p>“已经懂的没必要写、还不懂的不会写”,这是写作的心理障碍。</p>
2021-01-20T00:46:10Z任峻宏tag:renny.ren,2005:Article/322020-12-27T01:43:47+08:002021-12-29T13:24:06+08:00https://renny.ren/ch/articles/32我的2020年总结<p style="text-align:justify;">转眼2020年即将过去,今天终于能静下心来,好好写个总结。</p><p style="text-align:justify;">写东西其实是需要灵感的,有的时候突然脑子里会有一堆东西,但是没有及时记录下来,后面再想写,就难了。等真的坐下来开始写的时候,却发现不知道写啥了。</p><p style="text-align:justify;"> </p><p style="text-align:center;"><strong>1</strong></p><p style="text-align:justify;">回望2020,这对我来说真的是不平凡的一年。相比之下想想我的2019年,总的来说可以说是风平浪静,而2020年确实遇到了很多事情。</p><p style="text-align:justify;">年初时由于疫情,在成都第一次见到了街上空无一人的凄凉景象,每次看到那些共同抗疫的视频,真的是内心十分感动。</p><p style="text-align:justify;">5月份由于房东原因,搬了家,离开了住了两年的小区,现在回看,那个小区也还挺不错的,周边的商业也多,离地铁也近交通也方便,总的来说性价比很高了。新搬的地方也还不错,就是房间采光和通风太差,另外还经常有小虫,查了一下应该是德国小蠊,买了药来搞它,似乎有所缓解;另外就是停车不太方便,老是被贴罚单,以前不在意,现在想想罚得多了确实也是一笔经济损失,就这么点收入不能每月就交房东交警察了啊。</p><p style="text-align:justify;"> </p><p style="text-align:center;"><strong>2</strong></p><p>感情方面,我和在一起7年半的女友分手了,这种感受我认为没有亲身经历过的人是体会不到的。这么长时间的感情,最后就这样悄无声息地收场了。对于一向习惯有始有终的我的来说,这确实是一个遗憾。</p><p style="text-align:justify;">刚分手的那段时间,我真的很痛苦,只有我自己知道我是怎么熬过来的,我感觉有好几次我都处在崩溃的边缘了,甚至去测试,结果是中度抑郁,我不知道是真是假,但确实很难受。</p><p style="text-align:justify;">所幸还有工作,还有事可做,我很庆幸,那段时间是工作陪伴了我。</p><p style="text-align:justify;">肯定有人会觉得,不就分个手嘛有什么大不了的。是啊,多大点事嘛,生活还是要继续。但我觉得每个人的性格不一样,我真的就是那种对待事情很认真的人,或者说是容易陷进去的人,而在感情里越认真的人,往往就会越受伤。这种性格和态度有好处也有坏处,认真专一当然是好事,但不等于钻牛角尖,如果你的内心只有她,那有一天她没了,你的世界就挂了,可以想象你把自己抛出去,然后摔得粉碎,就是那种感觉。</p><p style="text-align:justify;">回想起来,我其实很感激她,因为每次痛苦经历后,人总是会有成长的。总会有人教会你成长,教会你如何爱与被爱,如何经营好一段感情。几乎没有人可以一来就遇到一个人白头偕老,我身边还有朋友谈了十年的感情也在今年分了(似乎给了我点安慰)。或许每个人都会经历恋爱、失恋、空窗、寻觅、再交往的过程,这些放到人的一生来看,不过是一段正常的旅程罢了。时隔大半年,我也慢慢放下了,我很感谢她陪我走过的七年多时光,引用一句话:“此情有憾,然无对错,往后,希望彼此都能成为更好的自己。”</p><p style="text-align:justify;">在分手之后,我对爱情的态度比较保守,但后来我偶然间遇到了一个我非常喜欢的女生,让我重新燃起了对爱情的渴望。那段时间我其实比较纠结,一方面我真的很喜欢她,另一方面那时我分手不久,其实心情是很复杂的,每做一件事脑子里都会考虑很多东西。我以为我们会很合适,但遗憾的是,最终没能如愿。我的心情也像坐过山车一样,从那种发自内心的欣喜,突然又跌落到了失望沮丧的深渊,对我来说其实打击挺大的。</p><p style="text-align:justify;">谈恋爱和工作、生活其实都是相似的,每一次失败都应该要反思自己,有总结才能有提高。</p><p style="text-align:justify;">经历了一番折腾之后,慢慢发现,有的东西也许真的是要讲缘分的。爱情有苦有甜,不要太刻意去追求什么,相互欣赏的才是最适合的。慢慢发现,好像人长大之后真的会失去以前那种奋不顾身的勇气,以前遇到什么事都是:干就完了!而经历多了之后似乎顾虑也会越来越多,反而变得彳亍了。</p><p style="text-align:justify;"> </p><p style="text-align:center;"><strong>3</strong></p><p>说到工作,今年去参加的一次面试,是我特别想进的公司,面完之后自我感觉良好,都在考虑后面的事情了,结果居然被拒了。这也是我第一次面试被拒,说实话这对我的打击也挺大的,而且和感情上的事情几乎发生在同一段时间,那段时间就感觉是各种不顺,甚至开始怀疑自己。</p><p style="text-align:justify;">回想起来,可能是因为我之前的道路太顺了,以至于一次面试被拒都让我感到不适,当然另一方面确实也是自己期望比较高,有希望才有失望嘛。我就是有时候把有的东西看得太重了,然而现实生活哪有一切顺利的呢,只是自己希望掌控一切,希望工作生活完全按照自己的计划进行,然而生活不可能是这样的。</p><p style="text-align:justify;">以前总觉得,自己是无所不能的,只要自己拼命努力,就没有我干不成的事。越长大才越发现,有的事情靠一个人的力量是无法完成的,有的事情自己是无法左右的,自己的心态还是不够好,放松一些,平和一些,才会变得更好。</p><p style="text-align:justify;">或许人总要经历些磨难,去蜕变,去成长。经过今年这些大大小小的事情后,感觉自己也算是打怪升级了,我知道,必须要勇敢地站起来,继续往前走。回头看看,呵呵多大点事嘛,不过如此;往前看,相信明天不会比今天更糟糕,当然,如果还能更糟糕,那今天的糟糕又算什么呢?^_^</p><p style="text-align:justify;"> </p><p style="text-align:center;"><img class="image_resized" style="width:32.48%;" src="https://renny-assets.oss-cn-chengdu.aliyuncs.com/images/user/t1.png"><br> </p>2021-12-29T13:24:06Z任峻宏tag:renny.ren,2005:Article/252020-06-10T00:16:32+08:002020-06-10T00:18:33+08:00https://renny.ren/ch/articles/25如何避免冲动消费?<p>我在生活中自认是一个比较有计划的人,在做比较大的事情的时候都喜欢先计划一下,然后再去做。即便如此,我发现计划往往赶不上变化,很多时候计划只是为了让自己心里踏实。虽然计划不一定完全有用,但对我来说还是一件不得不做的事情。</p>
<p>谈到冲动消费,这显然是一个计划之外的行为。相信女生都有过这样的经历,特别是在逛街的时候,很容易买买买。现在618、双十一、双十二各种活动天花乱坠,一不小心就会让你买到一大堆自己根本不需要的东西。我虽然对这些营销活动不感冒,但也曾有过一些冲动消费,大多是买了一些当时很想买,过后发现其实没什么卵用的东西。那么如何避免冲动消费呢?现在如果我突然很想买一个东西,我可能先看一看,加入购物车,但是不买;等第二天、第三天,看是不是还会想起这个事,如果过几天都还会主动想买,那说明这个东西可能真的是我需要的,这时候再下单。</p>
<p>关于冲动消费还有另一种说法,有人认为它实际的意义并不在于你买的东西最后能带给你多少价值,而在于你买这个东西的那一刻,你是很爽的,你的心情是很愉快的,这就值了,至于这东西最后有没有用已经不在乎了。我觉得这种说法也有一定的道理,有的人就是很享受花钱的感觉。但是对于我这种还没实现财务自由的人来说,一方面要明白钱肯定不是省出来的,要把自己的时间分成多份,创造更多的价值;另一方面在消费上,一定要把钱用在刀刃上,不该花的钱不要花,该花的钱大方花。当然土豪就随意了。</p>
<p> </p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/42/content_123.png" style="width: 300px; height: 248px;" /></p>
2020-06-10T00:18:33Z任峻宏tag:renny.ren,2005:Article/242020-05-31T17:41:47+08:002020-05-31T17:42:22+08:00https://renny.ren/ch/articles/24《不能不去爱的两件事》读书笔记<p>想想自己也读了些书,但总是看完就扔一边了,尽管当时有些感悟,但过段时间就完全忘了自己看过些什么,遂决定用写读书笔记、读后感的方式记录下来,毕竟好记性不如烂笔头。如果能够合上书,写下自己的收获总结自然最好;如果不能,哪怕是摘抄一些书中有意义的文字下来,也能加深印象。</p>
<p style="text-align: center;"><img alt="" height="325" src="//assets.renny.ren/ckeditor_assets/pictures/39/content_IMG_20200531_161333.jpg" width="244" /></p>
<p>这次读的书是松浦弥太郎的《不能不去爱的两件事》,这是我在一年多前已经读完的书,今天又拿出来随手翻了下,顺便记录记录。松浦弥太郎,被称为“日本最懂生活的男人”,所以这本书主要是在讲如何克服我们生活中的“恐惧”和“寂寞”。作者认为,生活中,我们经常在畏惧这两件事,甚至可以说,就是这两种情绪在策动着我们的生活和工作。我们认真工作,因为我们内心有害怕被别人比下去、害怕被社会排挤的“恐惧”;想买新衣服,打扮得体面一点,是出自我们不想被别人看轻的“恐惧”;你想与人保持联系,恋爱、结婚、交友、加入小团体……这些行动其实全部都是源自于你心中的“寂寞”。</p>
<p>说是一本书,它更像是一本手册,看看目录,都是给XXX的你这样的标题,正如书中前言所说,这本书的使用方法应该是:在你觉得焦虑不安、想要寻求答案的时候,不要往外面的世界走,希望大家能把这本书当做工具,转而凝视你自己的内心。</p>
<p style="text-align: center;"><span style="line-height:1.2;"><img alt="" height="498" src="//assets.renny.ren/ckeditor_assets/pictures/41/content_IMG_20200531_161415.jpg" width="664" /></span></p>
<p style="text-align: center;"><span style="line-height:1.2;"><span style="font-size:12px;"><span style="color:#bdc3c7;">目录</span></span></span></p>
<p>下面就简要挑一些比较有感触的地方记录一下吧:</p>
<ul>
<li>给对将来感到不安的你</li>
</ul>
<p>我在生活中也遇到过这样的人,总是对未来充满各种想象,而且大都是在想象自己并不乐于见到的未来。随着想象无穷无尽地扩散,不安也越来越严重。我觉得那些受困于“对将来的不安和寂寞”的人,对未来往往有“想太多”的毛病。</p>
<p>“我这么做,明天不会有问题吧?”</p>
<p>“我的将来会何去何从呢?”</p>
<p>如果你也有同样的困扰,我有一个建议,何不试着换一种思维方式呢——只去看那些此时此刻在眼前发生的事,把精神集中在“现在”这一刻,专注于处理眼前的问题。</p>
<p> </p>
<ul>
<li>给需要别人肯定的你</li>
</ul>
<p>希望别人肯定你这个人,和希望在工作上受到肯定,这是两码事。如果你希望别人认同你的工作表现,你必须先做出实绩。尽管严苛,但这就是职场的游戏规则。如果你只是抱着“我很努力,我比别人多付出三倍”这样的心态,这并不能为别人带来益处,只是你个人的问题。</p>
<blockquote>
<p>“虽然我还没有做出成绩,但我在别人看不见的地方一直付出着努力。我抱着这样的想法,一直在加油。”</p>
<p>如果有下属对我说这种话,我的回答一律是:</p>
<p>“你在别人看不见的地方努力,是一件很棒的事,这点我肯定你,但我无法把这点当作对你在工作上的评价。”</p>
<p>如果有下属不满我的回答,我大概会这么回应:</p>
<p>“你是希望我认可你这个人吗?肯定你在别人看不见的地方努力,算是对你这个人的评价对吧?可是,你在进这家公司工作的同时,就代表你已经受到了认可啊。”</p>
<p>公司不会雇佣他们不认可的员工。公司一定是人为有付薪水给你的价值,才会聘请你。</p>
<p>“如果公司不肯定我,我现在就不会出现在这里。”</p>
<p>只要你这么想,你心中“不被别人肯定的不安和寂寞”想必就能得到安抚。</p>
</blockquote>
<p>是啊,回想我自己,读书的时候非常刻苦,天还没亮就爬起来第一个人到教室开始学习;实习的时候办公室都熄灯了,独自在那里加班,总是在别人看不见的地方努力。但结果呢?只是差强人意,感动了自己。所以我们不应该过分追求得到别人的肯定,只要自己保持勤勉、严于律己地生活,做好自己眼前的事情,一切都是水到渠成。</p>
<p> </p>
<ul>
<li>给不愿变老的你</li>
</ul>
<p>“年纪增长,年华老去”是人类无法避免的事情之一,尽管有各种方式可以延缓生理层面的衰老,但精神层面又是另外一回事了。书里提到一点,就是我们应该经常审视自己的眼睛,因为眼睛是判断你心灵老化程度的测量计,观察眼神,也就是在观察自己的心。肉体的衰老无可避免,但你的心却可永葆年轻。</p>
<blockquote>
<p>是否还炯炯有神呢?眼神有没有失去了好奇心?是否还保持年轻?</p>
</blockquote>
<blockquote>
<p>那些看起来越活越年轻的人,是通过许多人生经验和见识,获得了免于年老的自由。他们能够像小孩一样天真无邪,渴望学习新的事物,眼神总是洋溢着好奇心。</p>
<p>有些人才年纪轻轻,心灵便已经衰老了。特别是在人累积了一点经验,开始对自己产生自信的时期,危险的红灯更容易亮起。如果你不知道还有下个舞台在等待自己,认定眼前的舞台便是结局,你会失去许多做梦的机会。</p>
</blockquote>
<p> </p>
<ul>
<li>给想自痛苦中逃离的你</li>
</ul>
<blockquote>
<p>人在健身的时候,会为自己想锻炼的部位多加一点负荷。如果想锻炼手臂,就多活动手臂肌肉,直到有点吃力的程度。如果想锻炼腿力,就积极摆动双腿,直到自己精疲力竭。</p>
<p>也就是说,那些承受痛苦的部位正是因为承受了痛苦,才变得更加强壮。</p>
</blockquote>
<p>每个人都想避开困难通过眼前的路,但感觉到“痛苦、吃力”,却也是自己正在成长、正在前进的证明。人生就像爬楼梯,一阶段一阶段地向上爬,困难也会接踵而至,一个困难解决了又会遇到下一个困难,永远不会消失。在这周而复始的过程中,我们一边学习一边成长。我很喜欢书中的这么一段话:</p>
<blockquote>
<p>碰上痛苦的事情,我希望自己不要心怀怨恨,而是能够感恩地想:“谢谢,让我得到了一个可以学习的机会。”</p>
<p>如果一个人能把自己遇到的一切人事都当作礼物收下,他就能变成一个很棒的人。</p>
</blockquote>
2020-05-31T17:42:22Z任峻宏tag:renny.ren,2005:Article/222020-05-01T14:10:02+08:002020-05-01T15:00:25+08:00https://renny.ren/ch/articles/22记一次搬家<p>时间过得很快,转眼我在现在这个小区已经住了两年了。由于与房东的合同到期,房东不租了,所以今天是我们在这里的最后一天,中午吃了顿散伙饭,下午大家就要搬家走人了。</p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/23/content_WechatIMG139.jpeg" style="width: 360px; height: 479px;" /></p>
<h4 style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;">室友做的黄辣丁和小龙虾(我是蹭饭的)</span></span></h4>
<p>一次偶然的机会接触了自如租房,之前对自如的印象一直是“甲醛”这个词。不过这次实际体验之后发现他们服务其实挺不错,房源质量也很高,总的来说比较省心,所以最终就签了自如的房子。</p>
<p>这两天搬家还是挺累的,不过也有些兴奋,毕竟要住新的地方了。身边的人很多都觉得搬家是件麻烦的事,要尽量避免,觉得租房没有家的感觉,一会又要被迫换房了。不过我觉得倒还好,毕竟我是个喜欢折腾的人,相反,搬家可以给我带来新鲜感,可以在不同的地方换着住,有新的体验;而如果是有了自己的房子了就一直住那里,那多没意思呀!我还挺喜欢这种漂泊的感觉的,也许是和人生所处的阶段不同有关吧。</p>
<p>收拾得也差不多了,准备撤咯!</p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/24/content_WechatIMG140.jpeg" style="width: 400px; height: 300px;" /></p>
<p> </p>
2020-05-01T15:00:25Z任峻宏tag:renny.ren,2005:Article/232020-04-26T14:39:01+08:002020-05-01T15:41:40+08:00https://renny.ren/ch/articles/23My blog V3.0 has been published!<div style="display: none"><img src="//assets.renny.ren/ckeditor_assets/pictures/32/content_avatar.jpg" style="width: 300px; height: 300px;" /></div>
<p>我的博客从上一个版本(V2.0)更新之后,到现在已经两年多了,期间没怎么变化过,由于工作比较忙也一直没更新什么内容。</p>
<p>之前已经弃掉了在 DO 上养了三年的 droplet, 把服务器从新加坡迁移到了成都,现在国内访问速度快很多了。最近开始着手迭代博客新版本,在996工作之余每天抽点时间来开发,本来原计划是一个月内搞定,结果自己一边开发一边给自己加需求,最后搞了两个月才勉强收尾😂</p>
<p>这次对几乎所有页面都做了 redesign, 虽然谈不上多高大上,还有一些瑕疵,但是比之前的效果已经好很多了:</p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/25/content_o1.png" style="width: 500px; height: 254px;" /></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;">V2.0 主页</span></span></p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/26/content_n1.png" style="width: 500px; height: 248px;" /></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;">V3.0 主页</span></span></p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/27/content_o2.png" style="width: 500px; height: 254px;" /></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;">V2.0 文章页面</span></span></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/28/content_n2.png" style="width: 500px; height: 269px;" /></span></span></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;">V3.0 文章页面</span></span></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/29/content_o5.png" style="width: 500px; height: 254px;" /></span></span></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;">V2.0 关于页面</span></span></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/30/content_o4.png" style="width: 500px; height: 254px;" /></span></span></p>
<p style="text-align: center;"><span style="font-size:12px;"><span style="color:#999999;">V2.0 关于页面</span></span></p>
<p>总的来说,加了一些时间轴、搜索筛选等功能,调整了页面,完成了 responsive mobile, 还做了很多优化和 bug fix</p>
<p>技术栈方面,还是使用 rails,新页面是用 slim 模板写的了,前端还是 coffee,之前本来说用 React 重写一下但是感觉没有必要,反而变得麻烦了,可能以后会放到别的项目上去实践吧。另外这次用了 Semantic UI,用它没有什么原因,也不是什么新技术,只是没用过想尝试一下,之前已经用过了 Bootstrap,所以这次就不用它了。 对于 blog 这种小项目,我还是想的怎么快怎么来吧,在此基础上再去尝试一些没有用过的东西体验一下。当然前后端分离就完全没有必要了。</p>
<p>以前我是什么框架都不用,纯JS自己搞,就连一个 modal 弹窗都自己去实现,现在看来没必要,适当借助一些现有的东西会方便很多,减去很多麻烦,从而把时间精力放在其他更重要的地方。😄</p>
2020-05-01T15:41:40Z任峻宏tag:renny.ren,2005:Article/202018-03-27T21:35:49+08:002022-03-07T13:23:42+08:00https://renny.ren/ch/articles/20解决服务器上运行 bundle install 时 killed 的问题<h3>场景</h3><p>今天在服务器上跑 bundle,运行了几次,都是在一个 gem 卡住,然后显示 killed,一直不能成功</p><h3>解决方法</h3><p>刚开始以为是 gem 源或者网络的问题。后来发现是因为内存不够了- - 除了升级服务器配置,可以通过增加交换分区来解决这个问题。</p><p>swap(交换分区)是当计算机物理内存不足时用来暂时存储数据的地方,占用的是硬盘空间。当 RAM 没有足够内存来 hold 活跃程序的数据时,swap space 就可以起到作用,所以 swap 在一定程度上可以缓解内存不足的情况。</p><p>具体操作可以参考这个<a href="https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-16-04">教程</a></p>2022-03-07T13:23:42Z任峻宏tag:renny.ren,2005:Article/182018-01-06T16:39:32+08:002022-03-07T13:24:07+08:00https://renny.ren/ch/articles/18解决生产环境不能发送邮件的问题<h3>问题描述</h3><p>使用 smtp 方式、ActionMailer 发送邮件,开发环境能正常发送,同样的配置移植到生产环境就不能发邮件了。控制台信息显示 sent 已成功发送,且无任何报错信息,但邮箱就是没收到邮件。</p><h3>解决过程</h3><ol><li>开发环境能正常发送,说明邮件本身应该没有问题,即邮件视图,发件人地址、用户名密码等;</li><li>换用 QQ 邮箱和 outlook 测试,均不能收到邮件,说明不是接受者邮箱问题;</li><li>测试 smtp 参数,先后修改了 <code>default_url_options</code>, <code>domain</code>, <code>enable_starttls_auto</code> 等参数,都不起作用;</li><li>ipV6问题,禁用了ipV6依然不起作用;</li><li>参考<a href="https://stackoverflow.com/questions/16040158/rails-mailer-netopentimeout-execution-expired-exception-on-production-serve">https://stackoverflow.com/questions/16040158/rails-mailer-netopentimeout-execution-expired-exception-on-production-serve</a>,排查端口问题,可能是25端口被屏蔽了,换用465端口,但出现 readtimeout 错误</li><li>添加参数 <code>ssl: true</code> 问题解决</li></ol><p>这其中还有一个很重要的问题,就是用 <code>deliver_later</code><i> </i>看不到任何报错,偶然间换成了 <code>deliver_now</code> 发现有 <code>Net::OpenTimeout</code> 错误才顺藤摸瓜解决了问题。<code>telnet smtp.ym.163.com 25</code> 是不通的,<code>telnet smtp.ym.163.com 465</code> 可以连接。所以很有可能就是服务商屏蔽了25端口导致的这个问题。</p><h3>部分相关代码</h3><p>production.rb</p><pre><code class="language-ruby">config.action_mailer.perform_caching = false
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.ym.163.com',
port: 465,
ssl: true,
user_name: your_username,
password: your_password,
authentication: 'plain',
enable_starttls_auto: true
}
</code></pre>2022-03-07T13:24:07Z任峻宏tag:renny.ren,2005:Article/172018-01-02T23:23:14+08:002022-03-07T13:24:24+08:00https://renny.ren/ch/articles/17解决内存泄漏记录<p><a href="https://devcenter.heroku.com/articles/ruby-memory-use#too-many-workers-over-time">https://devcenter.heroku.com/articles/ruby-memory-use#too-many-workers-over-time</a></p><h3>过程</h3><p>首先降低 puma 的 worker = 1 ,结果基本没用</p><p>查看是不是 gem 的问题,用到了 derailed 这个 gem, to see how much memory each gem uses at boot time</p><p>分析发现 countries 用了 5MB 多的内存,去掉之后确实不报 R14 错误了,说明确实是存在 gem 使用问题导致内存泄漏的问题</p><p>最后通过 update 了 carrierwave 和 fog gem 到 latest versioin,减小了内存占用,问题解决</p><p>啊。发现问题还是没有解决,并发高的时候内存又溢出了。。</p><p>判断可能是属于 Too much memory used at runtime</p><p>装了个 scout_apm(heroku 的 add-on)分析了一下</p><p>再一查发现还是一个查询语句的问题,出现了慢查询,有一个查询花了接近10秒。最后把查询语句简化,问题解决。</p><h3>总结</h3><p>内存超出(Memory Quota Exceeded),排查两个问题:</p><p>服务器的问题。worker 是否过多?Puma 切换到 unicorn?</p><p>gem的使用问题。使用 derailed gem 可以分析 gem 的内存使用情况;</p><p>参考 <a href="https://devcenter.heroku.com/articles/ruby-memory-use#too-many-workers-over-time">https://devcenter.heroku.com/articles/ruby-memory-use#too-many-workers-over-time</a> 排查问题</p>2022-03-07T13:24:24Z任峻宏tag:renny.ren,2005:Article/162017-09-14T21:19:36+08:002022-03-12T21:21:10+08:00https://renny.ren/ch/articles/16勿忘初心<p>自从看了Terry写的《<a href="https://mp.weixin.qq.com/s/ezPIFa9k2NFC_AWT-Mpm2A">我如何把薪水从50人民币/天提升到100美元/小时</a>》以及《<a href="http://terrytai.me/how-to-begin-soho-1/">如何开始你的SOHO之旅</a>》后,我就对远程工作充满了向往, 同时文中的一些内容也深深触动了我。不用每天花2小时上下班,不用每天准时一大早起床,不用每周不管有事没事都必须工作5天,不用考虑公司在哪,不用考虑打卡、交通、吃饭、租房杂七杂八的……总结起来就是两个字:自由。天哪,这样的工作听起来很爽吧。是的,也只是听起来很爽。</p><p>闲暇之余,我也随便浏览了几个 freelancer 的网站,看着市场需求还挺大的,于是便认认真真地写了个简历,投了个proposal。没想到很快就收到了回复:Please let me know when you are available to chat. I can share more project details with you and also assess the fit for us to work together. 邀我详谈,我当然立马就答应了。</p><p>“I am on another call now but should be available in 1 hour if you will be free then?”</p><p>1小时后,她给我发了个google hangout的request, 我那时根本没听说过hangout这个东西,于是赶紧去下载弄了一个,手机上也装好了app.</p><p>“you can call me when you're free”,然后就等她的电话了。</p><p>由于这位客户在美国,等到她第二天开始工作,已经是晚上八点了,她泡了杯咖啡,准备开始面试。然而等她的电话打来,我才发现firefox不支持hangout, 需要装个插件才能接视频,10分钟后等我弄好回复她,她那边已经没有音讯了。我以为这次面试就这样结束了。</p><p>三天后她回复了,原来她之前去旧金山旅游了,问我多久有空,不得不说这位客户事情真多,等我有空了她又去打别的电话了。就这样持续了四五天,终于在某天的凌晨12点半正式开始了面试。</p><p>说实话这几乎是一次裸面,我甚至都不知道工作内容是什么,其实我只是想试试和外国人交谈,毕竟这好像是我生平第一次和外国人face-to-face说话。然后就尴尬了,整个过程我是懵逼的,面试我的竟然是HR,这是一个全职工作(我之前以为是兼职),除了开场的一句问候语打打招呼,和中间断续的几个单词+疑问的语气断定是问了一个问题以外,我几乎不知道她在说什么,勉强回答了几句,最后我说“Thank you”,她说“thank you”,然后就没有然后了……</p><p>这次的面试虽说一开始我就抱着试试的心态,但后来还是让我有点感触,想想自己学了十多年英语,成绩还一直不错,各种比赛证书高分,最后连一个面试都不能正常进行,当时还是有点失落的。我第一次意识到自己的口语如此之差,或者说是听力太差没听懂导致不知道说什么,又或者只是当时太紧张一时间脑袋里词穷了。不管什么原因,我知道我还需要提高。从那以后,我坚持每天早晨晨读,加了一堆口语群和外国人聊天练练感觉,毕竟学语言嘛,环境、氛围才是最重要的,多读多说培养语感,久而久之就熟了。我以前很少看电影,更不用说国外的电影,我也完全不听英文歌,英文歌好听吗?不见得,毕竟中文歌才是最屌的。甚至我上英语课也基本不听讲,高中三年下来我的英语书是几乎全白的和新的一样,但是成绩却名列前茅,有很多人问过我,你是怎么学英语的呢?我想了想,其实总结下来就两个字:语感。至于具体的,就说来话长了,这里就不赘述了。</p><p>言归正传。不久后,我又投了一份简历,很快收到了我的第一个offer。我的第一个客户是德国人,我很庆幸第一份工作就遇到了他,都说德国人很严谨,怎么说呢,我觉得用一两个词就去评判一个国家的人肯定是印象流了,不过这位客户真的是超级礼貌,超级nice, 这个程度真的刚开始的时候让我有点不习惯(后来发现欧洲的客户都是这样特别有礼貌)。他是休假期间回家带带孩子,顺便发个兼职,我和他一起开发,所有问题他都超级耐心地解答,可以看出他不仅是对工作,还是一个热爱生活的人,聊天中还不时透露出一些老外式的幽默。这次的工作差不多两个周就做完了,算是个小兼职吧,也让我适应了节奏。之后客户给了我一个好评,这也算是我 freelancer 之路的一个敲门砖了。</p><p style="text-align:center;"><img src="http://r.photo.store.qq.com/psb?/9de1e891-9594-4fd2-8bc4-d313641f7a5d/s226f*9EbTiJmWbdAKkzY8E*prQ1AtejeKqtpWQgptQ!/o/dHMAAAAAAAAA&ek=1&kp=1&pt=0&bo=gAKPAsAC0QIDIBs!&su=0113212865&tm=1505394000&sce=0-12-12&rf=2-9"></p><p> </p><p style="text-align:center;"><span style="color:#999999;">来自客户的评价</span></p><p> </p><p>本来以为到这里也就结束了,我也要准备一些考试和毕业之类的东西了。现在看来这时候才算刚刚入坑,并且一发不可收拾。由于之前的好评,我竟然没有投简历也收到了几个面试邀请,经过斟酌后我又一口气接了两个项目,准备继续干下去。当时本来要准备专升本考试和其他一堆事情,接到项目之后便把其他事情都先挤到一边了。</p><p>第二个客户是个印度人,当时想的是时差只有3.5小时,交流比较方便。后来发现,印度人的英语,真的是……怎么说呢,口语发音就不说了,那是真的听不懂,印式英语口音太重。至于写下来的,我不知道是不是印度英语都这样,但总之这个客户和上一位德国客户形成了强烈的反差,让我很不习惯。同时另一边是一位以色列的客户,他应该是作为一个公司的助理来招聘的,我也算是加入了他们的 organization,要求我每天都要做报告,内容也很简单:1. What have you done yesterday? 2. What will you do today? 3. Any problem? 这似乎成为了很多公司的日常了。有一天客户要和我语音开会,有了上次的经验,这次我提前做了一些准备,包括会问到什么,怎么回答等等。等到夜深人静时我们接通了电话。结果这次会议比想象中顺利多了,可能是因为客户的母语也不是英语而是希伯来语还是什么的,他说话也特别慢,这次的交流无障碍。</p><p>本以为只是找个兼职做,很快就完事,没想到工作并不是那么容易的。由于同时接了两个项目,需要每天推进,加上当时还要准备考试等,每天真是起早贪黑,要是一个地方卡住了,其他的事情都会受影响,那是相当痛苦的。万事开头难,还记得有一回我实在被难住了,花了很长时间,当时真的准备放弃,字都已经码好了准备给客户说不干了,就差发出去了。最后我还是没发出去,经过努力最终还是达到了目标,这种坚持下来最后终于成功的感觉简直 nice,比拿了奖状或者拿了五杀超神还要激动,现在回想起来确实挺不容易的。</p><p>第四个客户是在加拿大,但他是伊朗人移民过去的。这次的时差来到了11个小时,基本上我说 good morning 他就要对我说 good night 了。可以说这次我被虐得很惨,客户是一个要求非常严格的人,每天的 update 他都会逐一检查review,而且还经常改需求、加需求(这个我就不吐槽了),我也是一个有点完美主义的人,倒不是说强迫症那么夸张,对代码还是有洁癖的。我也不想拒绝别人,客户说要整,那就整!结果整个项目搞下来,比预估工时超了2个周,我大概写了2000多行代码,重构了两次。重构的时候我总是舍不得删掉之前的代码,毕竟是自己亲手敲出来的23333,于是就给注释掉,结果到后来发现注释越来越多,还是咬牙删了。话说算法真是个神奇的东西,第二次重构时候我实在看不下去了,于是更改了思路,优化了算法,结果之前一堆搞得昏头昏脑的判断循环全部删了,代码量少了一大半,瞬间感觉心里痛快啊。</p><p>嗯,所以说不管是客户对我还是我对自己要求都比较高,不过换个角度看,正是这样严格的要求才让我学到了更多的东西,每天脑子里想的全是项目的事,就连上厕所也不放过,每天都在遇到新的困难并解决困难,和打怪升级一样,还是过得挺充实的。让我有点意外的是大部分时间我都是在写前端,这让我俨然感觉自己成为了一个前端工程师2333,从JS到jQuery再到vue, react</p><p style="text-align:center;"><img src="http://a4.qpic.cn/psb?/9de1e891-9594-4fd2-8bc4-d313641f7a5d/olC.bfuIQp8g2cptvbBYdDZy4QsBSRJznr4jz56S.V8!/b/dIMAAAAAAAAA&ek=1&kp=1&pt=0&bo=gAKHAgAAAAARECA!&su=0258991185&tm=1505394000&sce=0-12-12&rf=2-9"></p><p>回过头来看,折腾一遍之后还是有所收获的,自己通过努力,完成了一些真的是之前自己都认为不可能的事,真的感觉佩服自己,这或许就是所谓的挑战自我,突破自我吧!最后客户还给了我50刀的小费,说了句 I like it my man, you are the best, I definitely need to hire you again. 我感觉所有的付出都是值得的。</p><p>总的来说,当一个自由职业者在家工作可能是很多人羡慕的,想几点起就几点起,有时我一天就工作2个小时。当然更多的时候还是这个状态(动图):</p><p><img src="http://a4.qpic.cn/psb?/9de1e891-9594-4fd2-8bc4-d313641f7a5d/okFWbBCMolkjm0o*.lkeHSxWqPSEr0Q1jfRs2HVbG2U!/b/dFcBAAAAAAAA&ek=1&kp=1&pt=0&bo=1wD5AAAAAAACEBo!&su=0256206449&tm=1505394000&sce=0-12-12&rf=2-9"></p><p>加班到深夜更是家常便饭,累的时候毫不夸张地说,躺上床我2分钟之内绝对睡着。不过那又怎样呢,虽然有时很辛苦,脑力劳动到害怕自己有一天会不会像别人说的那样30岁秃顶(想多了),但那种知道自己想要什么并为之努力的感觉,为成功之后的喜悦之情作了铺垫,渲染了欢快的气氛,侧面烘托出主人公美滋滋的情感(高考中毒太深:p)。很自由、很快乐,自从掉进 Ruby 这个坑,这个理念就已经影响了我。</p><p>最后再做一些总结吧: 1) 能坚持做好一件小事真的不容易。比如罗辑思维每天一段语音,或者一个人坚持每天锻炼、每天晨读等等,别说10分钟,就是60秒,坚持每天做好也真的不容易,所以我也很佩服那些能坚持下来的人。坚持下来之后的效果肯定是明显的,积少成多嘛;</p><p>2) 不要轻易放弃,也许你离成功只差最后一口气,坚持下来,说不定会有意想不到的惊喜。同时你也会因为完成了一次艰巨的任务而信心大增。</p><p><img src="http://a1.qpic.cn/psb?/9de1e891-9594-4fd2-8bc4-d313641f7a5d/kFCCZI7oqoLmFrdNDa8jZB3Ey5GF*UlaisNpLK24bNE!/b/dNAAAAAAAAAA&ek=1&kp=1&pt=0&bo=uAG4AQAAAAARECc!&su=0262193969&tm=1505394000&sce=0-12-12&rf=2-9"></p><p>3) 多与大神接触,其实他们可能和想象中有些不一样,多学习,学习他们的思维方式,学习他们解决问题的方法和思路,学习他们的观点和经验,保持一种正能量和积极向上的态度,这很重要,真的。</p><p>4) 没有做不到,就怕你想不到。很多时候做不到是因为想不到做的方法(这不是废话么。。),所以不知道从何下手,或是想到了一些很笨的方法,做着做着发现越来越费劲,思路越来越不清晰,逻辑越来越复杂、混乱,最后就脑袋爆炸了。。</p><p><img src="http://a1.qpic.cn/psb?/9de1e891-9594-4fd2-8bc4-d313641f7a5d/Kose34WjpBgsFpoqjcpuZEav5IeWnfSO0ioOctTEqRg!/b/dDwBAAAAAAAA&ek=1&kp=1&pt=0&bo=kAFgAQAAAAACUIQ!&su=057024273&tm=1505394000&sce=0-12-12&rf=2-9"></p><p>其实这种情况多半是思路没对,路没选择对(虽然能到达终点但是太蜿蜒曲折陡峭了),一定有更好的路,理清思路,搞清逻辑,其实很快就搞定了。</p><p>5) 要勇于尝试。有的事情看起来很难,但不去试试你怎么知道你就不行呢?为什么成功的那个人不能是你呢?万一你突然人品爆发就成功了呢?这不是侥幸,我意思是有时候真的可以多尝试一下。好比追女生,你不去找机会搭讪,怎么知道对方对你的印象呢,万一你刚好是她的暗恋对象呢哈哈哈。。。有的事情真的是试了才知道,哦,原来也不过如此嘛。回过头看来时的路,发现自己还是有所成长的嘛。就像学习一项技能或知识,开始觉得很难,入门了感觉有点感觉了,到最后完全掌握了你就会觉得,其实这东西很简单嘛,然后你又可以开始向下一个目标进发。 畏缩、保守、害怕失败不是我们该有的作风,青春就应该多尝试、多吃苦、甚至多失败,一帆风顺不见得总是好事,如果一个人总是一帆风顺,觉得生活很轻松,那么他肯定是待在了自己的一个所谓舒适区,过着安逸的生活,而这样通常是不会成长的;反过来,如果觉得很困难,可能是因为你在走上坡路。</p><p>6) 学会分解目标。列出清单,将大目标分解为小目标。这是我在得到上得到的(没有打广告QAQ),真的是这样,一个大目标会让人感觉无从下手,分解成一个个的小目标之后,就好办多了!</p><p>以上都是我的真实感受,就像NBA连续三年的骑勇大战,大家都觉得这是理所应当的,总决赛难道不是骑勇?其他队还需要打球吗?其实正如库里所说,没有什么是理所应当的,他们也是通过努力才走到了今天的位置。正如那句鸡汤:你必须非常努力,才能看起来毫不费力。</p><p>我不是一个经常看书的人,但有两本书最近对我影响深刻,多次震撼了我,一本是《你要么出众 要么出局》,另一本是有关乔布斯的《记住你即将死去》。</p><p><img class="image_resized" style="width:16.02%;" src="http://r.photo.store.qq.com/psb?/9de1e891-9594-4fd2-8bc4-d313641f7a5d/nhjZgdJTVd1M.MIFaU2yopQL46XcgXscNeQliV1dZ4o!/o/dGsBAAAAAAAA&ek=1&kp=1&pt=0&bo=gAJVA7AEQAYREAE!&su=090154321&tm=1505394000&sce=0-12-12&rf=2-9"></p><p>“其实这辈子,要么努力地按照想法去活,要么习惯了按照活法去想;要么拼命改变生活,要么习惯给平庸找借口。” 勿忘初心,每天进步,给自己生活中加点料,给未来留下一些期待,生活的小马达就运转起来了 :)</p>2022-03-12T21:21:10Z任峻宏tag:renny.ren,2005:Article/152017-07-23T17:07:20+08:002022-03-07T13:24:40+08:00https://renny.ren/ch/articles/15关于 RSpec 的一点方法总结<p><i>注:</i><a href="http://jakeyesbeck.com/2017/07/12/a-few-rspec-helpful-hints/?utm_source=rubyweekly&utm_medium=email"><i>原文</i></a><i>翻译时有删改</i></p><p>两个主要的框架垄断了Ruby的测试界:Rspec 和 MiniTest. Rspec 是一个非常有表现力的测试框架,它有很多好的特性和辅助方法来让测试变得可读。当我们用 Rspec 写测试的时候,有几个小的方法,或许可以让测试更好写、更易读、更利于维护。</p><p>现在假设有一个系统,有书(<code>Books</code>) 和 作者(<code>Authors</code>),让我们使用一些方法来简化测试。</p><pre><code class="language-ruby">class Book
attr_reader :title, :genre
def initialize(title, genre)
@title = title
@genre = genre
end
end
class Author
attr_reader :books
def initialize(name, books)
@name = name
@books = Array(books)
end
def has_written_a_book?
!books.empty?
end
end
</code></pre><p> </p><h3>let 和 subject</h3><p><code>let()</code> 有两个好处: - 不用赋值给实例变量就可以缓存值; - 定义的变量是“惰性计算”的,不调用就不会执行赋值操作;</p><p><code>subject{}</code> 可用来声明测试的对象,后续的测试用例就无需明确指定了。</p><p>通过声明 <code>let</code> 和 <code>subject</code> 变量是一个保持测试可读性和不重复自己(DRY)的好方法。</p><p>举个例子,如果我们想确认一个作者有名字(assert an <code>Author</code> has a <code>name</code>),如果不用 <code>let</code> 和 <code>subject</code>变量,测试大概长这样:</p><pre><code class="language-ruby">describe Author do
before do
@book_genre = 'Historical Fiction'
@book_title = 'A Tale of Two Cities'
@book = Book.new(@book_genre, @book_title)
@author_name = 'Charles Dickens'
@author = Author.new(@author_name, [@book])
end
describe '#name'do
it 'has a name set' do
expect(@author.name).to eq(@author_name)
end
end
end
</code></pre><p>如果再加上别的测试,比如确认书的数量,不同的名字,或者其他关于这个作者的东西的话,这测试就会变得很冗长。</p><p>所以,我们可以使用 <code>let</code> 和 <code>subject</code> 变量来实现DRY:</p><pre><code class="language-ruby">describe Author do
let(:book_genre) { 'Historical Fiction' }
let(:book_title) { 'A Tale of Two Cities' }
let(:book) { Book.new(book_genre, book_title) }
let(:book_array) { [book] }
let(:author_name) { 'Charles Dickens' }
subject { Author.new(author_name, book_array) }
describe '#name'do
it 'has a name set' do
expect(subject.name).to eq(author_name)
end
end
describe '#books' do
context 'with books' do
it 'has books set' do
expect(subject.books).to eq(book_array)
end
end
context 'without books' do
context 'books variable is nil' do
let(:book_array) { nil }
it 'sets books to an empty array' do
expect(subject.books).to eq([])
end
end
context 'books variable is an empty array' do
let(:book_array) { [] }
it 'sets books to an empty array' do
expect(subject.books).to eq([])
end
end
end
end
end
</code></pre><p>不再需要几个 <code>before</code> 块来定义一个个实例变量,这段代码用了 <code>let</code>,简洁易读。更具体地说,这些测试的工作机制是:每当一个 <code>it</code> 块运行的时候,<code>context</code>里面离得最近的 <code>let</code>就被用来初始化这个 <code>subject</code>。</p><p>通过设置 let 变量,如果想要测试 <code>subject.books</code>是不是一个数组,判断输入是<code>nil</code>或者<code>[]</code>,只需要简单地修改一下 <code>let</code> 声明:<code>let(:book_array) { nil }</code></p><p> </p><h3>Loose Expectations</h3><p>如果一个测试不关心细节,Rspec 允许使用 general expectations 和 占位符(placeholders). 这些占位符可以只专注于真正重要的东西,从而简化测试。</p><p><strong>1. </strong><code><strong>anything</strong></code></p><p>正如其名,当一个方法要求一个参数但是这个具体的参数又对测试没有影响的时候,我们可以使用 <code>anything</code> 这个参数匹配符。</p><p>如果一个测试,想要确认一个作者已经写过书了,但不关心这本书的标题和类型,就可以用 <code>anything</code> :</p><pre><code class="language-ruby">describe Author do
describe '#has_written_a_book?' do
context 'when books are passed in' do
subject { Author.new(name, books) }
let(:books) { [Book.new(anything, anything)] }
it 'is true' do
expect(subject.has_written_a_book?).to eq(true)
end
end
end
end
</code></pre><p><strong>2. </strong><code><strong>hash_including</strong></code></p><p>当我们要测试一个期望是 <code>Hash</code> 的方法,这个 <code>Hash</code> 中的某些元素可能比其他的元素更重要。<code>hash_including</code> 匹配符允许开发者 assert 一个或多个 hash 的键值对,而不用指定整个 hash。</p><p>假设 <code>Book</code> 类有一个方法,实例化了一个 HTTP client (用来取得一些附加信息):</p><pre><code class="language-ruby">class Book
# ...
def fetch_information
HTTPClient.new({ title: title, genre: genre, time: Time.now })
.get('/information')
end
end
</code></pre><p>给这个方法写测试应该 assert 这个 client 已经初始化了几个关键点,这个场景就可以用 <code>hash_including</code>。</p><pre><code class="language-ruby">describe Book do
describe '#fetch_information' do
let(:book_genre) { 'Historical Fiction' }
let(:book_title) { 'A Tale of Two Cities' }
subject { Book.new(title, genre) }
it 'instantiates the client correctly' do
expect(HTTPClient).to receive(:new)
.with(hash_including(title: book_title,
genre: book_genre))
subject.fetch_information
end
end
end
</code></pre><p><code>hash_including</code>可以指定一个期望的 hash 中的键值对或者只是键。这里,这个测试只关心传入的书的标题(<code>title</code>)和类型(<code>genre</code>)。</p><p><strong>3. </strong><code><strong>match_array</strong></code></p><p>在Ruby中,当且仅当两个数组包含同样的元素且顺序相同时,这两个数组是相等(equal)的。在一些测试中,这个严格相等的准则可能不是必须的。那些情况下,RSpec 提供了一个叫做 <code>match_array</code>的方法来让测试顺利进行。</p><p>如果 <code>Author</code> 类从数据库中取得了它的书的清单,由于默认的 scopes 或者记录的更新操作,书的顺序可能不是连续的。</p><p>现定义一个 <code>fetch_books</code> 方法:</p><pre><code class="language-ruby">class Author
attr_reader :name
def initialize(name)
@name = name
end
def fetch_books
BookDB.find_by(author_name: name)
end
end
</code></pre><p>使用 <code>match_array</code>,测试可以确认返回了合适的书,无视顺序:</p><pre><code class="language-ruby">describe Author do
describe '#fetch_books' do
let(:name) { 'Jane Austen' }
let!(:books) do
Array.new(2) do
BookDB.create_book(author_name: name)
end
end
subject { Author.new(name: name) }
it 'fetches the books correctly' do
expect(subject.fetch_books).to match_array(books)
end
end
end
</code></pre><p> </p>2022-03-07T13:24:40Z任峻宏tag:renny.ren,2005:Article/142017-06-09T19:17:55+08:002022-03-07T13:25:00+08:00https://renny.ren/ch/articles/14使用 AJAX 重载部分页面<h3>场景</h3><p>实现点击按钮后,重新加载部分页面而不刷新整个页面。</p><h3>方法一</h3><p>1.jQuery:</p><pre><code class="language-javascript">$(function(){
$('body').on("click", ".ClassNameOfButton", function(){
$.ajax({
url: "home/refresh_part"
})
})
});
</code></pre><p>注意这里jquery的事件如果用<code>$('.class').click(function(){})</code>可能会造成刷新后页面的js不能运行,原因是这样写只对当前页面的element有效,所以reload之后的元素就不适用了;而<code>.on()</code>可以适用于新创建的元素。</p><p>2.在view中,把要刷新的部分提取成partial,然后加上div:</p><pre><code class="language-html"><div class="reload"><%= render 'PartialName' %></div>
</code></pre><p>3.写一个js的template(refresh_part.js.erb):</p><pre><code class="language-javascript">$(".reload").html("<%= j(render 'PartialName') %>");
</code></pre><p>这里的 j() 是 escape_javascript() 的alias, 所以也可以写成:</p><pre><code class="language-javascript">$(".reload").html("<%= escape_javascript(render 'PartialName') %>");
</code></pre><p>4.最后写上路由:</p><pre><code class="language-ruby">get '/home/refresh_part', to: 'home#refresh_part'
</code></pre><h3>方法二</h3><p>直接用jQuery来完成:</p><p>1.jQuery:</p><pre><code class="language-javascript">$(function(){
$('body').on("click", ".ClassNameOfButton", function(){
$('.reload').load('/home/refresh_part');
})
}
</code></pre><p>2.同上;</p><p>3.在对应的action里面render partial:</p><p>(in home_controller.rb)</p><pre><code class="language-ruby">def refresh_part
render partial: "PartialName"
end
</code></pre><p>这种方法不需要新定义路由,似乎比上面个方法还简单些。</p><h3>注意事项</h3><p>参考<a href="http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html">文档</a>,涉及到表单的话,一定要加上<code>data-remote: true</code>,加了之后,the form will be submitted by Ajax rather than by the browser's normal submit mechanism. Whenever you add <code>remote: true</code> you are telling your form make a POST request via JS instead of HTML. 否则会出现 missing template 之类的错误</p><h3>相关资料</h3><p><a href="http://railscasts.com/episodes/240-search-sort-paginate-with-ajax?autoplay=true">RailsCast #240</a></p><p><a href="https://stackoverflow.com/questions/13988556/is-it-possible-to-refresh-partial-frequently-using-ajax/44453659#44453659">https://stackoverflow.com/questions/13988556/is-it-possible-to-refresh-partial-frequently-using-ajax/44453659#44453659</a></p><p><a href="https://stackoverflow.com/questions/8594408/reload-javascript-file-after-an-ajax-request">https://stackoverflow.com/questions/8594408/reload-javascript-file-after-an-ajax-request</a></p>2022-03-07T13:25:00Z任峻宏tag:renny.ren,2005:Article/132017-04-10T16:58:30+08:002022-03-07T13:26:52+08:00https://renny.ren/ch/articles/13Nginx 添加 SSL 支持<p>SSL是安全套接层协议,加入了SSL认证的网站显然更让人放心,看起来也高端一点。。。 以下是我的一点记录。</p><h3> </h3><p>1.申请ssl证书</p><p> </p><p>如果不想花钱,可以申请免费的ssl证书,比如在<a href="https://www.sslforfree.com/">https://www.sslforfree.com/</a> <a href="https://www.startssl.com/">https://www.startssl.com/</a>等都可以申请到</p><p>2.服务器配置</p><p>以sslforfree为例,按照网站提示操作后,最后可以下载得到三个文件:certificate.crt, ca_bundle.crt和private.key.</p><p>将他们上传到服务器,然后修改nginx配置文件即可:</p><pre><code class="language-plaintext">server {
listen 443 default ssl;
ssl_certificate /etc/ssl/certificate.crt;
ssl_certificate_key /etc/ssl/private.key;
}
</code></pre><p>这样应该就可以通过https://yourdoamin.com 安全链接访问网站了。</p><p>如果提示证书来自未知授权中心的问题,是由于CA证书链不完整,没有配置中级根证书造成的。</p><p>这时我们应该在ssl certificate文件中加入完整的CA证书链,把所有的certificate合并成一个文件:</p><p>cat certificate.crt ca<i>bundle.crt >> cert</i>chain.crt</p><p>或者</p><p>cat certificate.crt certificate.ca-bundle >> cert_chain.crt</p><p>需要注意的是,组合之后的BEGIN和END分割线可能会合在一起,像这样:-----END CERTIFICATE----------BEGIN CERTIFICATE-----</p><p>我们需要将其分成两行,否则会报错。</p><h3>参考文章</h3><p><a href="http://www.360doc.com/content/14/1210/18/18924983_431842964.shtml">http://www.360doc.com/content/14/1210/18/18924983_431842964.shtml</a></p><p><a href="https://www.v2ex.com/t/183715">https://www.v2ex.com/t/183715</a></p><p><a href="https://www.namecheap.com/support/knowledgebase/article.aspx/9419/0/nginx">https://www.namecheap.com/support/knowledgebase/article.aspx/9419/0/nginx</a></p>2022-03-07T13:26:52Z任峻宏tag:renny.ren,2005:Article/122017-04-07T14:44:02+08:002022-03-07T13:26:47+08:00https://renny.ren/ch/articles/12Nginx 中进行重定向配置<p>有时需要给URL重定向,比如http自动跳转为https</p><h3> </h3><p><strong>方法一</strong>:return 301</p><p> </p><p>如果只是需要自动将http跳转为https,直接在server块中加上return 301重定向就可以了:</p><p><code>return 301 https://$server_name$request_uri;</code></p><h3> </h3><p><strong>方法二</strong>:用<a href="http://baike.baidu.com/item/Rewrite/5932592#viewPageContent">rewrite</a></p><p>rewrite是一种服务器重写技术,不仅可以对URL重写,还可以限制指定IP的访问</p><p>语法:<code>rewrite regexp replacement[flag];</code></p><p>编辑Nginx配置文件 <code>/etc/nginx/nginx.conf</code></p><p>写在server, location核心模块中:</p><pre><code class="language-plaintext">if ($http_host !~ “^www\.yourdomain\.com$”) {
rewrite ^(.*) http://www.youdomain.com$1 permanent;
}
</code></pre><p>其中$http_host是客户端设法要到达主机的主机名</p><p>permanent是rewrite的一个flag,表示返回状态码为301的永久重定向</p><p>另外还有其他的 Rewrite Flags:</p><p>last – 后面的rewrite指令失效,查找匹配改变后URI的新location区域</p><p>break – 中止Rewirte,不再继续匹配</p><p>redirect – 当replacement字符串不是以"http//"或"https//"开头时,返回临时重定向的状态码302</p>2022-03-07T13:26:47Z任峻宏tag:renny.ren,2005:Article/102017-03-17T13:56:01+08:002023-10-31T18:01:02+08:00https://renny.ren/ch/articles/10记我的第二次部署<p>还记得第一次部署Rails应用时花了我很多时间,也踩了一些坑,那是我第一次自己独立完成一次开发,最后输入域名看到网站正常运行的那一瞬间,真的是非常欣慰的。</p><p>当时本想把整个部署过程记录下来,但一直没来得及,转眼这又是第二次部署了,有了上次的经验,这次轻松了许多,参考相关资料,我把全过程简洁记录如下:</p><p>生成ssh key 并添加到服务器: <code>ssh-keygen -t rsa</code></p><p>如果已经有key, 直接显示ssh key内容: <code>cat ~/.ssh/id_rsa.pub</code></p><p>以root用户登录服务器: <code>ssh root@[ip.address]</code></p><p>创建部署用的用户: <code>useradd -m deploy</code></p><p>将用户加入sudo群组: <code>adduser deploy sudo</code></p><p>为deploy用户设置密码: <code>passwd deploy</code></p><p>退出当前 SSH 链接,用 deploy 帐号重新登陆。</p><p>如果此时用deploy无法登陆,可能是没有权限,尝试</p><pre><code class="language-plaintext">mkdir /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy.deploy /home/deploy/.ssh
chmod -R go-rwx /home/deploy/.ssh </code></pre><p><a href="https://rvm.io/rvm/install">安装rvm</a></p><pre><code class="language-plaintext">gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
\curl -sSL https://get.rvm.io | bash</code></pre><p>安装完成后启动:<code>source ~/.rvm/scripts/rvm</code></p><p>安装ruby <code>rvm use --install --default 2.3.1</code></p><p><code>apt-get update</code> (<a href="http://renny.ren/ch/articles/9">此处阿里云服务器可能有个坑</a>)</p><p>安装<a href="https://www.phusionpassenger.com/">Passenger</a> + Nginx</p><pre><code class="language-plaintext"># Install our PGP key and add HTTPS support for APT
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo apt-get install -y apt-transport-https ca-certificates
# Add our APT repository
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update
# Install Passenger + Nginx
sudo apt-get install -y nginx-extras passenger</code></pre><p>(此处的安装的Passenger是作为模块编译到nginx中,所以事先不能装nginx,如果装了要先卸载,否则会出现奇怪的错误)</p><p> </p><p>编辑<code>/etc/nginx/nginx.conf</code></p><p>在http块中注释掉或是加入 <code>include /etc/nginx/passenger.conf;</code></p><p><i>(此时可以用</i><code><i>nginx -t -c /etc/nginx/nginx.conf</i></code><i> 检查nginx配置是否正确</i></p><p><code><i>sudo /usr/bin/passenger-config validate-install</i></code><i> 检查passenger的安装是否正确)</i></p><p>编辑<code>/etc/nginx/passenger.conf</code></p><p>将 <code>passenger_ruby /usr/bin/ruby;</code>一行 改为 <code>passenger_ruby /home/deploy/.rvm/wrappers/default/ruby;</code></p><p>重启Nginx</p><p><code>sudo service nginx restart</code></p><p>上传文件</p><pre><code class="language-plaintext">mkdir -p /var/xxx/
cd xxx
git clone xxx
bundle install
rails db:create db:migrate
rails assets:precompile</code></pre><p>修改nginx配置</p><p>删除原有的默认网站配置: <code>rm /etc/nginx/sites-enabled/default</code></p><p>新建网站配置: <code>touch /etc/nginx/sites-enabled/example.com.conf</code></p><p>内容:</p><pre><code class="language-plaintext">server {
listen 80 default;
server_name example.com; # 这里填写你真实域名
root /var/www/example.com/current/public; #网站目录
passenger_enabled on;
}</code></pre><p>最后重启nginx,Bingo!</p><p>当然这其中还有很多大大小小的坑,欢迎交流。<br> </p>2023-10-31T18:01:02Z任峻宏tag:renny.ren,2005:Article/82017-03-16T21:27:41+08:002020-04-11T13:16:42+08:00https://renny.ren/ch/articles/8写在“毕业”之际<p> 上周回学校审了毕业论文初稿。毕业论文!是啊,这预示着我即将大学毕业了!</p>
<p> 毕业聚餐早已过去,上一次在学校上课已不记得是哪天了,虽然心里感觉自己早已是毕业状态,但一些标志性的日子终将要到来。审论文、清学分、拿证,大学三年即将结束,又一次要各奔东西了。标题之所以打个引号,是因为对于我来说可能大学还有两年的延长期(专升本):( </p>
<div style="text-align:center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/6/content_qc.png" style="width: 519px; height: 278px;" /></div>
<div style="text-align:center;"><span style="font-size:10px;"><span style="color:#bdc3c7;">(图片来自网络,侵删)</span></span></div>
<p> 不论如何,这【极其短暂】的三年快走完了,回顾这大学三年,真的感觉过得飞快,如果和高中的三年比起来,这就像是一年,甚至还不到。回想高中的日子,每天起早贪黑的学习生活,为了一个单纯的目标不断奋斗的日子,那种感受,那种状态,这辈子可能再也回不去了(我也不想再回去,因为实在太苦逼太黑暗)。</p>
<p> 我曾经计划每学期写一次总结,但好像除了大一后来都没写过,现在快要结束了,还是简单总结一下吧。</p>
<h3>大一(部分摘自以前的日志)</h3>
<p> 大一一晃就结束了,感觉过得非常之快。</p>
<p> 初进大学,第一天是相当激动而失落的,激动是因为新的环境、新的生活,失落也是因为陌生的环境、没有一个认识的人,我记得我第一天晚上去操场独自散步后甚至忘了怎么走回寝室(本来我就是彻底路痴)。好在开学之前我就从群里认识了不少素未谋面的同学,我们聚在一起玩牌、玩游戏,虽然大家都不认识。</p>
<p> 生活似乎是从严苛的军训开始的,这让我感觉还处在高中状态,每天很累,但晚上回寝室也很安静,没人玩游戏,没有人说很多话,大家都早点洗洗睡,感觉还是有几分拘束的。那是还在想晚上电脑拿出来会不会被没收,平时能不能出校门之类的问题,现在看来真是把自己当高中生啊!</p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/7/content_jx.jpg" style="width: 200px; height: 331px;" /></p>
<p style="text-align: center;"><span style="color:#bdc3c7;"><span style="font-size:10px;">军训时拍的照片</span></span></p>
<p> 总的来说,大学刚开始一段时间大家都还是那种学生状态的,该集合的集合,该上课的上课,该做什么做什么,不敢丝毫懈怠。不过过了那段时间,越到后来,大家就越松懈了,集合点名基本不能到齐了,逃课的比教室里的还多了,你起床发现那哥们开始关电脑睡觉了,你去应聘发现你同学在招聘人了……其实大学就是这样,是一个相对自由的时期,不是每天被迫学习,也不是放开了耍。每个人对待大学都有自己的态度,每个人在大学都有自己的生活方式,走着不同的属于自己的路。引用一段话,我认为写得相当好:</p>
<pre>
<code>有人大清早就去了自习室,直到熄灯才回来;有人整天宅在宿舍里玩游戏,熬到半夜中午才起;
有人在学生会社团工作很出色,为人行事各种通达;有人收获了爱情,每天从早到晚眼里只有一个人;
有人热爱运动,运动场上永远少不了他的身影;有人找兼职赚钱,为此受了不少磨练;
有人喜欢旅行,翘课,哪怕没钱,也风雨无阻;有人做科研项目,白天黑夜,完全忘我;
有人随波逐流,永远跟着大军,上课下课吃饭睡觉周末休息再上课;
有人追求着错误的东西义无反顾,但却觉得人的一生应该追求一次自己想要的东西,哪怕是错的。
有人笑,有人哭,有人沉默,有人兴奋,有人幸福。
这 就是大学,每个人做着自己做的事情,没有对与错。
谁都没有真正意义上是非曲直的标尺,谁都有为自己伸张正义的自由和理由,谁都有自己的人生准则和方向。
谁都可以随时堕落和振作。
多年以后,这各式各样的人中不见得谁混的一定比谁差。
整天泡自习室的人也许成了教授,翘课做兼职的人也许成了公司老总,宅在宿舍玩游戏的人也许进了知名游戏公司做高级工程师,
出去旅行的人也许单反玩的很溜成了摄影家,收获爱情的人也许此生和爱的人过着平淡的生活
但只要这是他们定义的幸福,他们就是幸福的。
生活没有三六九等,只要你做的是你的梦想中要做的事,你就是站在属于你自己的巅峰上,这也就够了
</code></pre>
<h3> </h3>
<p> 说实话,大一从学习上来说,除了微积分的一点知识外,其他的基本没有学习什么东西(微积分现在也忘完了)。图书馆一学期下来总共只去过两次,晨跑也只有5次,大多数时候没课还是不会提前起床。</p>
<p> 生活上来说,刚开学的军训真是很累,之后慢慢就适应了大学生活。</p>
<p> 刚行课时和期末的时候都是比较轻松的,我没有为期末考试不挂科花太多时间,因为毕竟平时还是多少在学的(虽然学的东西没什么用)。只有中间那段时间比较忙,工作上任务多,学习上课也比较多。我记得最忙的那几天真是东奔西走,开会策划等等,一个任务还没完下一个任务又来了。过了那段时间还真是比较闲了,到最后每周只有几节课了,都是非常自由的。我想大学时光可能是我一生中最美好的时光了。高中学习压力很大,到了大学学习压力是小很多了,但大学就像一个社会,要处理各方面的事务,不仅仅是学习,还有班级事务,社团工作,生活琐事等等。(现在回想起来,有些社团是扯淡的,当初我交了90元的会费然后,就没有然后了...)</p>
<p> 学校开的课真没什么意思,因为我觉得听了也根本没有意义,那些时间还不如自己去图书馆自习。(我想说一句不该说的话:什么锦城课堂大于天,我觉得就是浪费时间。不是说学校不好,但真的..唉,这个问题就扯远了)</p>
<p> 在大学每个人都是扮演着多个角色。大一我担任了班长、计算机科代表、英语组长、外事英语协会成员、钢琴协会成员,此外还参加了系篮球队、外语社和心理协会。</p>
<p> 其实作为英语组长,英语作业我真是一次都没做过,老实说我根本不知道有些啥作业。外协和我想象中还是有些不一样,刚开始面试的时候还是比较紧张,后来还是通过了。开始还是比较积极的,帮部长做事、交流会上台发言。后来感觉我们学习部基本没什么任务了,就是早上去管晨读,不过感觉也只是去打酱油而已。协会聚餐和活动部有几次活动我也没去参加,不知道搞得怎么样。</p>
<p> 阴差阳错兼任了学习委员和班长,班级大小事务都由我负责,这让我学会了担当。感觉我为班级还是做了很多事的,从设计班徽、每周组织课外活动,到写策划、开会、组织答辩、发通知、发礼物等等,通过这些多多少少都锻炼了我自己。</p>
<p> 在大一,班级事务确实占用了我一部分时间。很多人都在寻找大学的社团、班级事务与学习之间的平衡。我还记得当初我想当学习委员的初衷就是想更多的是搞学习方面的事,但后来我发现并不是这样的,什么学习委员劳动委员娱乐委员其实都一样。那段时间我经常在思考,花的这些时间,到底有没有价值。我认为班干部应该是不求回报的,为班级作出贡献是职责所当,但得到的报酬是一些宝贵的经验、经历,可以锻炼自己,学会与人交流沟通,与同学协商合作,学会怎样去完成一次策划,怎样组织大家参加一次集体活动,认识更多的人和事,慢慢建立起一个交际网。这些都是宝贵的财富,虽然有时确实比较累,但我想还是多多少少会有收获的吧。</p>
<h3>大二</h3>
<p> 转眼就到了大二,这一开学,我就开始为各种考试和比赛而准备。</p>
<p> 第一次NCRE二级C考试我由于准备不充分,考试时最后一个离开考场结果还是败下阵来,NECCS也是名落孙山,当时特别沮丧,所以我想这次我一定要一雪前耻。于是通过努力,第二次考二级C不到1小时我就交卷了,因为我知道No Problem! 这是小托马斯般的自信。</p>
<p> 接下来,热爱计算机的我当然不甘心止步二级,哪怕只是为了一个证书。于是我一次性报考了三级+四级,报的时候老师还说不要这样同时报,因为我们学校从来没有人同时通过三四级。我半信半疑的把三级报了就回寝室了,回去后越想越不对劲:凭什么不能同时报?以前没有人过,但凡事总有第一次啊,我会用成绩证明给你看的。于是第二天我哗哗地跑去把四级一起报了。接下来就是一段艰苦的学习时光,我可不想浪费180大洋的报名费,三月底要搞定三四级,另外参加的创业大赛要着手写策划书,四月初还报了全国征文比赛,四月中旬的第二次NECCS我希望能理想一点,同时我还在兼职做点小生意,开发了一个会员系统。</p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/8/content_sys.jpg" style="width: 600px; height: 408px;" /></p>
<p> 天哪,我竟然给自己安排了那么多事。不得不说那段时间真是过得相当充实的,学校要求我们一学期在图书馆坐满900分钟还是多少分钟来着,就只是3月份我就在图书馆待了77个小时(日历上红色的就是时间),不算休息日,平均每天都要在图书馆自学3.5个小时,其实也就是除了上课吃饭睡觉都在图书馆,晚上下着大雨打着伞也要去,经常22点图书馆关门了才被迫走,真的是风雨兼程。</p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/9/content_rl.jpg" style="width: 350px; height: 600px;" /></p>
<p> 其实我觉得我的学习效率真的不高,换做别人应该不需要这么多的时间准备考试的。不过总算功夫不负有心人,花了这么多时间,最终我成为了我们学校第一个高分同时通过计算机三四级的童鞋(后来我才知道别人计算机专业毕业即4级),拿到了获奖率0.5%的全国英语竞赛一等奖,同时兼职做某事营业额达到五六千(虽然最后利润几乎为0)。写这些不是说我多么厉害,只是我真的为自己努力后的成果感到高兴。或许我们有时真的应该问问自己:比你优秀的人都比你还努力,你凭什么不努力?如果问我大学做的最有意义的事情,我觉得可能就是这次了。</p>
<p> 正如马云说的:<strong>人必须要有自己坚信不疑的事情,如果你没有坚信不疑的东西,你不会走下去,尽管一开始你只是坚信了一点点,但是你越做越发现一切皆有可能,自己要相信自己,坚信自己在做什么。</strong></p>
<p> 当然,我一开始也不相信我会当5门课的科代表:</p>
<p> <img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/10/content_kdb.jpg" style="width: 300px; height: 403px;" /></p>
<h3>大三</h3>
<p> 整个大三的主要时间都是实习,但为了能顺利适应工作,我并没有急着去找实习,因为我还是觉得自己学到的东西太少了。但是学校又在催实习,于是我在开学前提前了两周来学校。</p>
<p> 到了学校发现确实很早,店铺基本都没开门,食堂只有几个窗口,校园也很冷清。</p>
<p> 正是炎热的8月,到了寝室苦逼地发现电扇坏了!一个人在寝室,晚上蚊子很多,主要是太闷热了,根本睡不着!于是第二天五点半天还没亮我就起床了,我想去看看图书馆是否开门,结果还开得挺早的,一个人在图书馆特别特别安静!</p>
<p><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/11/content_library.jpeg" style="width: 500px; height: 281px;" /></p>
<p> 写着写着发现成流水账了,就先到这里吧。</p>
<p> 毕业之际,祝好!</p>
2020-04-11T13:16:42Z任峻宏tag:renny.ren,2005:Article/72017-03-16T20:19:23+08:002022-03-07T13:26:36+08:00https://renny.ren/ch/articles/7JS 简单实现弹出层效果<h3>起源</h3><p>今天在添加微信联系方式的时候有了这个需求:需要点击按钮后弹出一个显示图片的对话框。</p><p>很简单的一个功能,我一开始想的是直接用<code>alert()</code>来弹出对话框然后显示图片,然后发现似乎这样并不能插入图片。 而且为了实现更酷炫的效果,最后我采用了以下方法:</p><h3>思路</h3><p>1.在页面中央直接摆放好图片,初始状态设为不显示;</p><p>2.放置一个覆盖整个页面的div,堆叠到图片下层,用于实现背景效果,初始状态也是不显示;</p><p>3.按钮的onclick事件来触发openWindow(),控制1、2的显示;</p><p>4.点击背景div触发closewindow事件,将显示状态还原。</p><h3>页面代码</h3><pre><code class="language-html"><img src="/wechat.png" alt="点击弹出二维码" onclick="openWindow()" /> # 这个当按钮
<div id="black_overlay" onclick="closeWindow()"></div> # 这是遮罩层背景
<%= image_tag 'wechat_add.png', id: "wechat" %> # 这是要弹出显示的图片
</code></pre><h3>JS</h3><pre><code class="language-javascript">function openWindow(){
document.getElementById('wechat').style.display = 'block';
document.getElementById('black_overlay').style.display='block';
}
function closeWindow(){
document.getElementById('wechat').style.display = 'none';
document.getElementById('black_overlay').style.display='none';
}
</code></pre><h3>CSS</h3><pre><code class="language-css"> #wechat{
height: 400px;
width: 450px;
display: none;
position: abosluted;
top: 20%;
left: 35%;
z-index: 1002;
}
#black_overlay{
display: none;
position: absoluted;
background-color: black;
z-index: 1001;
left: 0%;
top: 0%;
width: 100%;
height: 100%;
opacity: 0.8;
-moz-opacity: 0.8;
filter: alpha(opacity=80);
}
</code></pre><h3>效果</h3><p>最后实现的效果如下:</p><p><img class="image_resized" style="width:400px;" src="//assets.renny.ren/ckeditor_assets/pictures/5/content_qr_code.png" alt=""></p>2022-03-07T13:26:36Z任峻宏tag:renny.ren,2005:Article/12017-02-28T08:00:00+08:002020-04-10T23:32:23+08:00https://renny.ren/ch/articles/1速递易工作总结及感想<p> 时间过得飞快,一转眼我都实习了快5个月了。</p>
<h3>工作总结</h3>
<p> 我把目前为止所做的工作分为三个阶段来总结:</p>
<p> <strong>第一阶段</strong>是学习阶段,作为一个实习生,我的主要任务是学习基础知识,只有把基础的知识掌握好了才能谈其他东西。</p>
<p> 这个阶段我主要是看了三本书,分别是《Programming Ruby》、《ruby基础教程》和《rails tutorial》。</p>
<p> 入职前我得到了《Programming Ruby》《ruby基础教程》这两本书,我要求自己在入职之前的9天内把他们啃掉,一共1424页,这对我来说是一个不小的挑战,因为毕竟这是技术书籍,不是看小说,边看还要边理解、学习。刚接触ruby没多久,不懂的东西太多了,于是国庆节7天我就待在家里死磕,每天除了吃饭睡觉都是在学这个东西。最后我也基本完成了这个目标。</p>
<p> 通过这次学习,我掌握了很多ruby和rails相关的基础知识。从这个阶段中总结的一个经验就是,学一门语言首要解决的问题是迟迟不肯开始动手,如果只是看书而没有实际的练习就很容易忘掉,最后等书全部看完忘得也差不多了。所以务必要尽快进入实操的阶段,尽量通过解决实际问题来整合知识、巩固知识并加深理解。</p>
<p> 之后我做了一个日志练习学会相关的操作,包括状态码计数、相关gem的使用、代码的优化、重构等等,我认为这是一个很好的入门练习,所涉及的知识很全面,基本涵盖了我之前学到的所有基础知识,同时也加深了我的理解。这个练习我重构了三次,可以看到每次都有所进步,最后的效果是明显的,和刚开始写出来的东西相比优化了很多。</p>
<p> 这个做完之后我开始写爬虫来抓取某网站的一些数据,然后在后台展示出来。这是我第一次接触爬虫,通过这个练习也学到了很多知识,后台框架先是用了sinatra后用rails,帮助我更加了解了这两个框架。</p>
<h3> </h3>
<p> <strong>第二阶段</strong>是接触项目的阶段,基础的东西学完了,我开始接触公司的实际项目,这是我第一次看到真实的项目代码是什么样子。</p>
<p> </p>
<p> 刚开始是一头雾水的,每块内容单独来看虽然好像看得懂,但我不清楚到底做了些什么,不清楚它们的内在联系或是一些细节的东西。我甚至连把项目跑起来都花了很长的时间。</p>
<p> 通过这个阶段,我开始更多地和公司其他人有了交流合作,相对来说公司的项目代码比一些其他的个人写的代码更加规范,更有可读性和学习价值。</p>
<p> 我了解了一些开发流程,学会了一些技能和方法(比如git相关操作),第一次接触了前后端分离,也了解了一个公司里面的各个部门的员工到底是在做些什么事情(以前我不知道公司要分产品、测试、前端后端这些部门,学校可不会教这些东西,我以为这些都是一个人做的)。</p>
<h3> </h3>
<p> <strong>第三阶段</strong>是参与开发的阶段,正好公司有新项目来了,这次我参与了某系统的开发,从需求分析,出原型,讨论修改,到后端开发,前端开发,联调,测试等,我第一次经历了这个完整的开发流程。</p>
<p> </p>
<p> 说实话学到的干货不是很多,但是更多的是学到了一个项目从构思到最后完成要经历哪些过程,我觉得这个就和修房子是一样的,先作好图纸规划,打好地基,然后一步步往上建造,最后竣工。除此之外还锻炼了我的团队协作能力、交流沟通能力等,更重要的是解决问题的能力。</p>
<p> 我想一个程序员很重要的两个能力就是超强的自学能力和分析解决问题的能力。当我实习完后,可能这些项目若干年后也没用了,就是一堆符号而已,若干年后我自己回去看我写的这些代码可能也看不懂了,但是解决问题的能力是很重要的,这个报错完了还会出下一个报错,不能解决就寸步难行。学习能力也是一样,有自学能力什么都可以再学,知识总是在进步的。</p>
<h3>感想</h3>
<p> 我还记得人生第一天上班的心情是激动的,第一次走出学校,了解到一家公司是怎样运作的,一切对我来说都很新鲜。</p>
<p> 刚开始时每天要回学校住,不过我坚持了一周就受不了了,原因是太远,每天早上挤地铁晚上等公交,来回至少4小时。 于是我果断在附近租了房子,没有别的要求,越近越好! 按此要求专门请假了一天去看房子,所以最后我只需要漫步7分钟就能到公司。虽然房间很小,但我还是很高兴,因为至少我可以节省更多时间花在学习上了。</p>
<p style="text-align: center;"><img alt="" src="//assets.renny.ren/ckeditor_assets/pictures/4/content_room.jpg" style="width: 400px; height: 300px;" /></p>
<p> 公司氛围很好,大家相处得很融洽,刚来公司时我可以说是除了看过一点书,什么都不会,就连git提交代码我也是一脸懵逼。不过很快我就熟悉了,也学到了一些东西。但每天大家在公司说的很多专业名词,钉钉里的很多讨论,我当时其实还是云里雾里、不明嚼栗的。我觉得我不懂的实在太多,恨不得抓紧时间全部学完,我甚至想过每天就睡在公司还可以不用租房子,我把牙刷毛巾带来,正好厕所也可以洗漱哈哈哈:)</p>
<p> 我认为我是<a href="http://baike.baidu.com/link?url=e7KzgazS83zXdsvsV7mDkyTLEEQN6aNGzM8BqFoiPHMaIcseSI-b5Ae8YIufBnx6R3eyWx3peCYoBwADNni3-unrWQmA-axIJDfmIhETT7Eb-Y81DzvLyW7m0pOqENxq">九型性格</a>中偏和平型和完美型的人。我虽然平时话很少,但我看&听得比较多。 我还记得达达说的付出多少就收获多少,记得第一次和海洋哥一起加班到23点,记得凯哥耐心的项目讲解和指导,记得刚上班时丁哥过来和我打招呼(这次某迭代我没能做完深感遗憾),还有和赵伟一起学习公司文化,和磊哥的交流很开心,和Andy一起互相帮助一起打球,和佳成合作写爬虫,还有每天总是到的最早的杨哥和超超,给我莫大帮助的小虫哥,给我们发橘子吃的朱老师等等……整个开发团队,你们真的很棒! 虽然实习时间不长,但我真的很高兴认识大家。我的生活很单调,每天两点一线,在公司工作让我感到不孤独、有归属感。</p>
<p> 最近由于学校方面的原因我不得不暂停实习了,说实话这是一个艰难的决定。不远的将来我还希望能和大家共同成长。</p>
<p> 最后祝 <code>{ 速递易: 越来越好, 各位小伙伴: [] << "文档自动生成" << "测试一次通过" << "代码无bug" << "老板不改需求" << "服务没有报警" }</code> 好了似乎可以下班了。</p>
2020-04-10T23:32:23Z任峻宏tag:renny.ren,2005:Article/92017-02-14T08:00:00+08:002022-03-07T13:26:12+08:00https://renny.ren/ch/articles/9在 Aliyun ECS 上用 Nginx + Passenger 部署 Rails 应用时安装 Passenger 的问题<h3>问题</h3><p>第一次部署Rails应用,参照<a href="https://github.com/ruby-china/homeland/wiki/Ubuntu-12.04-%E4%B8%8A%E4%BD%BF%E7%94%A8-Nginx-Passenger-%E9%83%A8%E7%BD%B2-Ruby-on-Rails">资料1</a> <a href="https://ruby-china.org/wiki/deploy-rails-on-ubuntu-server">资料2</a> <a href="https://github.com/huacnlee/init.d">资料3</a> <a href="https://github.com/ruby-china/homeland/wiki/%E5%A6%82%E4%BD%95%E5%AE%89%E8%A3%85-Rails-%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83">资料4</a>等资料在服务器上装好了ruby, rvm, 相关依赖等,但在安装passenger时,参照<a href="https://www.phusionpassenger.com/library/install/nginx/install/oss/xenial/">此官方文档</a>,安装了秘钥和HTTP支持,也添加了APT源: <code>sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'</code></p><p>但在执行 <code>sudo apt-get update</code>时遇到以下错误:</p><pre><code class="language-plaintext">Hit:1 http://mirrors.cloud.aliyuncs.com/ubuntu xenial InRelease
Hit:2 http://mirrors.aliyun.com/ubuntu xenial InRelease
Get:3 http://mirrors.cloud.aliyuncs.com/ubuntu xenial-security InRelease [102 kB]
Get:4 http://mirrors.aliyun.com/ubuntu xenial-security InRelease [102 kB]
Get:5 http://mirrors.cloud.aliyuncs.com/ubuntu xenial-updates InRelease [102 kB]
Get:6 http://mirrors.aliyun.com/ubuntu xenial-updates InRelease [102 kB]
Get:7 http://mirrors.cloud.aliyuncs.com/ubuntu xenial-proposed InRelease [253 kB]
……
……
Get:45 http://mirrors.aliyun.com/ubuntu xenial-proposed/universe i386 Packages [31.6 kB]
Get:46 http://mirrors.aliyun.com/ubuntu xenial-proposed/universe Translation-en [12.9 kB]
Ign:47 https://oss-binaries.phusionpassenger.com/apt/passenger xenial InRelease
Ign:48 https://oss-binaries.phusionpassenger.com/apt/passenger xenial Release
Ign:49 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main amd64 Packages
Ign:50 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main i386 Packages
Ign:51 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main all Packages
Ign:52 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en_US
Ign:53 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en
Ign:49 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main amd64 Packages
Ign:50 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main i386 Packages
Ign:51 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main all Packages
Ign:52 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en_US
Ign:53 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en
Ign:49 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main amd64 Packages
Ign:50 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main i386 Packages
Ign:51 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main all Packages
Ign:52 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en_US
Ign:53 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en
Ign:49 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main amd64 Packages
Ign:50 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main i386 Packages
Ign:51 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main all Packages
Ign:52 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en_US
Ign:53 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en
Ign:49 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main amd64 Packages
Ign:50 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main i386 Packages
Ign:51 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main all Packages
Ign:52 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en_US
Ign:53 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en
Err:49 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main amd64 Packages
Bad header line
Ign:50 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main i386 Packages
Ign:51 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main all Packages
Ign:52 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en_US
Ign:53 https://oss-binaries.phusionpassenger.com/apt/passenger xenial/main Translation-en
Fetched 6,467 kB in 2min 3s (52.4 kB/s)
Reading package lists... Done
W: The repository 'https://oss-binaries.phusionpassenger.com/apt/passenger xenial Release' does not have a Release file.
N: Data from such a repository can't be authenticated and is therefore potentially dangerous to use.
N: See apt-secure(8) manpage for repository creation and user configuration details.
E: Failed to fetch https://oss-binaries.phusionpassenger.com/apt/passenger/dists/xenial/main/binary-amd64/Packages Bad header line
E: Some index files failed to download. They have been ignored, or old ones used instead.
</code></pre><p>与passenger相关的都是Ign或者Err了,然后报的<code>Bad header line</code>也不知道是怎么回事,总之到<code>apt-get update</code>这步永远也不能成功执行。</p><p>服务器系统是Ubuntu 16.04 LTS,我在本地物理机上相同系统也试了,没有出现这个问题,passenger可以成功安装,所以应该不是服务器系统或者APT源的问题。有人遇到过这种情况吗,google, stackoverflow 试了各种方法折腾了一天一夜也没解决,准备放弃 改用Puma了。BTW,用老的方法<code>passenger-install-nginx-module</code>也不行,downloading Nginx 之后就会卡住不动了。</p><h3>解决方法</h3><p>不得不说社区真强大,提问没多久就得到了@darkbaby123的解答:</p><blockquote><p>说说我前段时间碰到的类似问题,希望对你有帮助。当时也是装第三方 apt 源不成功,不过错误类型 是 apt-get update 报 403 。后来发现是阿里云的主机对 apt 配置了代理,应该是为了让内网 机器访问自家的源加速,但影响了第三方源的获取。</p><p>代理配置在 /etc/apt/apt.conf 里,把它注释掉再 update 就没问题了,注释用 # 号: <code>Acquire::http::Proxy "http://mirrors.aliyun.com/";</code></p></blockquote><p><a href="https://ruby-china.org/topics/32310">原文地址</a></p>2022-03-07T13:26:12Z任峻宏tag:renny.ren,2005:Article/62017-01-09T08:00:00+08:002022-03-07T13:26:06+08:00https://renny.ren/ch/articles/6[译] Common Rails Idioms that Kill Database Performance<h2>前言</h2><p>关于数据库查询,让我们来看一看那些让你的程序停滞不前的罪魁祸首。</p><p>我还记得我第一次看到rails的ActiveRecord,那是一次启发。那是2005年的时候,当时我在给一个PHP程序写SQL语句。突然间,写数据库从单调繁杂的零星工作变得简单且有趣了。</p><p>...然后我就开始关注到性能(performance)的问题。</p><p>ActiveRecord它本身并不慢。我停止把注意力花在那些实际正在运行的查询上。结果是,当数据量变得庞大后,在rails的增删查改操作中一些最符合语言习惯的数据库查询就十分低效。</p><p>这篇文章我们将讨论这其中的三个罪魁祸首。但首先,让我们先聊聊怎么知道你的数据库查询是否高效。</p><p> </p><h2>测量性能</h2><p>如果你数据量足够小的话每个数据库查询都是高效的。所以为了真正地感知效率,我们需要以一个生产级别的数据库为基准。在我们的示例中,我们将使用一个有大约22000条记录的叫做faults的表。</p><p>我们会用到postgres。在postgres中,衡量性能的方法是使用explain。比如:</p><pre><code class="language-plaintext"># explain (analyze) select * from faults where id = 1;
QUERY PLAN
----------------------------------------------------------------------------------------
Index Scan using faults_pkey on faults (cost=0.29..8.30 rows=1 width=1855) (actual
time=0.556..0.556 rows=0 loops=1)
Index Cond: (id = 1)
Total runtime: 0.626 ms
</code></pre><p>这显示出了执行这次查询的预计花费 (cost=0.29..8.30 rows=1 width=1855) 和执行的实际时间(actual time=0.556..0.556 rows=0 loops=1)。</p><p>如果你想要一个更易读的格式,你可以让postgres以YAML文件打印出来。</p><pre><code class="language-plaintext"># explain (analyze, format yaml) select * from faults where id = 1;
QUERY PLAN
--------------------------------------
- Plan: +
Node Type: "Index Scan" +
Scan Direction: "Forward" +
Index Name: "faults_pkey" +
Relation Name: "faults" +
Alias: "faults" +
Startup Cost: 0.29 +
Total Cost: 8.30 +
Plan Rows: 1 +
Plan Width: 1855 +
Actual Startup Time: 0.008 +
Actual Total Time: 0.008 +
Actual Rows: 0 +
Actual Loops: 1 +
Index Cond: "(id = 1)" +
Rows Removed by Index Recheck: 0+
Triggers: +
Total Runtime: 0.036
(1 row)
</code></pre><p>现在我们只需要关注 "Plan Rows" 和 "Actual Rows" - Plan Rows 在最坏的情况下,数据库需要循环多少行来响应你的查询 - Actual Rows 当它执行这次查询时,数据库实际循环了多少行?</p><p>如果"Plan Rows" 是1,就像上边这样,那么这次查询可能是高效的。如果"Plan Rows" 等于这个数据库的行数,那么意味着这次查询将会做一个“全表扫描”,并不够好。</p><p>既然你知道了怎样来测量查询的效率,那我们来看一些常规的rails语句,看它们是怎么运行的。</p><p> </p><h2>计数(Counting)</h2><p>在Rails views中经常看到这样的代码: <code>Total Faults <%= Fault.count %> </code>对应的SQL为: <code>select count(*) from faults; </code>让我们把它放到<code>explain</code>里看看会发生什么。</p><pre><code class="language-plaintext"># explain (analyze, format yaml) select count(*) from faults;
QUERY PLAN
--------------------------------------
- Plan: +
Node Type: "Aggregate" +
Strategy: "Plain" +
Startup Cost: 1840.31 +
Total Cost: 1840.32 +
Plan Rows: 1 +
Plan Width: 0 +
Actual Startup Time: 24.477 +
Actual Total Time: 24.477 +
Actual Rows: 1 +
Actual Loops: 1 +
Plans: +
- Node Type: "Seq Scan" +
Parent Relationship: "Outer"+
Relation Name: "faults" +
Alias: "faults" +
Startup Cost: 0.00 +
Total Cost: 1784.65 +
Plan Rows: 22265 +
Plan Width: 0 +
Actual Startup Time: 0.311 +
Actual Total Time: 22.839 +
Actual Rows: 22265 +
Actual Loops: 1 +
Triggers: +
Total Runtime: 24.555
(1 row)
</code></pre><p>哇,我们的示例count查询循环了22265行 — 整个表!在postgres中,counts总是循环整个数据集。 你可以通过加上<code>where</code>条件来减少数据集的大小。取决于你的要求,你可以减到足够小直到效率可以接受。</p><p>另外一个解决此问题的方法是把你的count值缓存起来。<a href="https://ruby-china.org/topics/32073">你可以这样来做:</a> <code>belongs_to :project, :counter_cache => true </code>另外,当去检查这次查询是否返回任何数据时,用<code>Users.exists?</code>而不是<code>Users.count>0</code>。这样查询的结果更加高效。</p><p> </p><h2>排序(Sorting)</h2><p>几乎每个程序都至少有一个index页面,你从数据库里面读取了最新的20条记录然后展示出来。怎样做更简单?</p><p>读数据的代码大概像这样: <code>@faults = Fault.order(created_at: :desc) </code>对应的sql为: <code>select * from faults order by created_at desc; </code>那么我们来分析一下:</p><pre><code class="language-plaintext"># explain (analyze, format yaml) select * from faults order by created_at desc;
QUERY PLAN
--------------------------------------
- Plan: +
Node Type: "Sort" +
Startup Cost: 39162.46 +
Total Cost: 39218.12 +
Plan Rows: 22265 +
Plan Width: 1855 +
Actual Startup Time: 75.928 +
Actual Total Time: 86.460 +
Actual Rows: 22265 +
Actual Loops: 1 +
Sort Key: +
- "created_at" +
Sort Method: "external merge" +
Sort Space Used: 10752 +
Sort Space Type: "Disk" +
Plans: +
- Node Type: "Seq Scan" +
Parent Relationship: "Outer"+
Relation Name: "faults" +
Alias: "faults" +
Startup Cost: 0.00 +
Total Cost: 1784.65 +
Plan Rows: 22265 +
Plan Width: 1855 +
Actual Startup Time: 0.004 +
Actual Total Time: 4.653 +
Actual Rows: 22265 +
Actual Loops: 1 +
Triggers: +
Total Runtime: 102.288
(1 row)
</code></pre><p>这里我们可以看到,每次你做这个查询时数据库会对所有的22265行进行排序。这样可不好。</p><p>默认情况下,SQL中的每个<code>order_by</code>语句都会实时地对数据集排序,没有缓存。</p><p>解决方法是使用index。像这种简单的情况,加一个sorted index到created_at column中将会大大提高查询速度。</p><p>在你的Rails migration中你可以:</p><pre><code class="language-ruby">class AddIndexToFaultCreatedAt < ActiveRecord::Migration
def change
add_index(:faults, :created_at)
end
end
</code></pre><p>这会运行以下的SQL: <code>CREATE INDEX index_faults_on_created_at ON faults USING btree (created_at); </code>这里最后的<code>created_at</code>指的是排列顺序,默认是升序。</p><p>现在我们再运行一下排序的查询,我们可以看到不再包含一个排序的步骤了,只是简单地从index里读取已经排序好的数据。</p><pre><code class="language-plaintext"># explain (analyze, format yaml) select * from faults order by created_at desc;
QUERY PLAN
----------------------------------------------
- Plan: +
Node Type: "Index Scan" +
Scan Direction: "Backward" +
Index Name: "index_faults_on_created_at"+
Relation Name: "faults" +
Alias: "faults" +
Startup Cost: 0.29 +
Total Cost: 5288.04 +
Plan Rows: 22265 +
Plan Width: 1855 +
Actual Startup Time: 0.023 +
Actual Total Time: 8.778 +
Actual Rows: 22265 +
Actual Loops: 1 +
Triggers: +
Total Runtime: 10.080
(1 row)
</code></pre><p>如果你要依据多个columns排序,你需要创建一个由多个columns排序的index。在Rails migration中: <code>add_index(:faults, [:priority, :created_at], order: {priority: :asc, created_at: :desc) </code>当你开始做更复杂的查询时,通过explain来执行它们是一个好办法,要趁早并且经常这样。</p><p> </p><h2>Limits and Offsets</h2><p>我们多半不会把数据库中的所有数据都放到一个index页面里展示。我们用的是paginate,一次只显示10, 30或者50条。实现这个的最常规的方法是用limit和offset: <code>Fault.limit(10).offset(100) </code>对应的SQL为: <code>select * from faults limit 10 offset 100; </code>现在如果我们运行explain,可以看到一些奇怪的东西。扫描的行数是110,等于limit加上offset。</p><pre><code class="language-plaintext"># explain (analyze, format yaml) select * from faults limit 10 offset 100;
QUERY PLAN
--------------------------------------
- Plan: +
Node Type: "Limit" +
...
Plans: +
- Node Type: "Seq Scan" +
Actual Rows: 110 +
...
</code></pre><p>如果你把offset改成10000你会看到扫描的行数跳到了10010,这个查询会变慢64倍。</p><pre><code class="language-plaintext"># explain (analyze, format yaml) select * from faults limit 10 offset 10000;
QUERY PLAN
--------------------------------------
- Plan: +
Node Type: "Limit" +
...
Plans: +
- Node Type: "Seq Scan" +
Actual Rows: 10010 +
...
</code></pre><p>这就可以得出一个不好的结论:当分页的时候,后面的页面要比靠前的页面加载得慢。假设一个页面有100条记录(像上面这个示例一样),那么第100页将比第一页慢13倍。</p><p>那我们该怎么办?</p><p>老实说,我还没有找到一个完美的解决方案。首先我想的是减小数据量,我就不用开始时分100页或者1000页了。</p><p>如果你不能减少数据集,最好的方法可能是用<code>where</code>语句替换掉<code>offset/limit</code>。</p><pre><code class="language-plaintext"># You could use a date range
Fault.where("created_at > ? and created_at < ?", 100.days.ago, 101.days.ago)
# ...or even an id range
Fault.where("id > ? and id < ?", 100, 200)
</code></pre><h2>结论</h2><p>我希望这篇文章能够说服你,真的应该利用好postgres的explain这个功能来查找你数据库查询中潜在的性能问题。即使是最简单的查询也会导致重大的性能问题,所以这值得去检查。:)</p><p><a href="http://blog.honeybadger.io/common-rails-idioms-that-kill-database-performance/">原文链接</a></p>2022-03-07T13:26:06Z任峻宏tag:renny.ren,2005:Article/32016-12-04T08:00:00+08:002022-03-07T13:25:58+08:00https://renny.ren/ch/articles/3Git 常用操作总结<p>git操作与程序员的日常工作紧密相关,以下列出一些常用的操作</p><p>查看远程分支 <code>git branch -a</code></p><p>更新远程分支 <code>git remote update</code></p><p>删除远程分支 <code>git push origin --delete <branchName></code> 或 <code>git push origin :<branchname></code></p><p>重命名本地分支 <code>git branch -m <oldname> <newname></code></p><p>重命名远程分支</p><p>在git中重命名远程分支,其实就是先删除远程分支,然后重命名本地分支,再重新提交一个远程分支。</p><p>创建并切换分支 <code>git checkout -b newbranch</code> 等价于:</p><p><code>git branch newbranch;</code><br><code>git checkout newbranch</code></p><p>链接远程仓库 <code>git remote add origin <server></code></p><p>合并commit <code>git rebase -i HEAD~2</code> <a href="https://blog.csdn.net/cmwly/article/details/76021267?locationNum=6&fps=1">链接</a></p><p>恢复到指定的commit(保留代码修改) <code>git reset --soft <resetVersionHash></code></p><p>恢复到指定的commit(不保留代码修改) <code>git reset --hard <resetVersionHash></code></p><p>恢复到指定的commit(到 git add 之前的状态,即绿字变红字) <code>git reset <resetVersionHash></code> 或 <code>git reset --mixed <resetVersionHash></code> --mixed 是默认参数</p><h3>git pull时出现冲突,如何放弃本地修改,强制pull远程代码到本地?</h3><p><code>git fetch origin # 获取最新版本</code><br><code>git reset --hard origin/master # 把HEAD指向最新下载的版本</code></p><h3> </h3><p>删除暂存区或分支上的文件,但保留本地文件 <code>git rm --cached file_path</code></p><p>修改提交信息(commit message) <code>git commit --amend -m "new-commit-message"</code></p><p>撤销git add(还没有git commit之前) <code>git reset <filename></code> 撤销所有add的文件: <code>git reset HEAD .</code></p><h3>切换分支时保存已修改的代码</h3><p>假设有两个分支:1和2。我在1上开发了一半,忽然需要切换到2去改bug。 这种情况有两个方法:</p><p>1.及时commit代码</p><p>在分支1上把已经开发完成的部分代码commit,不push,然后切换到分支2修改代码,做完了commit,所有分支互不影响,这是一个理想的方法。</p><p>2.使用git stash</p><p>在分支1上:<code>git stash</code></p><p>或者 <code>git stash save “修改的信息"</code></p><p>然后切到分支2修改代码完成,再回到分支1时,使用:<code>git stash pop</code></p><p>或者 <code>git stash list</code><br><code>git stash apply stash@{0} </code>就可以回到保存的版本了。</p><h3>解决每次git提交都要输入用户名和密码</h3><p>1.查看远程仓库: <code>git remote -v</code></p><p>2.此时仓库链接多半是http链接,将其删除: <code>git remote remove origin</code></p><p>3.重新用ssh链接远程仓库: <code>git remote add origin <address></code> (这个地址应类似git@github.com:xxx.git)</p><p> </p><h3>参考文章</h3><p><a href="http://zengrong.net/post/1746.htm">http://zengrong.net/post/1746.htm</a></p><p><a href="http://www.cnblogs.com/deepnighttwo/archive/2011/06/18/2084438.html">http://www.cnblogs.com/deepnighttwo/archive/2011/06/18/2084438.html</a></p><p><a href="http://www.tonitech.com/2344.html">http://www.tonitech.com/2344.html</a></p><p><a href="http://gitref.org/">http://gitref.org/</a></p><p><a href="http://www.oschina.net/news/68437/seven-git-hacks-you-just-cannot-ignore">http://www.oschina.net/news/68437/seven-git-hacks-you-just-cannot-ignore</a></p>2022-03-07T13:25:58Z任峻宏tag:renny.ren,2005:Article/52016-12-02T08:00:00+08:002022-03-07T13:25:52+08:00https://renny.ren/ch/articles/5[译] Terminal 功夫——方便开发者的实用技巧<h2>前言</h2><p>这是我在过去几年中提炼出的一些方便你编程的小技巧和提示。在这篇文章中,你将学到如何更有效率地做你每天做的事情。 </p><h2>快速搜索命令行历史记录</h2><p>我真的很难记住一些命令,特别是那些有一大堆参数的。而且有时候更烦,你不得不停下来然后又去google你两个小时之前引用的某个命令。不用担心,<code>reverse-i-search</code>可以拯救你。<strong>打开你的终端,按下</strong><code><strong>Ctrl + R</strong></code>。输入你最近使用的命令的前几个字符,它会显示出符合你要查询的命令。如果有多个匹配的命令,重复按<code>Ctrl + R</code>直到找到你想要的然后按<code>Ctrl + E</code>选择即可。<br>你也可以输入<code>history</code>就可以显示出你之前执行过的命令列表。 </p><h2>缩短退格时间</h2><p>你是否遇到过很长的查询,比如跨了4行,然后你想跳到前边去做一些修改,或者是你写了很长的命令,跨了4行,结果发现只是在最开始的地方有点错?这时候你可能要按箭头,然后一个一个字符地把光标移到开头去。但你可以使用快捷方法,这些方法可以节省你很多按方向键和空格键的时间。</p><p><code>Ctrl + K</code> - 删除光标右边的所有内容</p><p><code>Ctrl + W</code> - 删除光标左右的所有内容(一次一个单词)</p><p><code>Ctrl + A</code> - 跳到行首</p><p><code>Ctrl + E</code> - 跳到行尾 </p><h2>在 rails console 中用 _ 访问最后一个表达式</h2><p>这是另一个我经常犯的错误:在 rails 控制台中查询一个 ActiveRecord model,忘了把结果保存在一个变量里,然后按<code>↑</code>加上一个变量再做一遍。</p><p>很幸运比我聪明的人也有这个问题,实际上他们已经解决了。每次你在 console 里面执行一个命令或者是功能,其返回值都会被保存在一个叫做<code>_</code>的变量里(对,就是一个下划线)</p><p>所以,比如你在 console 里面执行了<code>User.first(5)</code>。你得到了前5个 users 但是你忘了存在一个变量里。这时你只需要<code>_.first.name</code>或者更好的是你可以 <code>users = _</code>. 现在变量 users 就等于<code>User.first(5)</code>的返回值了。 </p><h2>找到任何URL的 Controller 和 Action</h2><p>这个也超有用,尤其是当你的 routes 文件达到了500行,你有5个不同的 namespaces,还有一堆自定义的 actions 时。你有个URL,你想知道它执行的是哪个 Controller 和 Action。常见的方法是打开 routes 文件然后扫描整个文件去查找。或者你会用 <a href="https://github.com/dejan/rails_panel">RailsPanel</a> 来查看 Controller 和 Action。但这都要耗些时间。</p><p>如果你想要秒秒钟搞定这件事,在 rails console 里面用这个命令:</p><p><code>Rails.application.routes.recognize_path "http://localhost:3000/users/11"</code></p><p>Ps: 当用在不是GET的action上的时候要小心,可能会返回正确的controller,错误的action</p><h2>在 sandbox 里面玩</h2><p>你想在 console 中弄乱一些值然后看看程序怎么反应,但是你又不想破坏你的原始数据库?不用害怕,rails console 里面有一个内置的选项为此而设。用以下命令启动 rails console</p><p><code>rails console --sandbox</code></p><p>在这个模式下 console 被包装在数据库事务中启动。当你退出回话后,事务将回滚。所以你可以随意增删改数据,当你退出 console 时数据库会被恢复到原始状态。但要小心在沙箱里面运行事务因为并非所有数据库都可以处理嵌套事务。 </p><h2>重新加载控制台</h2><p>你在 console 里面试了一些东西,不起作用。然后你要改一下你的代码再试一次,这时候不用关掉 console 又重新启动,只需 <code>reload!</code> </p><h2>去除ri和rdoc</h2><p><img src="http://www.rubyonrails365.com/images/2015/june/xkcd_bundle_installing-58173d59.png" alt="bundle installing"></p><p>你在 Github 上看到一个很好的项目,你立即把它 clone 下来然后<code>bundle install</code>。然后...你花了30分钟来下载所有的 gems,而这其中一半的时间都花在了下载 RDoc 和 ri 文件上了,你可能从来不用这些,甚至你可能根本不知道这是什么鬼。可以说只要你联网了你就不是必须要用他们,但是当你 bundle 的时候会自动下载。这里有两个方法来处理:</p><p>第一,如果你是安装单个 gem,使用</p><p><code>gem install rails --no-ri --no-rdoc</code></p><p>这样就告诉 bundler 不要下载 RDoc 和集成 ri 文件。这样就节省了很多时间和空间。</p><p>但上面这个方法只是在你安装单个gem的时候有用。如果你想让安装所有gem的时候都默认这样做的话:</p><p>步骤1. 在home目录下创建一个文件命名为<code>.gemrc</code></p><p><code>cd ~; touch .gemrc;</code></p><p>步骤2. 在你的编辑器里面打开这个文件</p><p><code>subl ~/.gemrc # 用你的编辑器打开这个文件</code></p><p>步骤3. 将以下代码粘贴进去即可</p><pre><code class="language-plaintext">gem: --no-ri --no-rdoc
install: --no-rdoc --no-ri
update: --no-rdoc --no-ri
</code></pre><p>我们还可以更深入一点。很多大中型项目的gem平均要跑50秒,这还不包括解决每个gem的依赖。例如<a href="https://github.com/discourse/discourse">Discourse</a>这个项目有大概<a href="https://github.com/discourse/discourse/blob/master/Gemfile">150个gems</a></p><p>我们可以通过并发安装gem让这变得更快,确保充分利用你的网络带宽处理能力。我们通过给bundler加上<code>--jobs</code>这个参数来完成。</p><p><code>bundle install discourse --jobs=4</code></p><p>运行这个命令你会看到gems的安装快得飞起。然而这不是任何时候都适用的,有时候会出现死锁或者冲突。解决方法只需运行:</p><p><code>bundle install discourse --jobs=1</code></p><p>这样就设置回了默认的情况。 </p><h2>看一下gem内部</h2><p>或许因为你的好奇心,你想看一下一个特定的功能是怎么执行的,想看一下你正在使用的一个gem的源代码。是的,你可以每次去google github 里面的项目然后在那里看。但是其实可以不用这样。只需要</p><p><code>EDITOR=subl bundle open devise</code></p><p><code>subl</code>这里是你的编辑器,<code>devise</code>是你的gem名字。这里我们打开了devise这个gem。这样做的好处是它可以打开你正在使用的gem的版本。</p><p>然后你可以在项目里面做你想做的事情,你可以修改代码来理解它做了什么事,可以修改下function甚至是增加functions。当你看完了代码之后,你可以用以下命令使它回到初始状态</p><p><code>gem pristine devise</code> </p><h2>在你的代码里面写注释</h2><p>当你写代码的时候,你可能想在代码里面快速地做一些笔记。或许你可能想写下“想要重构”之类的。不用切换到其他地方去做笔记,rails提供了一个东西,你只需要在你的程序里面使用 <code>#TODO</code>.</p><pre><code class="language-ruby">#TODO make this a one line function.
def new
end
</code></pre><p>之后你可以在console里面输入以下命令来查看你的所有笔记: <code>bundle exec rake notes:todo</code></p><p>但这样久了之后你可能会发现你有很多 todos,你可能想要描述得更加具体,你可以做一些自定义的修改,比如:</p><pre><code class="language-ruby">#REFACTOR make this a one line function.
def new
end
</code></pre><p>然后这样去查找: <code>bundle exec rake notes:custom ANNOTATION=REFACTOR</code> </p><h2>找到任何方法的源地址</h2><p>有时你在 console 中使用一个方法,尤其是你引用的 gem 包里面定义的方法,你想看看它的源代码。方法经常在不同的文件之间有相同的名称,你不确定是调用的哪个,或者说想查看这个方法在哪里定义的。这时候找到源代码的位置的最简单方法是:</p><pre><code class="language-plaintext">user.method(:password=).source_location
=> #["/Users/ror365/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activemodel-4.2.0
/lib/active_model/secure_password.rb", 119]
</code></pre><p>返回的是确切的文件路径和相应的行号,然后你就可以按照这个去查找啦。 </p><h2>列出所有的 rake 任务</h2><p>如果你有一个较大的应用程序,你写了多个 rake 任务,然后 rails 本身也内置了一些rake任务,这时候你可能很难记住或者找到他们。最简单快速的方法来找到 rake 任务,运行:</p><p><code>bundle exec rake -T</code></p><p><code>-T</code>这个参数告诉 rake 列出在这个应用程序里面找到的所有 rake 任务,而且可以打印出这个任务的描述。 </p><h2>清理一些磁盘空间</h2><p>我的一个同事,他的 Macbook Pro 有120个G的硬盘。时间长了就占满了,然后他想找一些东西来删掉。我碰巧有次看到他在做这个事,然后就问他:“你清理你的rails logs了吗?”他说没有,然后他去看log,他本地的 development log 已经占用了2G的空间。看来从他买了这电脑就一直没清理过。</p><p>日复一日地开发一个应用程序,你可能没注意到你所做的每个请求都写在了开发日志(development log)里面。这可能并不是很大,但是它会快速地增加。不用手动去删除它,你可以运行这个专门为此设计的命令:</p><p><code>bundle exec rake log:clear</code></p><p>这会清理你的日志从而你可以获得一些硬盘空间。</p><h2>找出所有过时的gems</h2><p>开源世界,变化万千。如果你有一堆gems,你可能经常需要更新他们。然而运行<code>bundle update</code>命令有点危险,因为它会破坏一些东西。我建议在你的项目里先执行</p><p><code>bundle outdated</code></p><p>这样会列出所有需要更新的gems。然后你可以根据这个去bundle update某个单独的gem. </p><h2>总结</h2><p>一个人可以知道的小技巧是无限的,知道这些可能并不会让你成为一个更好的开发者,而且一个知道这些小提示和技巧的人也不见得比你厉害,虽然往往会有这样的错觉。这些小技巧可以使你更高效一点,没有别的。</p><p>(<a href="http://www.rubyonrails365.com/tips-and-tricks/">原文</a>翻译时有改动)</p><p> </p>2022-03-07T13:25:52Z任峻宏tag:renny.ren,2005:Article/42016-11-28T08:00:00+08:002022-03-07T13:25:45+08:00https://renny.ren/ch/articles/4实例方法与类方法<p>实例方法与类方法有什么区别?model里的方法在controller里面怎么调用?这些很基础的问题是必须要搞清楚的。</p><pre><code class="language-ruby">class Object
def abc
p "instance abc"
end
def self.abc
p "class abc"
end
end
obj = Object.new
obj.abc # instance abc
Object.abc # class abc
</code></pre><p>类方法只有类本身可以调用,实例方法是类的一个实例调用。</p><p>在ruby中,类方法是一种特殊的单例方法(singleton method)</p><pre><code class="language-ruby">class C # 定义类C
def a_method # 定义实例方法 a_method
puts "C#a_method"
end
def self.a_class_method # 定义类方法 a_class_method
puts "C#a_class_method"
end
end
class D < C # 定义类D,继承自类C
end
obj = D.new # 创建类D的实例对象obj
obj.a_method # 实例方法
D.a_class_method # 类方法
class Object
def eigenclass
class << self
self
end
end
end
class << obj
def a_singleton_method # 给obj定义单例方法 a_singleton_method
puts 'obj#a_singleton_method'
end
end
puts obj.a_singleton_method
</code></pre><h3>参考资料</h3><p>《ruby元编程》</p>2022-03-07T13:25:45Z任峻宏tag:renny.ren,2005:Article/112016-11-19T14:09:55+08:002022-03-07T13:25:36+08:00https://renny.ren/ch/articles/11各种报错解决集合<p>Q: ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)</p><p>A:</p><pre><code class="language-plaintext">sudo chown -R _mysql:mysql /usr/local/var/mysql
sudo mysql.server start
</code></pre><p> </p><p>Q: /usr/lib/ruby/2.3.0/rubygems/dependency.rb:319:in `to_specs': Could not find 'bundler' (>= 0.a) among 16 total gem(s) (Gem::LoadError)</p><p>A:</p><pre><code class="language-plaintext">gem uninstall bundler
gem install bundler # don't run with 'sudo'
</code></pre><p> </p><p>Q: ERROR 2003 (HY000) Can't connect to MySQL server on 'xxx.xxx' (61)</p><p>A:</p><p>1.Check if your mysql server is listening on a socket with netstat:</p><pre><code class="language-plaintext">netstat -tulpen
</code></pre><p>and search for 3306.</p><p>2.not or if only on localhost, check my.cnf(vi /etc/mysql/mysql.conf.d/mysqld.cnf) and search the bind-address line and change it to:</p><pre><code class="language-plaintext">bind-address = 0.0.0.0
</code></pre><p>3.restart mysql:</p><pre><code class="language-plaintext">/etc/init.d/mysql restart
</code></pre>2022-03-07T13:25:36Z任峻宏