VOTING POWER100.00%
DOWNVOTE POWER100.00%
RESOURCE CREDITS100.00%
REPUTATION PROGRESS26.99%
Net Worth
6.449USD
STEEM
0.035STEEM
SBD
13.416SBD
Effective Power
5.010SP
├── Own SP
0.126SP
└── Incoming DelegationsDeleg
+4.884SP
Detailed Balance
| STEEM | ||
| balance | 0.001STEEM | STEEM |
| market_balance | 0.000STEEM | STEEM |
| savings_balance | 0.000STEEM | STEEM |
| reward_steem_balance | 0.034STEEM | STEEM |
| STEEM POWER | ||
| Own SP | 0.126SP | SP |
| Delegated Out | 0.000SP | SP |
| Delegation In | 4.884SP | SP |
| Effective Power | 5.010SP | SP |
| Reward SP (pending) | 8.422SP | SP |
| SBD | ||
| sbd_balance | 0.000SBD | SBD |
| sbd_conversions | 0.000SBD | SBD |
| sbd_market_balance | 0.000SBD | SBD |
| savings_sbd_balance | 0.000SBD | SBD |
| reward_sbd_balance | 13.416SBD | SBD |
{
"balance": "0.001 STEEM",
"savings_balance": "0.000 STEEM",
"reward_steem_balance": "0.034 STEEM",
"vesting_shares": "204.281357 VESTS",
"delegated_vesting_shares": "0.000000 VESTS",
"received_vesting_shares": "7939.378449 VESTS",
"sbd_balance": "0.000 SBD",
"savings_sbd_balance": "0.000 SBD",
"reward_sbd_balance": "13.416 SBD",
"conversions": []
}Account Info
| name | davidpm |
| id | 800148 |
| rank | 521,963 |
| reputation | 138388664591 |
| created | 2018-03-03T02:46:06 |
| recovery_account | steem |
| proxy | None |
| post_count | 17 |
| comment_count | 0 |
| lifetime_vote_count | 0 |
| witnesses_voted_for | 0 |
| last_post | 2019-08-09T03:01:57 |
| last_root_post | 2019-08-09T03:01:57 |
| last_vote_time | 2018-07-24T18:25:51 |
| proxied_vsf_votes | 0, 0, 0, 0 |
| can_vote | 1 |
| voting_power | 0 |
| delayed_votes | 0 |
| balance | 0.001 STEEM |
| savings_balance | 0.000 STEEM |
| sbd_balance | 0.000 SBD |
| savings_sbd_balance | 0.000 SBD |
| vesting_shares | 204.281357 VESTS |
| delegated_vesting_shares | 0.000000 VESTS |
| received_vesting_shares | 7939.378449 VESTS |
| reward_vesting_balance | 17190.776628 VESTS |
| vesting_balance | 0.000 STEEM |
| vesting_withdraw_rate | 0.000000 VESTS |
| next_vesting_withdrawal | 1969-12-31T23:59:59 |
| withdrawn | 0 |
| to_withdraw | 0 |
| withdraw_routes | 0 |
| savings_withdraw_requests | 0 |
| last_account_recovery | 1970-01-01T00:00:00 |
| reset_account | null |
| last_owner_update | 1970-01-01T00:00:00 |
| last_account_update | 2018-03-05T01:37:21 |
| mined | No |
| sbd_seconds | 0 |
| sbd_last_interest_payment | 1970-01-01T00:00:00 |
| savings_sbd_last_interest_payment | 1970-01-01T00:00:00 |
{
"active": {
"account_auths": [],
"key_auths": [
[
"STM8kxLaTeG6HXYMuVtjr6R1yFcDpob1H4R7HuWj3CpQDKB2rhRnZ",
1
]
],
"weight_threshold": 1
},
"balance": "0.001 STEEM",
"can_vote": true,
"comment_count": 0,
"created": "2018-03-03T02:46:06",
"curation_rewards": 1,
"delegated_vesting_shares": "0.000000 VESTS",
"downvote_manabar": {
"current_mana": 2035914951,
"last_update_time": 1779059865
},
"guest_bloggers": [],
"id": 800148,
"json_metadata": "{\"profile\":{\"profile_image\":\"https://avatars3.githubusercontent.com/u/6234571?s=400&u=81563e5c8010d6ffdb26be7d77dad7c555c6066f&v=4\",\"website\":\"http://davidpm.io\",\"cover_image\":\"https://pbs.twimg.com/profile_banners/948220550/1503333176/1500x500\",\"about\":\"quick posts about programming\"}}",
"last_account_recovery": "1970-01-01T00:00:00",
"last_account_update": "2018-03-05T01:37:21",
"last_owner_update": "1970-01-01T00:00:00",
"last_post": "2019-08-09T03:01:57",
"last_root_post": "2019-08-09T03:01:57",
"last_vote_time": "2018-07-24T18:25:51",
"lifetime_vote_count": 0,
"market_history": [],
"memo_key": "STM6P3LvVZjSRnWetfbWcXeEcF6smw5xpW87s43oEd21Lea53ovPb",
"mined": false,
"name": "davidpm",
"next_vesting_withdrawal": "1969-12-31T23:59:59",
"other_history": [],
"owner": {
"account_auths": [],
"key_auths": [
[
"STM5DEkfgR4JvnCFgdPyqbyseWJS4ePqHj9VnBbLVadAJuCQErBpy",
1
]
],
"weight_threshold": 1
},
"pending_claimed_accounts": 0,
"post_bandwidth": 0,
"post_count": 17,
"post_history": [],
"posting": {
"account_auths": [],
"key_auths": [
[
"STM5D1ZCtab5A5DzioThZSGKCASa4QuYXsegaFYr4E2edcKa85ciP",
1
]
],
"weight_threshold": 1
},
"posting_json_metadata": "{\"profile\":{\"profile_image\":\"https://avatars3.githubusercontent.com/u/6234571?s=400&u=81563e5c8010d6ffdb26be7d77dad7c555c6066f&v=4\",\"website\":\"http://davidpm.io\",\"cover_image\":\"https://pbs.twimg.com/profile_banners/948220550/1503333176/1500x500\",\"about\":\"quick posts about programming\"}}",
"posting_rewards": 10904,
"proxied_vsf_votes": [
0,
0,
0,
0
],
"proxy": "",
"received_vesting_shares": "7939.378449 VESTS",
"recovery_account": "steem",
"reputation": "138388664591",
"reset_account": "null",
"reward_sbd_balance": "13.416 SBD",
"reward_steem_balance": "0.034 STEEM",
"reward_vesting_balance": "17190.776628 VESTS",
"reward_vesting_steem": "8.422 STEEM",
"savings_balance": "0.000 STEEM",
"savings_sbd_balance": "0.000 SBD",
"savings_sbd_last_interest_payment": "1970-01-01T00:00:00",
"savings_sbd_seconds": "0",
"savings_sbd_seconds_last_update": "1970-01-01T00:00:00",
"savings_withdraw_requests": 0,
"sbd_balance": "0.000 SBD",
"sbd_last_interest_payment": "1970-01-01T00:00:00",
"sbd_seconds": "0",
"sbd_seconds_last_update": "1970-01-01T00:00:00",
"tags_usage": [],
"to_withdraw": 0,
"transfer_history": [],
"vesting_balance": "0.000 STEEM",
"vesting_shares": "204.281357 VESTS",
"vesting_withdraw_rate": "0.000000 VESTS",
"vote_history": [],
"voting_manabar": {
"current_mana": "8143659806",
"last_update_time": 1779059865
},
"voting_power": 0,
"withdraw_routes": 0,
"withdrawn": 0,
"witness_votes": [],
"witnesses_voted_for": 0,
"rank": 521963
}Withdraw Routes
| Incoming | Outgoing |
|---|---|
Empty | Empty |
{
"incoming": [],
"outgoing": []
}From Date
To Date
2026/05/17 23:17:45
2026/05/17 23:17:45
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 7939.378449 VESTS |
| Transaction Info | Block #106142304/Trx 4a92b6b4c77a5f74d992f6df28d272b2cd6808ec |
View Raw JSON Data
{
"block": 106142304,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "7939.378449 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-05-17T23:17:45",
"trx_id": "4a92b6b4c77a5f74d992f6df28d272b2cd6808ec",
"trx_in_block": 1,
"virtual_op": 0
}2026/05/11 23:58:51
2026/05/11 23:58:51
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 5227.168044 VESTS |
| Transaction Info | Block #105971089/Trx 6b9abc42fbeccecffadae72eeaee6605ca847fe5 |
View Raw JSON Data
{
"block": 105971089,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "5227.168044 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-05-11T23:58:51",
"trx_id": "6b9abc42fbeccecffadae72eeaee6605ca847fe5",
"trx_in_block": 1,
"virtual_op": 0
}2026/04/25 22:40:18
2026/04/25 22:40:18
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 7951.894205 VESTS |
| Transaction Info | Block #105509986/Trx 6fffc6d3538dce58a71d50c4e87ef26add073536 |
View Raw JSON Data
{
"block": 105509986,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "7951.894205 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-04-25T22:40:18",
"trx_id": "6fffc6d3538dce58a71d50c4e87ef26add073536",
"trx_in_block": 0,
"virtual_op": 0
}2026/01/23 05:13:48
2026/01/23 05:13:48
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 5268.714863 VESTS |
| Transaction Info | Block #102848729/Trx f7d1a0a8405940875b1ad8fb547879a54f0fc156 |
View Raw JSON Data
{
"block": 102848729,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "5268.714863 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-01-23T05:13:48",
"trx_id": "f7d1a0a8405940875b1ad8fb547879a54f0fc156",
"trx_in_block": 1,
"virtual_op": 0
}2024/12/17 00:33:45
2024/12/17 00:33:45
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 5432.934060 VESTS |
| Transaction Info | Block #91295154/Trx 62ce356d3c56462f7e159dfcb5e5e0c28bee9ffb |
View Raw JSON Data
{
"block": 91295154,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "5432.934060 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2024-12-17T00:33:45",
"trx_id": "62ce356d3c56462f7e159dfcb5e5e0c28bee9ffb",
"trx_in_block": 0,
"virtual_op": 0
}2023/11/13 16:17:24
2023/11/13 16:17:24
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 5602.067592 VESTS |
| Transaction Info | Block #79849384/Trx 7392d75e78f0004367daa5e2f7851c125fda2eee |
View Raw JSON Data
{
"block": 79849384,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "5602.067592 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2023-11-13T16:17:24",
"trx_id": "7392d75e78f0004367daa5e2f7851c125fda2eee",
"trx_in_block": 6,
"virtual_op": 0
}2023/09/21 20:42:18
2023/09/21 20:42:18
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 8539.346378 VESTS |
| Transaction Info | Block #78346484/Trx 9d116f69c7516aab7c61a3d564e452c28f83d82f |
View Raw JSON Data
{
"block": 78346484,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "8539.346378 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2023-09-21T20:42:18",
"trx_id": "9d116f69c7516aab7c61a3d564e452c28f83d82f",
"trx_in_block": 1,
"virtual_op": 0
}2022/11/03 10:38:36
2022/11/03 10:38:36
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 8761.027816 VESTS |
| Transaction Info | Block #69111996/Trx 2bdcc6415336babe8ee7f3c101b5c557a7a10070 |
View Raw JSON Data
{
"block": 69111996,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "8761.027816 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2022-11-03T10:38:36",
"trx_id": "2bdcc6415336babe8ee7f3c101b5c557a7a10070",
"trx_in_block": 2,
"virtual_op": 0
}2022/01/17 09:59:48
2022/01/17 09:59:48
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 8981.561047 VESTS |
| Transaction Info | Block #60808268/Trx d55a51ae09560ce2d6fb79c21c43dcc8ffb262cd |
View Raw JSON Data
{
"block": 60808268,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "8981.561047 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2022-01-17T09:59:48",
"trx_id": "d55a51ae09560ce2d6fb79c21c43dcc8ffb262cd",
"trx_in_block": 19,
"virtual_op": 0
}2021/06/13 23:57:18
2021/06/13 23:57:18
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 9165.329705 VESTS |
| Transaction Info | Block #54606703/Trx c491496d4f459e144a687a30727f5f6c44d80532 |
View Raw JSON Data
{
"block": 54606703,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "9165.329705 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2021-06-13T23:57:18",
"trx_id": "c491496d4f459e144a687a30727f5f6c44d80532",
"trx_in_block": 0,
"virtual_op": 0
}2020/12/11 10:17:39
2020/12/11 10:17:39
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 9352.751679 VESTS |
| Transaction Info | Block #49354199/Trx 39495d1fa3fa19b2d30085d5dcaafe139b25c46e |
View Raw JSON Data
{
"block": 49354199,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "9352.751679 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-12-11T10:17:39",
"trx_id": "39495d1fa3fa19b2d30085d5dcaafe139b25c46e",
"trx_in_block": 4,
"virtual_op": 0
}2020/12/06 03:54:42
2020/12/06 03:54:42
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 1912.543513 VESTS |
| Transaction Info | Block #49205761/Trx 637780728a1a52138df2e7404c135c6d4a3f34a6 |
View Raw JSON Data
{
"block": 49205761,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "1912.543513 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-12-06T03:54:42",
"trx_id": "637780728a1a52138df2e7404c135c6d4a3f34a6",
"trx_in_block": 2,
"virtual_op": 0
}2020/12/05 11:52:03
2020/12/05 11:52:03
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 9359.118318 VESTS |
| Transaction Info | Block #49186871/Trx ed36edc5d88474a63d3b9eb3b3824be877b5ce52 |
View Raw JSON Data
{
"block": 49186871,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "9359.118318 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-12-05T11:52:03",
"trx_id": "ed36edc5d88474a63d3b9eb3b3824be877b5ce52",
"trx_in_block": 5,
"virtual_op": 0
}2020/11/02 13:51:33
2020/11/02 13:51:33
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 1920.017158 VESTS |
| Transaction Info | Block #48255710/Trx a33ccca58d377c2e75b33825f9ca2e22d976af67 |
View Raw JSON Data
{
"block": 48255710,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "1920.017158 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-11-02T13:51:33",
"trx_id": "a33ccca58d377c2e75b33825f9ca2e22d976af67",
"trx_in_block": 2,
"virtual_op": 0
}2020/05/09 04:51:15
2020/05/09 04:51:15
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 9561.764892 VESTS |
| Transaction Info | Block #43216000/Trx ec8bbff692dd16bd04c90eea9ea63b9764cbc7f4 |
View Raw JSON Data
{
"block": 43216000,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "9561.764892 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-05-09T04:51:15",
"trx_id": "ec8bbff692dd16bd04c90eea9ea63b9764cbc7f4",
"trx_in_block": 6,
"virtual_op": 0
}2020/05/08 08:19:36
2020/05/08 08:19:36
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 1953.311140 VESTS |
| Transaction Info | Block #43191943/Trx 7ce1c8ad8ed7594fd832029ab485af3e3c7a441b |
View Raw JSON Data
{
"block": 43191943,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "1953.311140 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-05-08T08:19:36",
"trx_id": "7ce1c8ad8ed7594fd832029ab485af3e3c7a441b",
"trx_in_block": 2,
"virtual_op": 0
}2020/03/05 03:34:30
2020/03/05 03:34:30
| author | steemitboard |
| body | Congratulations @davidpm! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@davidpm/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table> <sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@davidpm) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=davidpm)_</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge"><img src="https://steemitimages.com/64x128/https://cdn.steemitimages.com/DQmTugCUsoXX762vg1CuHRrpnPbfnjPogp8iCGv7F2kSVuj/image.png"></a></td><td><a href="https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge">Use your witness votes and get the Community Badge</a></td></tr></table> ###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes! |
| json metadata | {"image":["https://steemitboard.com/img/notify.png"]} |
| parent author | davidpm |
| parent permlink | disabling-dangerous-redis-commands-in-production-redux |
| permlink | steemitboard-notify-davidpm-20200305t033430000z |
| title | |
| Transaction Info | Block #41375143/Trx 685cc2a172b99fd7ed31475ac98750b0a774cdf2 |
View Raw JSON Data
{
"block": 41375143,
"op": [
"comment",
{
"author": "steemitboard",
"body": "Congratulations @davidpm! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@davidpm/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table>\n\n<sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@davidpm) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=davidpm)_</sub>\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge\"><img src=\"https://steemitimages.com/64x128/https://cdn.steemitimages.com/DQmTugCUsoXX762vg1CuHRrpnPbfnjPogp8iCGv7F2kSVuj/image.png\"></a></td><td><a href=\"https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge\">Use your witness votes and get the Community Badge</a></td></tr></table>\n\n###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes!",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
"parent_author": "davidpm",
"parent_permlink": "disabling-dangerous-redis-commands-in-production-redux",
"permlink": "steemitboard-notify-davidpm-20200305t033430000z",
"title": ""
}
],
"op_in_trx": 0,
"timestamp": "2020-03-05T03:34:30",
"trx_id": "685cc2a172b99fd7ed31475ac98750b0a774cdf2",
"trx_in_block": 10,
"virtual_op": 0
}2019/11/08 04:59:03
2019/11/08 04:59:03
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 9664.078231 VESTS |
| Transaction Info | Block #37985985/Trx 0870e92a6fc8a1380b34138978c97a9dead9f005 |
View Raw JSON Data
{
"block": 37985985,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "9664.078231 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2019-11-08T04:59:03",
"trx_id": "0870e92a6fc8a1380b34138978c97a9dead9f005",
"trx_in_block": 7,
"virtual_op": 0
}davidpmpublished a new post: disabling-dangerous-redis-commands-in-production-redux2019/08/18 19:38:12
davidpmpublished a new post: disabling-dangerous-redis-commands-in-production-redux
2019/08/18 19:38:12
| author | davidpm |
| body | @@ -2742,12 +2742,20 @@ ier -ya. +for ya (%E1%B5%94%E1%B4%A5%E1%B5%94) %0A%0ATh |
| json metadata | {"tags":["programming","ruby","honeybadger","redis"],"links":["https://www.honeybadger.io/leveling-up/safeguarding-redis/","weedmaps.com","https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b","https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e"],"app":"steemit/0.1","format":"markdown","image":["https://dwglogo.com/wp-content/uploads/2017/12/1100px_Redis_Logo_01.png"]} |
| parent author | |
| parent permlink | programming |
| permlink | disabling-dangerous-redis-commands-in-production-redux |
| title | Disabling dangerous redis commands in Production redux |
| Transaction Info | Block #35668553/Trx 1709f5657c42d1ad9145fc39979759c4022f50a3 |
View Raw JSON Data
{
"block": 35668553,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -2742,12 +2742,20 @@\n ier \n-ya. \n+for ya (%E1%B5%94%E1%B4%A5%E1%B5%94)\n %0A%0ATh\n",
"json_metadata": "{\"tags\":[\"programming\",\"ruby\",\"honeybadger\",\"redis\"],\"links\":[\"https://www.honeybadger.io/leveling-up/safeguarding-redis/\",\"weedmaps.com\",\"https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b\",\"https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\",\"image\":[\"https://dwglogo.com/wp-content/uploads/2017/12/1100px_Redis_Logo_01.png\"]}",
"parent_author": "",
"parent_permlink": "programming",
"permlink": "disabling-dangerous-redis-commands-in-production-redux",
"title": "Disabling dangerous redis commands in Production redux"
}
],
"op_in_trx": 0,
"timestamp": "2019-08-18T19:38:12",
"trx_id": "1709f5657c42d1ad9145fc39979759c4022f50a3",
"trx_in_block": 2,
"virtual_op": 0
}2019/08/09 04:18:03
2019/08/09 04:18:03
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 29563.286980 VESTS |
| Transaction Info | Block #35391454/Trx d3042cdd0590d2d4b5307d855aee92e1cc419493 |
View Raw JSON Data
{
"block": 35391454,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "29563.286980 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2019-08-09T04:18:03",
"trx_id": "d3042cdd0590d2d4b5307d855aee92e1cc419493",
"trx_in_block": 6,
"virtual_op": 0
}resteemyousent 0.001 STEEM to @davidpm- "Hi! I re-blog posts to 7000+ followers if you send only 0.03 SBD/Steem with post link in memo || comments disabled. Thanx ♥"2019/08/09 03:12:21
resteemyousent 0.001 STEEM to @davidpm- "Hi! I re-blog posts to 7000+ followers if you send only 0.03 SBD/Steem with post link in memo || comments disabled. Thanx ♥"
2019/08/09 03:12:21
| amount | 0.001 STEEM |
| from | resteemyou |
| memo | Hi! I re-blog posts to 7000+ followers if you send only 0.03 SBD/Steem with post link in memo || comments disabled. Thanx ♥ |
| to | davidpm |
| Transaction Info | Block #35390144/Trx 0d36a0f746974927b13dff844eae370e564f49c4 |
View Raw JSON Data
{
"block": 35390144,
"op": [
"transfer",
{
"amount": "0.001 STEEM",
"from": "resteemyou",
"memo": "Hi! I re-blog posts to 7000+ followers if you send only 0.03 SBD/Steem with post link in memo || comments disabled. Thanx ♥",
"to": "davidpm"
}
],
"op_in_trx": 0,
"timestamp": "2019-08-09T03:12:21",
"trx_id": "0d36a0f746974927b13dff844eae370e564f49c4",
"trx_in_block": 16,
"virtual_op": 0
}davidpmpublished a new post: disabling-dangerous-redis-commands-in-production-redux2019/08/09 03:03:48
davidpmpublished a new post: disabling-dangerous-redis-commands-in-production-redux
2019/08/09 03:03:48
| author | davidpm |
| body | @@ -1,8 +1,90 @@ +!%5Bredis%5D(https://dwglogo.com/wp-content/uploads/2017/12/1100px_Redis_Logo_01.png)%0A I recent |
| json metadata | {"tags":["programming","ruby","honeybadger","redis"],"links":["https://www.honeybadger.io/leveling-up/safeguarding-redis/","weedmaps.com","https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b","https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e"],"app":"steemit/0.1","format":"markdown","image":["https://dwglogo.com/wp-content/uploads/2017/12/1100px_Redis_Logo_01.png"]} |
| parent author | |
| parent permlink | programming |
| permlink | disabling-dangerous-redis-commands-in-production-redux |
| title | Disabling dangerous redis commands in Production redux |
| Transaction Info | Block #35389973/Trx 94f424937915d70b90e20276095249ab3f25c8d6 |
View Raw JSON Data
{
"block": 35389973,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -1,8 +1,90 @@\n+!%5Bredis%5D(https://dwglogo.com/wp-content/uploads/2017/12/1100px_Redis_Logo_01.png)%0A\n I recent\n",
"json_metadata": "{\"tags\":[\"programming\",\"ruby\",\"honeybadger\",\"redis\"],\"links\":[\"https://www.honeybadger.io/leveling-up/safeguarding-redis/\",\"weedmaps.com\",\"https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b\",\"https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\",\"image\":[\"https://dwglogo.com/wp-content/uploads/2017/12/1100px_Redis_Logo_01.png\"]}",
"parent_author": "",
"parent_permlink": "programming",
"permlink": "disabling-dangerous-redis-commands-in-production-redux",
"title": "Disabling dangerous redis commands in Production redux"
}
],
"op_in_trx": 0,
"timestamp": "2019-08-09T03:03:48",
"trx_id": "94f424937915d70b90e20276095249ab3f25c8d6",
"trx_in_block": 9,
"virtual_op": 0
}cronupvoted (0.02%) @davidpm / disabling-dangerous-redis-commands-in-production-redux2019/08/09 03:02:03
cronupvoted (0.02%) @davidpm / disabling-dangerous-redis-commands-in-production-redux
2019/08/09 03:02:03
| author | davidpm |
| permlink | disabling-dangerous-redis-commands-in-production-redux |
| voter | cron |
| weight | 2 (0.02%) |
| Transaction Info | Block #35389938/Trx 35af34476e6a92f0fe17d03bb9fe461a8facde18 |
View Raw JSON Data
{
"block": 35389938,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "disabling-dangerous-redis-commands-in-production-redux",
"voter": "cron",
"weight": 2
}
],
"op_in_trx": 0,
"timestamp": "2019-08-09T03:02:03",
"trx_id": "35af34476e6a92f0fe17d03bb9fe461a8facde18",
"trx_in_block": 27,
"virtual_op": 0
}davidpmpublished a new post: disabling-dangerous-redis-commands-in-production-redux2019/08/09 03:01:57
davidpmpublished a new post: disabling-dangerous-redis-commands-in-production-redux
2019/08/09 03:01:57
| author | davidpm |
| body | I recently read a great article from [Honeybadger](https://www.honeybadger.io/leveling-up/safeguarding-redis/) that muses on what could happen to a production environment if a fat fingered developer did something like: `Redis.current.flushdb`. This scared me enough that I decided we needed something like this at [Weedmaps](weedmaps.com). The only problem is that we want to keep these commands available in lower environments. We also do not allow new code into our codebases without tests. We make exceptions but they are rare, since we use our tests as documentation and a sanity check. Here is my adaptation to this [gist]https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b) which was provided in the Honeybadger article above: ``` ############### config/initializers/redis.rb ######################### require 'redis' require 'redis_dangerous_commands' class Redis prepend DangerousCommands end ############### lib/redis_dangerous_commands.rb ######################### module DangerousCommands class ProhibitedCommandError < StandardError; end def self.prepended(base) return unless Rails.env.production? base.send(:define_method, :flushdb) do raise ProhibitedCommandError, error_message('EMPTY THE ENTIRE DATABASE') end base.send(:define_method, :flushall) do raise ProhibitedCommandError, error_message('FLUSH ALL DATABASES') end end def error_message(inner_message) "DANGEROUS destructive operation. If you really want to #{inner_message} do it from `redis-cli`." end end ############### spec/lib/redis_dangerous_commands_spec.rb ######################### describe 'DangerousCommands' do context 'Production' do let(:dumb) { Dummy.new } before do allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) class Dummy prepend DangerousCommands end end it 'raises ProhibitedCommandError' do expect{ dumb.flushdb }.to raise_error DangerousCommands::ProhibitedCommandError end it 'raises ProhibitedCommandError' do expect{ dumb.flushall }.to raise_error DangerousCommands::ProhibitedCommandError end end context 'Non-production' do let(:dumb) { DifferentDummy.new } before do class DifferentDummy prepend DangerousCommands end end it 'does not respond to flushdb' do expect(dumb).not_to respond_to(:flushdb) end it 'does not respond to flushall' do expect(dumb).not_to respond_to(:flushall) end end end ``` And a link to a [gist](https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e) if that's easier ya. The basic idea is that we needed a way to a) optionally include the disabled override methods based on the `Rails.env` and b) test this. The trick was figuring out how to stub `Rails.env` in an initializer spec. Turns out it isn't possible since the environment loads those initializers as it boots up to run your specs. By time you stub out your `Rails.env` it is too late. This approach only defines those methods in the prepended hook provided by Ruby. Since, this module lives in `lib` we can stub our `Rails.env` and prepend our module into some dummy classes. If the dummy class receives those methods in when we stub out a production env then we know we are in business. ~(˘▾˘~) |
| json metadata | {"tags":["programming","ruby","honeybadger","redis"],"links":["https://www.honeybadger.io/leveling-up/safeguarding-redis/","weedmaps.com","https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b","https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | programming |
| permlink | disabling-dangerous-redis-commands-in-production-redux |
| title | Disabling dangerous redis commands in Production redux |
| Transaction Info | Block #35389936/Trx 4b2a93bd0eb760c2b5e41491dc7b1a2b1db28fd0 |
View Raw JSON Data
{
"block": 35389936,
"op": [
"comment",
{
"author": "davidpm",
"body": "I recently read a great article from [Honeybadger](https://www.honeybadger.io/leveling-up/safeguarding-redis/) that muses on what could happen to a production environment if a fat fingered developer did something like:\n`Redis.current.flushdb`.\n\nThis scared me enough that I decided we needed something like this at [Weedmaps](weedmaps.com). The only problem is that we want to keep these commands available in lower environments. We also do not allow new code into our codebases without tests. We make exceptions but they are rare, since we use our tests as documentation and a sanity check.\n\nHere is my adaptation to this [gist]https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b) which was provided in the Honeybadger article above:\n```\n############### config/initializers/redis.rb #########################\nrequire 'redis'\nrequire 'redis_dangerous_commands'\n\nclass Redis\n prepend DangerousCommands\nend\n\n############### lib/redis_dangerous_commands.rb #########################\nmodule DangerousCommands\n class ProhibitedCommandError < StandardError; end\n\n def self.prepended(base)\n return unless Rails.env.production?\n\n base.send(:define_method, :flushdb) do\n raise ProhibitedCommandError, error_message('EMPTY THE ENTIRE DATABASE')\n end\n\n base.send(:define_method, :flushall) do\n raise ProhibitedCommandError, error_message('FLUSH ALL DATABASES')\n end\n end\n\n def error_message(inner_message)\n \"DANGEROUS destructive operation. If you really want to #{inner_message} do it from `redis-cli`.\"\n end\nend\n\n############### spec/lib/redis_dangerous_commands_spec.rb #########################\ndescribe 'DangerousCommands' do\n context 'Production' do\n let(:dumb) { Dummy.new }\n\n before do\n allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))\n class Dummy\n prepend DangerousCommands\n end\n end\n\n it 'raises ProhibitedCommandError' do\n expect{ dumb.flushdb }.to raise_error DangerousCommands::ProhibitedCommandError\n end\n\n it 'raises ProhibitedCommandError' do\n expect{ dumb.flushall }.to raise_error DangerousCommands::ProhibitedCommandError\n end\n end\n\n context 'Non-production' do\n let(:dumb) { DifferentDummy.new }\n\n before do\n class DifferentDummy\n prepend DangerousCommands\n end\n end\n\n it 'does not respond to flushdb' do\n expect(dumb).not_to respond_to(:flushdb)\n end\n\n it 'does not respond to flushall' do\n expect(dumb).not_to respond_to(:flushall)\n end\n end\nend\n```\n\nAnd a link to a [gist](https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e) if that's easier ya. \n\nThe basic idea is that we needed a way to a) optionally include the disabled override methods based on the `Rails.env`\nand b) test this. The trick was figuring out how to stub `Rails.env` in an initializer spec. Turns out it isn't possible since the environment loads those initializers as it boots up to run your specs. By time you stub out your `Rails.env` it is too late.\n\nThis approach only defines those methods in the prepended hook provided by Ruby. Since, this module lives in `lib`\nwe can stub our `Rails.env` and prepend our module into some dummy classes. If the dummy class receives those methods in when we stub out a production env then we know we are in business.\n\n~(˘▾˘~)",
"json_metadata": "{\"tags\":[\"programming\",\"ruby\",\"honeybadger\",\"redis\"],\"links\":[\"https://www.honeybadger.io/leveling-up/safeguarding-redis/\",\"weedmaps.com\",\"https://gist.github.com/joshuap/c1ff2657c150df6fb1257398b1d2716b\",\"https://gist.github.com/david-pm/748846ba853d2b48834f0cca6fce2b6e\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "programming",
"permlink": "disabling-dangerous-redis-commands-in-production-redux",
"title": "Disabling dangerous redis commands in Production redux"
}
],
"op_in_trx": 0,
"timestamp": "2019-08-09T03:01:57",
"trx_id": "4b2a93bd0eb760c2b5e41491dc7b1a2b1db28fd0",
"trx_in_block": 14,
"virtual_op": 0
}2019/03/03 04:21:03
2019/03/03 04:21:03
| author | steemitboard |
| body | Congratulations @davidpm! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@davidpm/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table> <sub>_[Click here to view your Board](https://steemitboard.com/@davidpm)_</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/carnival/@steemitboard/carnival-2019"><img src="https://steemitimages.com/64x128/http://i.cubeupload.com/rltzHT.png"></a></td><td><a href="https://steemit.com/carnival/@steemitboard/carnival-2019">Carnival Challenge - Collect badge and win 5 STEEM</a></td></tr></table> ###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) and get one more award and increased upvotes! |
| json metadata | {"image":["https://steemitboard.com/img/notify.png"]} |
| parent author | davidpm |
| parent permlink | heuristics-on-the-practice-of-programming |
| permlink | steemitboard-notify-davidpm-20190303t042102000z |
| title | |
| Transaction Info | Block #30819645/Trx 739326d8102d8f2f17661db4929f57d0a8fa0350 |
View Raw JSON Data
{
"block": 30819645,
"op": [
"comment",
{
"author": "steemitboard",
"body": "Congratulations @davidpm! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@davidpm/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table>\n\n<sub>_[Click here to view your Board](https://steemitboard.com/@davidpm)_</sub>\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/carnival/@steemitboard/carnival-2019\"><img src=\"https://steemitimages.com/64x128/http://i.cubeupload.com/rltzHT.png\"></a></td><td><a href=\"https://steemit.com/carnival/@steemitboard/carnival-2019\">Carnival Challenge - Collect badge and win 5 STEEM</a></td></tr></table>\n\n###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) and get one more award and increased upvotes!",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
"parent_author": "davidpm",
"parent_permlink": "heuristics-on-the-practice-of-programming",
"permlink": "steemitboard-notify-davidpm-20190303t042102000z",
"title": ""
}
],
"op_in_trx": 0,
"timestamp": "2019-03-03T04:21:03",
"trx_id": "739326d8102d8f2f17661db4929f57d0a8fa0350",
"trx_in_block": 3,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor2019/02/03 07:48:00
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor
2019/02/03 07:48:00
| author | davidpm |
| body | @@ -2708,20 +2708,16 @@ u could -all install |
| json metadata | {"tags":["ruby","gem","thor","cli","matrix"],"format":"markdown","links":["https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two","https://bundler.io/","https://git-scm.com/","http://rspec.info/","https://github.com/ruby/rake","https://github.com/erikhuda/thor","https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58","https://en.wikipedia.org/wiki/Ouroboros"],"image":["https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif","https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png"],"app":"steemit/0.1"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| title | Create a command-line gem from scratch with Thor |
| Transaction Info | Block #30018006/Trx 33b44980ba5712c327431816c24720248aa0af70 |
View Raw JSON Data
{
"block": 30018006,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -2708,20 +2708,16 @@\n u could \n-all \n install \n",
"json_metadata": "{\"tags\":[\"ruby\",\"gem\",\"thor\",\"cli\",\"matrix\"],\"format\":\"markdown\",\"links\":[\"https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two\",\"https://bundler.io/\",\"https://git-scm.com/\",\"http://rspec.info/\",\"https://github.com/ruby/rake\",\"https://github.com/erikhuda/thor\",\"https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58\",\"https://en.wikipedia.org/wiki/Ouroboros\"],\"image\":[\"https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif\",\"https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png\"],\"app\":\"steemit/0.1\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"title": "Create a command-line gem from scratch with Thor"
}
],
"op_in_trx": 0,
"timestamp": "2019-02-03T07:48:00",
"trx_id": "33b44980ba5712c327431816c24720248aa0af70",
"trx_in_block": 11,
"virtual_op": 0
}johnteeeupvoted (100.00%) @davidpm / heuristics-on-the-practice-of-programming2018/12/27 13:19:09
johnteeeupvoted (100.00%) @davidpm / heuristics-on-the-practice-of-programming
2018/12/27 13:19:09
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| voter | johnteee |
| weight | 10000 (100.00%) |
| Transaction Info | Block #28931242/Trx bf68582bdc4744a81793619cc57d0547de12a537 |
View Raw JSON Data
{
"block": 28931242,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"voter": "johnteee",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-12-27T13:19:09",
"trx_id": "bf68582bdc4744a81793619cc57d0547de12a537",
"trx_in_block": 51,
"virtual_op": 0
}davidpmpublished a new post: heuristics-on-the-practice-of-programming2018/12/16 23:17:45
davidpmpublished a new post: heuristics-on-the-practice-of-programming
2018/12/16 23:17:45
| author | davidpm |
| body | @@ -908,16 +908,127 @@ e bug.%0A%0A +* %5BFunctions should do one thing%5D(https://github.com/uohzxela/clean-code-ruby#functions-should-do-one-thing).%0A%0A * Everyt |
| json metadata | {"tags":["technology","programming"],"app":"steemit/0.1","format":"markdown","image":["https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%20Shot%202018-09-04%20at%202.46.01%20PM.png"],"links":["https://github.com/uohzxela/clean-code-ruby#functions-should-do-one-thing"]} |
| parent author | |
| parent permlink | technology |
| permlink | heuristics-on-the-practice-of-programming |
| title | Heuristics on the Practice of Programming |
| Transaction Info | Block #28626596/Trx fecbcd0896c8bd02dfd4954e6e550ab8bcd58ae3 |
View Raw JSON Data
{
"block": 28626596,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -908,16 +908,127 @@\n e bug.%0A%0A\n+* %5BFunctions should do one thing%5D(https://github.com/uohzxela/clean-code-ruby#functions-should-do-one-thing).%0A%0A\n * Everyt\n",
"json_metadata": "{\"tags\":[\"technology\",\"programming\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\",\"image\":[\"https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%20Shot%202018-09-04%20at%202.46.01%20PM.png\"],\"links\":[\"https://github.com/uohzxela/clean-code-ruby#functions-should-do-one-thing\"]}",
"parent_author": "",
"parent_permlink": "technology",
"permlink": "heuristics-on-the-practice-of-programming",
"title": "Heuristics on the Practice of Programming"
}
],
"op_in_trx": 0,
"timestamp": "2018-12-16T23:17:45",
"trx_id": "fecbcd0896c8bd02dfd4954e6e550ab8bcd58ae3",
"trx_in_block": 5,
"virtual_op": 0
}2018/12/04 22:40:06
2018/12/04 22:40:06
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 9860.997057 VESTS |
| Transaction Info | Block #28280460/Trx 82be5210f09e2d4db69000ef14f1180e3c14a810 |
View Raw JSON Data
{
"block": 28280460,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "9860.997057 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-12-04T22:40:06",
"trx_id": "82be5210f09e2d4db69000ef14f1180e3c14a810",
"trx_in_block": 2,
"virtual_op": 0
}2018/11/26 17:11:36
2018/11/26 17:11:36
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 30005.480771 VESTS |
| Transaction Info | Block #28043573/Trx 8126a36cd7bd7070f952354e7ebf9883cfd7da69 |
View Raw JSON Data
{
"block": 28043573,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "30005.480771 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-11-26T17:11:36",
"trx_id": "8126a36cd7bd7070f952354e7ebf9883cfd7da69",
"trx_in_block": 38,
"virtual_op": 0
}davidpmreceived 0.033 STEEM, 0.042 SP author reward for @davidpm / heuristics-on-the-practice-of-programming2018/09/11 21:30:27
davidpmreceived 0.033 STEEM, 0.042 SP author reward for @davidpm / heuristics-on-the-practice-of-programming
2018/09/11 21:30:27
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| sbd payout | 0.000 SBD |
| steem payout | 0.033 STEEM |
| vesting payout | 68.755111 VESTS |
| Transaction Info | Block #25877154/Virtual Operation #4 |
View Raw JSON Data
{
"block": 25877154,
"op": [
"author_reward",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"sbd_payout": "0.000 SBD",
"steem_payout": "0.033 STEEM",
"vesting_payout": "68.755111 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-09-11T21:30:27",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 4
}msiccupvoted (100.00%) @davidpm / heuristics-on-the-practice-of-programming2018/09/05 03:23:15
msiccupvoted (100.00%) @davidpm / heuristics-on-the-practice-of-programming
2018/09/05 03:23:15
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| voter | msicc |
| weight | 10000 (100.00%) |
| Transaction Info | Block #25682677/Trx e23140a46a4f66e1173c111bedd0365869a1ce8e |
View Raw JSON Data
{
"block": 25682677,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"voter": "msicc",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-09-05T03:23:15",
"trx_id": "e23140a46a4f66e1173c111bedd0365869a1ce8e",
"trx_in_block": 31,
"virtual_op": 0
}hr1upvoted (0.02%) @davidpm / heuristics-on-the-practice-of-programming2018/09/04 22:00:33
hr1upvoted (0.02%) @davidpm / heuristics-on-the-practice-of-programming
2018/09/04 22:00:33
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| voter | hr1 |
| weight | 2 (0.02%) |
| Transaction Info | Block #25676234/Trx 3cc03ede6703a8d4f9977283a0b13dca5d0c2414 |
View Raw JSON Data
{
"block": 25676234,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"voter": "hr1",
"weight": 2
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T22:00:33",
"trx_id": "3cc03ede6703a8d4f9977283a0b13dca5d0c2414",
"trx_in_block": 17,
"virtual_op": 0
}alphabotupvoted (1.00%) @davidpm / heuristics-on-the-practice-of-programming2018/09/04 21:51:36
alphabotupvoted (1.00%) @davidpm / heuristics-on-the-practice-of-programming
2018/09/04 21:51:36
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| voter | alphabot |
| weight | 100 (1.00%) |
| Transaction Info | Block #25676055/Trx 585efafaf1e7605678620e3d5bd27671f6eedf08 |
View Raw JSON Data
{
"block": 25676055,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"voter": "alphabot",
"weight": 100
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:51:36",
"trx_id": "585efafaf1e7605678620e3d5bd27671f6eedf08",
"trx_in_block": 4,
"virtual_op": 0
}davidpmpublished a new post: heuristics-on-the-practice-of-programming2018/09/04 21:51:12
davidpmpublished a new post: heuristics-on-the-practice-of-programming
2018/09/04 21:51:12
| author | davidpm |
| body | @@ -1969,16 +1969,17 @@ her.%0A%0A* +%22 Just bec @@ -2041,16 +2041,17 @@ t a bug. +%22 -Piers |
| json metadata | {"tags":["technology","programming"],"app":"steemit/0.1","format":"markdown","image":["https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%20Shot%202018-09-04%20at%202.46.01%20PM.png"]} |
| parent author | |
| parent permlink | technology |
| permlink | heuristics-on-the-practice-of-programming |
| title | Heuristics on the Practice of Programming |
| Transaction Info | Block #25676047/Trx b86084588012b72002fc979fbe7179aecb5e530a |
View Raw JSON Data
{
"block": 25676047,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -1969,16 +1969,17 @@\n her.%0A%0A* \n+%22\n Just bec\n@@ -2041,16 +2041,17 @@\n t a bug.\n+%22\n -Piers \n",
"json_metadata": "{\"tags\":[\"technology\",\"programming\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\",\"image\":[\"https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%20Shot%202018-09-04%20at%202.46.01%20PM.png\"]}",
"parent_author": "",
"parent_permlink": "technology",
"permlink": "heuristics-on-the-practice-of-programming",
"title": "Heuristics on the Practice of Programming"
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:51:12",
"trx_id": "b86084588012b72002fc979fbe7179aecb5e530a",
"trx_in_block": 16,
"virtual_op": 0
}fastresteemupvoted (1.00%) @davidpm / heuristics-on-the-practice-of-programming2018/09/04 21:47:33
fastresteemupvoted (1.00%) @davidpm / heuristics-on-the-practice-of-programming
2018/09/04 21:47:33
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| voter | fastresteem |
| weight | 100 (1.00%) |
| Transaction Info | Block #25675974/Trx 66bcd58e6999bdcb5c814f165b78a907b4ae8c93 |
View Raw JSON Data
{
"block": 25675974,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"voter": "fastresteem",
"weight": 100
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:47:33",
"trx_id": "66bcd58e6999bdcb5c814f165b78a907b4ae8c93",
"trx_in_block": 0,
"virtual_op": 0
}davidpmpublished a new post: heuristics-on-the-practice-of-programming2018/09/04 21:47:18
davidpmpublished a new post: heuristics-on-the-practice-of-programming
2018/09/04 21:47:18
| author | davidpm |
| body | @@ -1,12 +1,186 @@ +!%5BScreen Shot 2018-09-04 at 2.46.01 PM.png%5D(https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%2520Shot%25202018-09-04%2520at%25202.46.01%2520PM.png)%0A ### A Living |
| json metadata | {"tags":["technology","programming"],"app":"steemit/0.1","format":"markdown","image":["https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%20Shot%202018-09-04%20at%202.46.01%20PM.png"]} |
| parent author | |
| parent permlink | technology |
| permlink | heuristics-on-the-practice-of-programming |
| title | Heuristics on the Practice of Programming |
| Transaction Info | Block #25675969/Trx cc0d6277e94225371a9e8030610c2359cc8e213b |
View Raw JSON Data
{
"block": 25675969,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -1,12 +1,186 @@\n+!%5BScreen Shot 2018-09-04 at 2.46.01 PM.png%5D(https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%2520Shot%25202018-09-04%2520at%25202.46.01%2520PM.png)%0A\n ### A Living\n",
"json_metadata": "{\"tags\":[\"technology\",\"programming\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\",\"image\":[\"https://cdn.steemitimages.com/DQmbN4HMppNXVRkvfxQYXhev3KYHdAMfFCnbBSnfBPkA5v7/Screen%20Shot%202018-09-04%20at%202.46.01%20PM.png\"]}",
"parent_author": "",
"parent_permlink": "technology",
"permlink": "heuristics-on-the-practice-of-programming",
"title": "Heuristics on the Practice of Programming"
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:47:18",
"trx_id": "cc0d6277e94225371a9e8030610c2359cc8e213b",
"trx_in_block": 19,
"virtual_op": 0
}davidpmfollowed @ilovecoding2018/09/04 21:31:30
davidpmfollowed @ilovecoding
2018/09/04 21:31:30
| id | follow |
| json | ["follow",{"follower":"davidpm","following":"ilovecoding","what":["blog"]}] |
| required auths | [] |
| required posting auths | ["davidpm"] |
| Transaction Info | Block #25675653/Trx 621bf2dbbcda49b97fca67dabf29094d82d7c400 |
View Raw JSON Data
{
"block": 25675653,
"op": [
"custom_json",
{
"id": "follow",
"json": "[\"follow\",{\"follower\":\"davidpm\",\"following\":\"ilovecoding\",\"what\":[\"blog\"]}]",
"required_auths": [],
"required_posting_auths": [
"davidpm"
]
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:31:30",
"trx_id": "621bf2dbbcda49b97fca67dabf29094d82d7c400",
"trx_in_block": 34,
"virtual_op": 0
}ilovecodingreplied to @davidpm / 20180904t213039359z2018/09/04 21:30:39
ilovecodingreplied to @davidpm / 20180904t213039359z
2018/09/04 21:30:39
| author | ilovecoding |
| body | Hello! Your post has been resteemed and upvoted by @ilovecoding because **we love coding**! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!  *Reply !stop to disable the comment. Thanks!* |
| json metadata | {"tags":["ilovecoding"],"app":"ilovecoding"} |
| parent author | davidpm |
| parent permlink | heuristics-on-the-practice-of-programming |
| permlink | 20180904t213039359z |
| title | |
| Transaction Info | Block #25675636/Trx ea782363c789158f241ad756ac85987db0ec0dfb |
View Raw JSON Data
{
"block": 25675636,
"op": [
"comment",
{
"author": "ilovecoding",
"body": "Hello! Your post has been resteemed and upvoted by @ilovecoding because **we love coding**! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On! \n  \n*Reply !stop to disable the comment. Thanks!*",
"json_metadata": "{\"tags\":[\"ilovecoding\"],\"app\":\"ilovecoding\"}",
"parent_author": "davidpm",
"parent_permlink": "heuristics-on-the-practice-of-programming",
"permlink": "20180904t213039359z",
"title": ""
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:30:39",
"trx_id": "ea782363c789158f241ad756ac85987db0ec0dfb",
"trx_in_block": 30,
"virtual_op": 0
}ax3upvoted (1.00%) @davidpm / heuristics-on-the-practice-of-programming2018/09/04 21:30:36
ax3upvoted (1.00%) @davidpm / heuristics-on-the-practice-of-programming
2018/09/04 21:30:36
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| voter | ax3 |
| weight | 100 (1.00%) |
| Transaction Info | Block #25675635/Trx 73423f15dcf0572c77f3023f87ebcfa0a630449b |
View Raw JSON Data
{
"block": 25675635,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"voter": "ax3",
"weight": 100
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:30:36",
"trx_id": "73423f15dcf0572c77f3023f87ebcfa0a630449b",
"trx_in_block": 22,
"virtual_op": 0
}ilovecodingupvoted (10.00%) @davidpm / heuristics-on-the-practice-of-programming2018/09/04 21:30:36
ilovecodingupvoted (10.00%) @davidpm / heuristics-on-the-practice-of-programming
2018/09/04 21:30:36
| author | davidpm |
| permlink | heuristics-on-the-practice-of-programming |
| voter | ilovecoding |
| weight | 1000 (10.00%) |
| Transaction Info | Block #25675635/Trx ca0d844a533b7a5e4a201984b802800e750801a5 |
View Raw JSON Data
{
"block": 25675635,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "heuristics-on-the-practice-of-programming",
"voter": "ilovecoding",
"weight": 1000
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:30:36",
"trx_id": "ca0d844a533b7a5e4a201984b802800e750801a5",
"trx_in_block": 15,
"virtual_op": 0
}davidpmpublished a new post: heuristics-on-the-practice-of-programming2018/09/04 21:30:27
davidpmpublished a new post: heuristics-on-the-practice-of-programming
2018/09/04 21:30:27
| author | davidpm |
| body | ### A Living List What follows is a living list of heuristics I've come to internalize over the years as a developer. My goal is to periodically add to this list. Consider it a repository of programming wisdom. If you have something you'd like added to the list, feel free to comment and I may update the list ʕ•ᴥ•ʔ If you disagree with anything on the list, let's fight it out in the comments. I believe in respectful, healthy debate on the open marketplace of ideas. If you find any of these nuggets to be a paraphrase or quote from someone of note, let me know! I'm not interested in plagiarizing, just sharing. ### Sagesse * No matter how many alerts or people are screaming at you, the first step is to reliably replicate the bug. * Everything in software development has already been invented. * Don’t trust the compiler. Don’t trust the tools. Don’t trust the documentation. Don’t trust yourself. * We don’t need any more computer languages. * Maintaining code is harder than writing it. * Do not overload your working memory. Protect it, keep it focused only on the task at hand if possible. * You have been taught to program as though memory, processor time, and network bandwidth are all free and infinite: none is. * "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%." -Knuth * You’re going to forget what your code does in a few months: make it easy to read. * Sometimes, all you need is a one-liner in `sed` * Beware of programmers who speak in absolutes. Programming is an art, not a religion. * If you know you will do a fixed sequence of steps more than ten times, automate it. * Backing it up is one thing. Restoring it is another. * Just because it works on your machine does not mean there is not a bug. -Piers Sutton * Wait for the point-one release of development tools before installing them. Let other people be guinea pigs. * Good programmers write good code. Great programmers write no code. Zen programmers delete code. * Lock in on one task and focus on completing it quickly and correctly, to the exclusion of all others. Every successful person does this. * Don't memorize commands or syntax; memorize concepts. Concentrate on problem solving. * When really stuck, go beyond questioning the code: question your assumptions about what the code does. * When you get overwhelmed by using new software just remember that you simply have to learn its API. Understand what it does, then learn its interface. * Ask yourself how hard your code will be to change later. Remember code is cheap. * "After you have left, remember to look back into the abyss." - Douglas Crockford * Almost everything `rms` has ever said re: proprietary software. ### (▀̿Ĺ̯▀̿ ̿) |
| json metadata | {"tags":["technology","programming"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | technology |
| permlink | heuristics-on-the-practice-of-programming |
| title | Heuristics on the Practice of Programming |
| Transaction Info | Block #25675632/Trx c64e0c9b15136348dfec2ef6abdabe06794d91bf |
View Raw JSON Data
{
"block": 25675632,
"op": [
"comment",
{
"author": "davidpm",
"body": "### A Living List\n\nWhat follows is a living list of heuristics I've come to internalize over the years as a developer. My goal is to periodically add to this list. Consider it a repository of programming wisdom. If you have something you'd like added to the list, feel free to comment and I may update the list ʕ•ᴥ•ʔ If you disagree with anything on the list, let's fight it out in the comments. I believe in respectful, healthy debate on the open marketplace of ideas. If you find any of these nuggets to be a paraphrase or quote from someone of note, let me know! I'm not interested in plagiarizing, just sharing. \n\n### Sagesse\n\n* No matter how many alerts or people are screaming at you, the first step is to reliably replicate the bug.\n\n* Everything in software development has already been invented.\n\n* Don’t trust the compiler. Don’t trust the tools. Don’t trust the documentation. Don’t trust yourself.\n\n* We don’t need any more computer languages.\n\n* Maintaining code is harder than writing it.\n\n* Do not overload your working memory. Protect it, keep it focused only on the task at hand if possible.\n\n* You have been taught to program as though memory, processor time, and network bandwidth are all free and infinite: none is. \n\n* \"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.\" -Knuth\n\n* You’re going to forget what your code does in a few months: make it easy to read.\n\n* Sometimes, all you need is a one-liner in `sed`\n\n* Beware of programmers who speak in absolutes. Programming is an art, not a religion.\n\n* If you know you will do a fixed sequence of steps more than ten times, automate it.\n\n* Backing it up is one thing. Restoring it is another.\n\n* Just because it works on your machine does not mean there is not a bug. -Piers Sutton\n\n* Wait for the point-one release of development tools before installing them. Let other people be guinea pigs.\n\n* Good programmers write good code. Great programmers write no code. Zen programmers delete code.\n\n* Lock in on one task and focus on completing it quickly and correctly, to the exclusion of all others. Every successful person does this.\n\n* Don't memorize commands or syntax; memorize concepts. Concentrate on problem solving.\n\n* When really stuck, go beyond questioning the code: question your assumptions about what the code does.\n\n* When you get overwhelmed by using new software just remember that you simply have to learn its API. Understand what it does, then learn its interface.\n\n* Ask yourself how hard your code will be to change later. Remember code is cheap.\n\n* \"After you have left, remember to look back into the abyss.\" - Douglas Crockford\n\n* Almost everything `rms` has ever said re: proprietary software.\n\n### (▀̿Ĺ̯▀̿ ̿)",
"json_metadata": "{\"tags\":[\"technology\",\"programming\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "technology",
"permlink": "heuristics-on-the-practice-of-programming",
"title": "Heuristics on the Practice of Programming"
}
],
"op_in_trx": 0,
"timestamp": "2018-09-04T21:30:27",
"trx_id": "c64e0c9b15136348dfec2ef6abdabe06794d91bf",
"trx_in_block": 50,
"virtual_op": 0
}davidpmupvoted (100.00%) @berniesanders / what-are-you-puffin-on2018/07/24 18:25:51
davidpmupvoted (100.00%) @berniesanders / what-are-you-puffin-on
2018/07/24 18:25:51
| author | berniesanders |
| permlink | what-are-you-puffin-on |
| voter | davidpm |
| weight | 10000 (100.00%) |
| Transaction Info | Block #24463348/Trx c6571811ff46b9215e108bbc26d4991e662f3199 |
View Raw JSON Data
{
"block": 24463348,
"op": [
"vote",
{
"author": "berniesanders",
"permlink": "what-are-you-puffin-on",
"voter": "davidpm",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-07-24T18:25:51",
"trx_id": "c6571811ff46b9215e108bbc26d4991e662f3199",
"trx_in_block": 37,
"virtual_op": 0
}davidpmreceived 0.001 SP curation reward for @amn / authentication-in-ruby-on-rails-from-scratch2018/07/22 10:34:03
davidpmreceived 0.001 SP curation reward for @amn / authentication-in-ruby-on-rails-from-scratch
2018/07/22 10:34:03
| comment author | amn |
| comment permlink | authentication-in-ruby-on-rails-from-scratch |
| curator | davidpm |
| reward | 2.027780 VESTS |
| Transaction Info | Block #24396351/Virtual Operation #64 |
View Raw JSON Data
{
"block": 24396351,
"op": [
"curation_reward",
{
"comment_author": "amn",
"comment_permlink": "authentication-in-ruby-on-rails-from-scratch",
"curator": "davidpm",
"reward": "2.027780 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-07-22T10:34:03",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 64
}davidpmupvoted (100.00%) @amn / authentication-in-ruby-on-rails-from-scratch2018/07/17 18:13:18
davidpmupvoted (100.00%) @amn / authentication-in-ruby-on-rails-from-scratch
2018/07/17 18:13:18
| author | amn |
| permlink | authentication-in-ruby-on-rails-from-scratch |
| voter | davidpm |
| weight | 10000 (100.00%) |
| Transaction Info | Block #24261652/Trx 8d9a181a0c1a4296ff193eddb9c1286c767c21f2 |
View Raw JSON Data
{
"block": 24261652,
"op": [
"vote",
{
"author": "amn",
"permlink": "authentication-in-ruby-on-rails-from-scratch",
"voter": "davidpm",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-07-17T18:13:18",
"trx_id": "8d9a181a0c1a4296ff193eddb9c1286c767c21f2",
"trx_in_block": 64,
"virtual_op": 0
}davidpmupvoted (100.00%) @kleric-lod / crypto-currency-cryptocurrency-going-mainstream-2018-22018/07/17 18:12:24
davidpmupvoted (100.00%) @kleric-lod / crypto-currency-cryptocurrency-going-mainstream-2018-2
2018/07/17 18:12:24
| author | kleric-lod |
| permlink | crypto-currency-cryptocurrency-going-mainstream-2018-2 |
| voter | davidpm |
| weight | 10000 (100.00%) |
| Transaction Info | Block #24261634/Trx 5c41cc2d37cee4ca5a049d7630e4f8cfaa6f4a4a |
View Raw JSON Data
{
"block": 24261634,
"op": [
"vote",
{
"author": "kleric-lod",
"permlink": "crypto-currency-cryptocurrency-going-mainstream-2018-2",
"voter": "davidpm",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-07-17T18:12:24",
"trx_id": "5c41cc2d37cee4ca5a049d7630e4f8cfaa6f4a4a",
"trx_in_block": 47,
"virtual_op": 0
}2018/07/08 21:24:51
2018/07/08 21:24:51
| delegatee | davidpm |
| delegator | steem |
| vesting shares | 30234.011559 VESTS |
| Transaction Info | Block #24006384/Trx ad811caffc1b6972ada8cf3d1eaf5b3b50f60015 |
View Raw JSON Data
{
"block": 24006384,
"op": [
"delegate_vesting_shares",
{
"delegatee": "davidpm",
"delegator": "steem",
"vesting_shares": "30234.011559 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-07-08T21:24:51",
"trx_id": "ad811caffc1b6972ada8cf3d1eaf5b3b50f60015",
"trx_in_block": 11,
"virtual_op": 0
}ishan89upvoted (100.00%) @davidpm / git-interactive-rebase-to-alter-commits2018/07/07 07:03:54
ishan89upvoted (100.00%) @davidpm / git-interactive-rebase-to-alter-commits
2018/07/07 07:03:54
| author | davidpm |
| permlink | git-interactive-rebase-to-alter-commits |
| voter | ishan89 |
| weight | 10000 (100.00%) |
| Transaction Info | Block #23960380/Trx 45f6bd446b334a444134575bdba9bbcbd02b96fa |
View Raw JSON Data
{
"block": 23960380,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "git-interactive-rebase-to-alter-commits",
"voter": "ishan89",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-07-07T07:03:54",
"trx_id": "45f6bd446b334a444134575bdba9bbcbd02b96fa",
"trx_in_block": 4,
"virtual_op": 0
}mymarketcloudhqupvoted (100.00%) @davidpm / git-interactive-rebase-to-alter-commits2018/07/07 07:03:45
mymarketcloudhqupvoted (100.00%) @davidpm / git-interactive-rebase-to-alter-commits
2018/07/07 07:03:45
| author | davidpm |
| permlink | git-interactive-rebase-to-alter-commits |
| voter | mymarketcloudhq |
| weight | 10000 (100.00%) |
| Transaction Info | Block #23960377/Trx 0eb7d486dbbcc512c18378d4eac47cbc5d911846 |
View Raw JSON Data
{
"block": 23960377,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "git-interactive-rebase-to-alter-commits",
"voter": "mymarketcloudhq",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-07-07T07:03:45",
"trx_id": "0eb7d486dbbcc512c18378d4eac47cbc5d911846",
"trx_in_block": 41,
"virtual_op": 0
}davidpmpublished a new post: git-interactive-rebase-to-alter-commits2018/07/07 07:02:33
davidpmpublished a new post: git-interactive-rebase-to-alter-commits
2018/07/07 07:02:33
| author | davidpm |
| body | Do you have skeletons in your git history? Maybe you want to re-write that history? I know I've been down that road, and since I've had to look up how to do this several times I thought it was high time to do a quick post.  For the uninitiated, [git](https://git-scm.com/) is powerful version control software written by [Linus Torvalds](https://en.wikipedia.org/wiki/Linus_Torvalds), who is most famous for writing the Linux kernel. If you are not familiar git, or version control this post will fly over your head. ## Poem Let's start writing a poem that we want under version control. Create a directory with an empty file: ``` > mkdir -p poem-git-example/poem && cd poem-git-example ``` Now let's write some [poetry](https://en.wikipedia.org/wiki/Les_Fleurs_du_mal): ``` # poem Hypocrite lecteur, — mon semblable, — mon frère! ``` Okay, so far we only have a one-line poem, but it looks like a classic. Let's commit our work: ``` > git init > git add . > git commit -m 'initial commit of poem' [master (root-commit) 5254598] initial commit of poem 1 file changed, 1 insertion(+) create mode 100644 poem ``` Let's make some changes. I want to add a title to the poem: ``` Les Fluers du Mal Hypocrite lecteur, — mon semblable, — mon frère! ``` Better put that under version control, too. ``` > git add . > git commit -m 'add title' ``` You should now have two commits. Let's just add one more thing: ``` Les Fluers du Mal Hypocrite lecteur, — mon semblable, — mon frère! - Charles Baudelaire ``` Okay, so its not mine. In fact, its not even part of the poem I stole it from. Its just one line from the preface but I like it so what? This is about git, so stay on task. Commit that last change: `git add . && git commit -m 'add signature'`. We now have three commits and a one-line poem, complete with title and signature (^̮^) But wait! looking back at our poem we have made an embarrassing typo. It should be `Fleurs` not `Fluers`! I could just correct the error and commit the changes and move on with my life. If this weren't a contrived example, I would do just that, but this is a contrived example. So, let's just say we are embarrassed that we left that punctuation out. We don't want that in our git history. What we'd like to do is go back to the original commit, amend the contents of that commit, leave the additions of the title and signature in place, and then carry on.  There are two main ways to integrate changes from one branch into another using git: merging and rebasing. If you are unfamiliar with rebasing you should definitely [read up about it](https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase). It's a great feature and nobody's <em>git-fu<em> is complete without the rebase. I'll be assuming you have experience with rebasing going forward. What we really need here to achieve our aims is something called the interactive rebase. We have three total commits, and we need to go back to that second commit where we added the misspelled title. We can use [treeish](https://git-scm.com/docs/gitglossary#gitglossary-aiddeftree-ishatree-ishalsotreeish) to walk back two commits inclusive from `HEAD`: ``` git rebase --interactive HEAD~2 # can also do: # git rebase -i HEAD~2 # or if the second commit has a SHA: f3626e9 # git rebase -i 'f3626e9^' ``` This will open an interactive prompt in your default text editor, or the one you defined in your git config. It should look something like this: ``` pick f3626e9 add title pick 4bef291 add signature # Rebase 5254598..4bef291 onto 5254598 (2 commands) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out ``` Take note of the helpful comments. Basically, we need to identify the commit we want to edit, change the word `pick` to `edit`, and then save the file. If we leave a commit as `pick` we'll use the changes from that commit as they are. If we say `edit` we'll be allowed to go make our edits, and rewrite history. ``` pick f3626e9 add title edit 4bef291 add signature # Rebase 5254598..4bef291 onto 5254598 (2 commands) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out ``` Save that and you should see some output like this: ``` Stopped at 4bef291... add signature You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue ``` This means we are free to make our changes. Open up poem and fix that typo `Fluers -> Fleurs`: ``` Les Fleurs du Mal Hypocrite lecteur, — mon semblable, — mon frère! - Charles Baudelaire ``` Now, we need to remember what our prompt was telling us. We can amend the commit with: `git commit --amend`. That's great and will open another prompt in your editor and ask you to update the commit message. In our case, we just want to use the same message, so we're going to add a couple flags to our command to skip ahead: ``` git commit --all --amend --no-edit ``` So, we have now updated that second commit with the correct spelling. But if you run `git status` you'll notice we are in a detached HEAD state. We want to move HEAD back to our third commit where we were. Once again, we can just follow the prompt and run `git rebase --continue` to return back to the previous commit HEAD. That's all there is to it! Now, this kind of fancy footwork will end up changing the SHA-1 of that commit and rewrites the history from that point forward. You can break repositories doing things like this, and is best to avoid it when you can. If you have a trunk/master branch that is immutable then you will be blocked from a repo admin from trying something like anyways. Having said all that, I'm sure you can see that this can come in handy in a pinch. Until next time! ### | (• ◡•)| (❍ᴥ❍ʋ) |
| json metadata | {"tags":["programming","git","rebase"],"image":["https://cdn.steemitimages.com/DQmWJqJjDB6mm7eRds4KACk5LjtvoBdUsU5K7VCmPx2AC5Y/Erase-The-Past.jpg","https://media.giphy.com/media/1M2BVyXFvMbYY/giphy.gif"],"links":["https://git-scm.com/","https://en.wikipedia.org/wiki/Linus_Torvalds","https://en.wikipedia.org/wiki/Les_Fleurs_du_mal","https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase","https://git-scm.com/docs/gitglossary#gitglossary-aiddeftree-ishatree-ishalsotreeish"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | programming |
| permlink | git-interactive-rebase-to-alter-commits |
| title | Git Interactive Rebase to Alter Commits |
| Transaction Info | Block #23960353/Trx 56fcc41411a69589ad1931c400f2ddc1dd3682b7 |
View Raw JSON Data
{
"block": 23960353,
"op": [
"comment",
{
"author": "davidpm",
"body": "Do you have skeletons in your git history? Maybe you want to re-write that history? I know I've been down that road, and since I've had to look up how to do this several times I thought it was high time to do a quick post.\n\n\n\nFor the uninitiated, [git](https://git-scm.com/) is powerful version control software written by [Linus Torvalds](https://en.wikipedia.org/wiki/Linus_Torvalds), who is most famous for writing the Linux kernel. If you are not familiar git, or version control this post will fly over your head.\n\n## Poem\n\nLet's start writing a poem that we want under version control. Create a directory with an empty file:\n```\n> mkdir -p poem-git-example/poem && cd poem-git-example\n```\nNow let's write some [poetry](https://en.wikipedia.org/wiki/Les_Fleurs_du_mal):\n```\n# poem\nHypocrite lecteur, — mon semblable, — mon frère!\n```\nOkay, so far we only have a one-line poem, but it looks like a classic. Let's commit our work:\n```\n> git init\n> git add .\n> git commit -m 'initial commit of poem'\n\n[master (root-commit) 5254598] initial commit of poem\n 1 file changed, 1 insertion(+)\n create mode 100644 poem\n```\nLet's make some changes. I want to add a title to the poem:\n```\nLes Fluers du Mal\n\nHypocrite lecteur, — mon semblable, — mon frère!\n```\nBetter put that under version control, too.\n```\n> git add .\n> git commit -m 'add title'\n```\nYou should now have two commits. Let's just add one more thing:\n```\nLes Fluers du Mal\n\nHypocrite lecteur, — mon semblable, — mon frère!\n\n- Charles Baudelaire\n```\nOkay, so its not mine. In fact, its not even part of the poem I stole it from. Its just one line from the preface but I like it so what? This is about git, so stay on task. \n\nCommit that last change: `git add . && git commit -m 'add signature'`. We now have three commits and a one-line poem, complete with title and signature (^̮^) But wait! looking back at our poem we have made an embarrassing typo. It should be `Fleurs` not `Fluers`! I could just correct the error and commit the changes and move on with my life. If this weren't a contrived example, I would do just that, but this is a contrived example. \n\nSo, let's just say we are embarrassed that we left that punctuation out. We don't want that in our git history. What we'd like to do is go back to the original commit, amend the contents of that commit, leave the additions of the title and signature in place, and then carry on.\n\n\n\nThere are two main ways to integrate changes from one branch into another using git: merging and rebasing. If you are unfamiliar with rebasing you should definitely [read up about it](https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase). It's a great feature and nobody's <em>git-fu<em> is complete without the rebase. I'll be assuming you have experience with rebasing going forward.\n\nWhat we really need here to achieve our aims is something called the interactive rebase. We have three total commits, and we need to go back to that second commit where we added the misspelled title. We can use [treeish](https://git-scm.com/docs/gitglossary#gitglossary-aiddeftree-ishatree-ishalsotreeish) to walk back two commits inclusive from `HEAD`:\n\n```\ngit rebase --interactive HEAD~2\n\n# can also do:\n# git rebase -i HEAD~2\n\n# or if the second commit has a SHA: f3626e9\n# git rebase -i 'f3626e9^'\n```\n\nThis will open an interactive prompt in your default text editor, or the one you defined in your git config. It should look something like this:\n\n```\npick f3626e9 add title\npick 4bef291 add signature\n\n# Rebase 5254598..4bef291 onto 5254598 (2 commands)\n#\n# Commands:\n# p, pick = use commit\n# r, reword = use commit, but edit the commit message\n# e, edit = use commit, but stop for amending\n# s, squash = use commit, but meld into previous commit\n# f, fixup = like \"squash\", but discard this commit's log message\n# x, exec = run command (the rest of the line) using shell\n# d, drop = remove commit\n#\n# These lines can be re-ordered; they are executed from top to bottom.\n#\n# If you remove a line here THAT COMMIT WILL BE LOST.\n#\n# However, if you remove everything, the rebase will be aborted.\n#\n# Note that empty commits are commented out\n```\nTake note of the helpful comments. Basically, we need to identify the commit we want to edit, change the word `pick` to `edit`, and then save the file. If we leave a commit as `pick` we'll use the changes from that commit as they are. If we say `edit` we'll be allowed to go make our edits, and rewrite history.\n```\npick f3626e9 add title\nedit 4bef291 add signature\n\n# Rebase 5254598..4bef291 onto 5254598 (2 commands)\n#\n# Commands:\n# p, pick = use commit\n# r, reword = use commit, but edit the commit message\n# e, edit = use commit, but stop for amending\n# s, squash = use commit, but meld into previous commit\n# f, fixup = like \"squash\", but discard this commit's log message\n# x, exec = run command (the rest of the line) using shell\n# d, drop = remove commit\n#\n# These lines can be re-ordered; they are executed from top to bottom.\n#\n# If you remove a line here THAT COMMIT WILL BE LOST.\n#\n# However, if you remove everything, the rebase will be aborted.\n#\n# Note that empty commits are commented out\n```\nSave that and you should see some output like this:\n```\nStopped at 4bef291... add signature\nYou can amend the commit now, with\n\n git commit --amend\n\nOnce you are satisfied with your changes, run\n\n git rebase --continue\n```\nThis means we are free to make our changes. Open up poem and fix that typo `Fluers -> Fleurs`:\n```\nLes Fleurs du Mal\n\nHypocrite lecteur, — mon semblable, — mon frère!\n\n- Charles Baudelaire\n```\nNow, we need to remember what our prompt was telling us. We can amend the commit with: `git commit --amend`. That's great and will open another prompt in your editor and ask you to update the commit message. In our case, we just want to use the same message, so we're going to add a couple flags to our command to skip ahead:\n```\ngit commit --all --amend --no-edit\n```\nSo, we have now updated that second commit with the correct spelling. But if you run `git status` you'll notice we are in a detached HEAD state. We want to move HEAD back to our third commit where we were. Once again, we can just follow the prompt and run `git rebase --continue` to return back to the previous commit HEAD.\n\nThat's all there is to it! Now, this kind of fancy footwork will end up changing the SHA-1 of that commit and rewrites the history from that point forward. You can break repositories doing things like this, and is best to avoid it when you can. If you have a trunk/master branch that is immutable then you will be blocked from a repo admin from trying something like anyways. Having said all that, I'm sure you can see that this can come in handy in a pinch. Until next time!\n\n### | (• ◡•)| (❍ᴥ❍ʋ)",
"json_metadata": "{\"tags\":[\"programming\",\"git\",\"rebase\"],\"image\":[\"https://cdn.steemitimages.com/DQmWJqJjDB6mm7eRds4KACk5LjtvoBdUsU5K7VCmPx2AC5Y/Erase-The-Past.jpg\",\"https://media.giphy.com/media/1M2BVyXFvMbYY/giphy.gif\"],\"links\":[\"https://git-scm.com/\",\"https://en.wikipedia.org/wiki/Linus_Torvalds\",\"https://en.wikipedia.org/wiki/Les_Fleurs_du_mal\",\"https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase\",\"https://git-scm.com/docs/gitglossary#gitglossary-aiddeftree-ishatree-ishalsotreeish\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "programming",
"permlink": "git-interactive-rebase-to-alter-commits",
"title": "Git Interactive Rebase to Alter Commits"
}
],
"op_in_trx": 0,
"timestamp": "2018-07-07T07:02:33",
"trx_id": "56fcc41411a69589ad1931c400f2ddc1dd3682b7",
"trx_in_block": 26,
"virtual_op": 0
}davidpmreceived 0.001 STEEM, 0.006 SBD, 0.006 SP author reward for @davidpm / create-a-command-line-gem-from-scratch-with-thor2018/06/10 06:17:51
davidpmreceived 0.001 STEEM, 0.006 SBD, 0.006 SP author reward for @davidpm / create-a-command-line-gem-from-scratch-with-thor
2018/06/10 06:17:51
| author | davidpm |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| sbd payout | 0.006 SBD |
| steem payout | 0.001 STEEM |
| vesting payout | 10.161328 VESTS |
| Transaction Info | Block #23192516/Virtual Operation #4 |
View Raw JSON Data
{
"block": 23192516,
"op": [
"author_reward",
{
"author": "davidpm",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"sbd_payout": "0.006 SBD",
"steem_payout": "0.001 STEEM",
"vesting_payout": "10.161328 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-10T06:17:51",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 4
}davidpmfollowed @radiumbattery2018/06/04 00:12:27
davidpmfollowed @radiumbattery
2018/06/04 00:12:27
| id | follow |
| json | ["follow",{"follower":"davidpm","following":"radiumbattery","what":["blog"]}] |
| required auths | [] |
| required posting auths | ["davidpm"] |
| Transaction Info | Block #23012467/Trx 87ba04c2f307f7c4385f4ad800ca44c76b047aaf |
View Raw JSON Data
{
"block": 23012467,
"op": [
"custom_json",
{
"id": "follow",
"json": "[\"follow\",{\"follower\":\"davidpm\",\"following\":\"radiumbattery\",\"what\":[\"blog\"]}]",
"required_auths": [],
"required_posting_auths": [
"davidpm"
]
}
],
"op_in_trx": 0,
"timestamp": "2018-06-04T00:12:27",
"trx_id": "87ba04c2f307f7c4385f4ad800ca44c76b047aaf",
"trx_in_block": 24,
"virtual_op": 0
}davidpmupvoted (100.00%) @radiumbattery / jade-hunt2018/06/04 00:12:18
davidpmupvoted (100.00%) @radiumbattery / jade-hunt
2018/06/04 00:12:18
| author | radiumbattery |
| permlink | jade-hunt |
| voter | davidpm |
| weight | 10000 (100.00%) |
| Transaction Info | Block #23012464/Trx 60ac81ae1604a239c82ab88b2d412859de2338f4 |
View Raw JSON Data
{
"block": 23012464,
"op": [
"vote",
{
"author": "radiumbattery",
"permlink": "jade-hunt",
"voter": "davidpm",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-06-04T00:12:18",
"trx_id": "60ac81ae1604a239c82ab88b2d412859de2338f4",
"trx_in_block": 31,
"virtual_op": 0
}lionindayardupvoted (0.49%) @davidpm / dropping-into-b-trees2018/06/03 21:35:09
lionindayardupvoted (0.49%) @davidpm / dropping-into-b-trees
2018/06/03 21:35:09
| author | davidpm |
| permlink | dropping-into-b-trees |
| voter | lionindayard |
| weight | 49 (0.49%) |
| Transaction Info | Block #23009322/Trx 2466fa51b78c0dac08c770531b8f90d1ad699d14 |
View Raw JSON Data
{
"block": 23009322,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "dropping-into-b-trees",
"voter": "lionindayard",
"weight": 49
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T21:35:09",
"trx_id": "2466fa51b78c0dac08c770531b8f90d1ad699d14",
"trx_in_block": 45,
"virtual_op": 0
}marketstackupvoted (0.49%) @davidpm / dropping-into-b-trees2018/06/03 21:35:09
marketstackupvoted (0.49%) @davidpm / dropping-into-b-trees
2018/06/03 21:35:09
| author | davidpm |
| permlink | dropping-into-b-trees |
| voter | marketstack |
| weight | 49 (0.49%) |
| Transaction Info | Block #23009322/Trx a01b75f25654ca838cc1c4d751efdd6ee54bdbe2 |
View Raw JSON Data
{
"block": 23009322,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "dropping-into-b-trees",
"voter": "marketstack",
"weight": 49
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T21:35:09",
"trx_id": "a01b75f25654ca838cc1c4d751efdd6ee54bdbe2",
"trx_in_block": 12,
"virtual_op": 0
}swaggerupvoted (0.03%) @davidpm / dropping-into-b-trees2018/06/03 21:27:54
swaggerupvoted (0.03%) @davidpm / dropping-into-b-trees
2018/06/03 21:27:54
| author | davidpm |
| permlink | dropping-into-b-trees |
| voter | swagger |
| weight | 3 (0.03%) |
| Transaction Info | Block #23009177/Trx 00c2c90a51b4179ce366b87c7276eb0131f053a4 |
View Raw JSON Data
{
"block": 23009177,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "dropping-into-b-trees",
"voter": "swagger",
"weight": 3
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T21:27:54",
"trx_id": "00c2c90a51b4179ce366b87c7276eb0131f053a4",
"trx_in_block": 25,
"virtual_op": 0
}davidpmpublished a new post: dropping-into-b-trees2018/06/03 21:06:12
davidpmpublished a new post: dropping-into-b-trees
2018/06/03 21:06:12
| author | davidpm |
| body | My recent (and only) RailsConf talk: https://www.youtube.com/watch?v=17XecHy9Pzg |
| json metadata | {"tags":["technology","rails","ruby","databases","programming"],"image":["https://img.youtube.com/vi/17XecHy9Pzg/0.jpg"],"links":["https://www.youtube.com/watch?v=17XecHy9Pzg"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | technology |
| permlink | dropping-into-b-trees |
| title | Dropping Into B-trees |
| Transaction Info | Block #23008743/Trx d2440d3bc8ae4f60632f99e3f39a4844f4b6224a |
View Raw JSON Data
{
"block": 23008743,
"op": [
"comment",
{
"author": "davidpm",
"body": "My recent (and only) RailsConf talk:\n\nhttps://www.youtube.com/watch?v=17XecHy9Pzg",
"json_metadata": "{\"tags\":[\"technology\",\"rails\",\"ruby\",\"databases\",\"programming\"],\"image\":[\"https://img.youtube.com/vi/17XecHy9Pzg/0.jpg\"],\"links\":[\"https://www.youtube.com/watch?v=17XecHy9Pzg\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "technology",
"permlink": "dropping-into-b-trees",
"title": "Dropping Into B-trees"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T21:06:12",
"trx_id": "d2440d3bc8ae4f60632f99e3f39a4844f4b6224a",
"trx_in_block": 29,
"virtual_op": 0
}magpieloverupvoted (100.00%) @davidpm / create-a-command-line-gem-from-scratch-with-thor-part-two2018/06/03 07:35:12
magpieloverupvoted (100.00%) @davidpm / create-a-command-line-gem-from-scratch-with-thor-part-two
2018/06/03 07:35:12
| author | davidpm |
| permlink | create-a-command-line-gem-from-scratch-with-thor-part-two |
| voter | magpielover |
| weight | 10000 (100.00%) |
| Transaction Info | Block #22992523/Trx 14ccfee4d8c44df15fe9cef575df3418097af1b3 |
View Raw JSON Data
{
"block": 22992523,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "create-a-command-line-gem-from-scratch-with-thor-part-two",
"voter": "magpielover",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T07:35:12",
"trx_id": "14ccfee4d8c44df15fe9cef575df3418097af1b3",
"trx_in_block": 1,
"virtual_op": 0
}2018/06/03 07:24:51
2018/06/03 07:24:51
| author | davidpm |
| permlink | create-a-command-line-gem-from-scratch-with-thor-part-two |
| voter | lionindayard |
| weight | 49 (0.49%) |
| Transaction Info | Block #22992316/Trx 07cf29dc61c2d8a9a7ffc0411e65091747a3b9b5 |
View Raw JSON Data
{
"block": 22992316,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "create-a-command-line-gem-from-scratch-with-thor-part-two",
"voter": "lionindayard",
"weight": 49
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T07:24:51",
"trx_id": "07cf29dc61c2d8a9a7ffc0411e65091747a3b9b5",
"trx_in_block": 38,
"virtual_op": 0
}marketstackupvoted (0.49%) @davidpm / create-a-command-line-gem-from-scratch-with-thor-part-two2018/06/03 07:24:51
marketstackupvoted (0.49%) @davidpm / create-a-command-line-gem-from-scratch-with-thor-part-two
2018/06/03 07:24:51
| author | davidpm |
| permlink | create-a-command-line-gem-from-scratch-with-thor-part-two |
| voter | marketstack |
| weight | 49 (0.49%) |
| Transaction Info | Block #22992316/Trx 789482d07c1a6cd5e8fc8d118820a2aed5e3419a |
View Raw JSON Data
{
"block": 22992316,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "create-a-command-line-gem-from-scratch-with-thor-part-two",
"voter": "marketstack",
"weight": 49
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T07:24:51",
"trx_id": "789482d07c1a6cd5e8fc8d118820a2aed5e3419a",
"trx_in_block": 2,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor-part-two2018/06/03 07:17:48
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor-part-two
2018/06/03 07:17:48
| author | davidpm |
| body | This is the last in a two-part post on creating a Ruby cli gem from scratch. Part one <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>found here</a>. --- Picking up from our <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>last post</a>, we want to start working on our user interface. In this case we want a cli that looks something like this: ``` #-> what would you like to do? 1 - Build a matrix 2 - Generate a random matrix #-> now that we have a matrix, would you like to spiralize it? 1 - Spiralize it 2 - Stare at it ``` ### Thor  We're going to use <a href='https://github.com/erikhuda/thor'>Thor</a>, which is a toolkit for building command-line tools and applications. In fact, `Bundler` itself was written using `Thor`. Let's add it to the bottom of our `gemspec` and re-install: ``` # spiralizer.gemspec spec.add_dependency "thor" --- # from cmd line: > bundle install ``` Now, we just need to require it in and start using it! ``` require 'thor' ... ``` But, this is also a good time to start separating our code a bit, so we don't let things get to messy. So, let's create a file called `lib/spiralizer/cli.rb`, and move our `require` statement into this file. Then we will stub out a class under the `Spiralizer` namespace and inherit from `Thor`: ``` require 'thor' require 'spiralizer' class Spiralizer::CLI < Thor end ``` Back in our main module we will need to require in this new file: ``` require "spiralizer/version" require "spiralizer/cli" module Spiralizer ... end ``` Our cli is gem that users can install on their systems. We want them to have an executable that will take a simple command to spin up prompt we defined earlier: Say the user enters `spiralizer go` for example. We would want output similar to what we envisioned above: ``` $ >spiralizer go #-> what would you like to do? 1 - Build a matrix 2 - Generate a random matrix ``` Thor gives us some easy-to-use tooling to get this thing going in no time. We'll get started by defining our `go` method, and use the `desc` macro annotation get some magic documentation: ``` require 'thor' require 'spiralizer' class Spiralizer::CLI < Thor desc 'go', 'starts a prompt that brings users to spiral heaven' def go say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" action = ask '> ' say "\nyou picked #{action}" exit! end end ``` `Thor` provides some intuitive methods like `say` to output something to the user, and `ask` to prompt the user for input. They work just as expected. Notice that you can assign the result of `ask` to a variable. To start playing around with it, we just need to create a new directory called `exe` (alternatively, we could have scaffolded our gem with a `--exe` option but here we are). It should be a sibling directory of `lib` and `bin`. `cd exe && touch spiralizer` to create a file. Then, give it executable permissions: `> chmod +x exe/spiralizer`, and dump this inside: ``` #!/usr/bin/env ruby require 'spiralizer/cli' Spiralizer::CLI.start ``` And let's give 'er a whirl: ``` > bundle exec exe/spiralizer Commands: spiralizer go # starts a prompt that brings users to spiral heaven spiralizer help [COMMAND] # Describe available commands or one specific command ``` Notice the helpful output Thor gives us. We have two commands available: `go` and `help`. Let's try them out: ``` > bundle exec exe/spiralizer help Commands: spiralizer go # starts a prompt that brings users to spiral heaven spiralizer help [COMMAND] # Describe available commands or one specific command > bundle exec exe/spiralizer go What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 1 you picked 1 ``` So, `help` is the default action, and we can see our new `go` command is registered properly. Right now our prompt gives us two options, let's add functionality for our second option. When a user enters `'2'` we want to spit out a random matrix. We can do a quick and dirty version of this and just hard code some matrix range/dimension pairs that we will feed to our matrix factory. Let's make a constant called `MATRICES` that itself is a matrix: ``` MATRICES = [ ['A-L', '4x3'], ['M-X', '3x4'], ['1-8', '2x4'], ['1-144', '12x12'], ['C-J', '4x2'], ['A-Z', '13x2'] ].freeze ``` We now need a semi-random way to pick one of these: `range_response, dimensions = MATRICES[rand(6)]`. This will give us a range value as a string like 'C-j' and dimensions like `4x2`. We'll then split that range string up, so we can feed it into our factory: ``` range = range_response.split('-') matrix = Spiralizer::Matrix.the_matrix(range: (range.first..range.last), dimensions: dimensions) ``` `Thor` wants you to put helper methods for your class in a `no_commands` block, so let's create one of those and put all this together so its somewhat presentable. Here's what we have so far: ``` class Spiralizer::CLI < Thor attr_reader :matrix MATRICES = [ ['A-L', '4x3'], ['M-X', '3x4'], ['1-8', '2x4'], ['1-144', '12x12'], ['C-J', '4x2'], ['A-Z', '13x2'] ].freeze desc 'go', 'starts a prompt that brings users to spiral heaven' def go say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" action = ask '> ' say "\n" range, dimensions = random_range_and_dimensions @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions) output_matrix end no_commands do def random_range_and_dimensions say "\ngenerating..." sleep 1 range_response, dimensions = MATRICES[rand(6)] range = range_response.split('-') return (range.first..range.last), dimensions end def output_matrix say "\nHere is your M A T R I X\n------------------------" matrix.each { |inner| say inner.join("\t") } exit! end end end ``` You'll notice that any input we give the prompt at this point will just generate a random matrix from our list. We're making progress! ``` > bundle exec exe/spiralizer go What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 2 generating... Here is your M A T R I X ------------------------ 1 2 3 4 5 6 7 8 ``` Next, let's hook up a follow up prompt that asks us if we want to spiralize the matrix. Update your `output_matrix` method: ``` def output_matrix say "\nHere is your M A T R I X\n------------------------" matrix.each { |arr| say arr.join("\t") } say "\nWould you like to spiralize it? (choose a number)\n 1 - Yes\n 2 - No" action = ask '> ' say "\n" if action == '1' say Spiralizer::Spiralize.new(matrix: matrix).perform end exit! end ``` We are now prompted for more input after the matrix has been generated! Pretty neat. ``` > bundle exec exe/spiralizer go What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 2 generating... Here is your M A T R I X ------------------------ M N O P Q R S T U V W X Would you like to spiralize it? (choose a number) 1 - Yes 2 - No > 1 m n o r u x w v s p q t ``` This is looking good but we still need to handle our first option to allow users to create their own matrix. Let's add a method to handle that input now: ``` def user_range_and_dimensions range_response = ask("Please provide range. Acceptable format: (A-L) or (1-6)\n> ") values = range_response.split('-') dimensions = ask("Please provide dimensions. Acceptable format: (4x3) or (3x2)\n> ") return (values.first..values.last), dimensions end ``` We want this to trigger when the user chooses `'1'` from our introduction prompt so we need to re-work our `go` method: ``` def go say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" action = ask '> ' say "\n" case action when '1' then range, dimensions = user_range_and_dimensions when '2' then range, dimensions = random_range_and_dimensions else exit! end range, dimensions = random_range_and_dimensions @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions) output_matrix rescue Spiralizer::InvalidInput => e say "\nUh oh! #{e.message}" ensure say "\nexiting..." exit! end ``` You should be getting the hang of how to use `Thor`. Our class is need of some cleanup, but with some refactoring we end up with something like this: ``` require 'thor' require 'spiralizer' class Spiralizer::CLI < Thor MATRICES = [ ['A-L', '4x3'], ['M-X', '3x4'], ['1-8', '2x4'], ['1-144', '12x12'], ['C-J', '4x2'], ['A-Z', '13x2'] ].freeze attr_reader :action, :matrix desc 'go', 'starts a prompt that brings users to spiral heaven' def go clear_screen! intro_prompt build_matrix output_matrix action_prompt spiralize! rescue Spiralizer::InvalidInput => e say "\nUh oh! #{e.message}" ensure quit_softly end no_commands do def intro_prompt say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" @action = ask '> ' end def build_matrix say "\n" case action when '1' then range, dimensions = user_range_and_dimensions when '2' then range, dimensions = random_range_and_dimensions else quit_softly end @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions) end def user_range_and_dimensions range_response = ask("Please provide range. Acceptable format: (A-L) or (1-6)\n> ") values = range_response.split('-') dimensions = ask("Please provide dimensions. Acceptable format: (4x3) or (3x2)\n> ") return (values.first..values.last), dimensions end def random_range_and_dimensions say "\ngenerating..." pause_for_effect range_response, dimensions = MATRICES[rand(6)] range = range_response.split('-') return (range.first..range.last), dimensions end def action_prompt say "\nWhat would you like to do with it? (choose a number)\n 1 - Spiralize it\n 2 - Look at it" @action = ask '> ' end def output_matrix say "\nHere is your M A T R I X\n------------------------" matrix.each { |inner| say inner.join("\t") } end def spiralize! say "\n" say Spiralizer::Spiralize.new(matrix: matrix).perform if action == '1' end def clear_screen! return system 'cls' if Gem.win_platform? system 'clear' end def pause_for_effect sleep 0.5 end def quit_softly say "\nexiting..." pause_for_effect exit! end end end ``` Now, let's try it all out: ``` What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 1 Please provide range. Acceptable format: (A-L) or (1-6) > 1-6 Please provide dimensions. Acceptable format: (4x3) or (3x2) > 2x3 Here is your M A T R I X ------------------------ 1 2 3 4 5 6 What would you like to do with it? (choose a number) 1 - Spiralize it 2 - Look at it > 1 1 2 4 6 5 3 exiting... ``` Our little gem is complete! There is a lot of cleanup we can and should do, but this is where this blog post ends. Feel free to add new functionality and experiment further with Thor. I've gone ahead and added a `Crisscross` class for example in my <a href="https://github.com/david-pm/matrix-spiralizer">repo</a>. Oh! One more thing, if yoy run `bundle exec rake install` bundler will install this gem on your system as a global executable so you can then call it like this: ``` > spiralizer go ``` If you ever need to uninstall it for some insane reason just run `gem uninstall spiralizer`. You can find a finalized version of the code <a href="https://github.com/david-pm/matrix-spiralizer">here</a>. Well, there you have it. We've built a cli gem from scratch using `Bundler` and `Thor`. I hope this post serves you well and until next time! ### (づ。◕‿‿◕。)づ *:・゚✧ ✧゚・*:・゚✧ ✧゚・ |
| json metadata | {"tags":["technology","ruby","thor","programming"],"image":["https://ae01.alicdn.com/kf/HTB1FjN5KFXXXXa6XFXXq6xXFXXXx/1-1-Scale-Full-Metal-Thor-Hammer-Mjolnir-1-1-Replica-Thor-Custom-Cosplay-Hammer-Collection.jpg_640x640.jpg"],"links":["https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor","https://github.com/erikhuda/thor","https://github.com/david-pm/matrix-spiralizer"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor-part-two |
| title | Create a command-line gem from scratch with Thor (part two) |
| Transaction Info | Block #22992175/Trx 7f9d967d5f2df769eec5c62c34679339a2ffca2a |
View Raw JSON Data
{
"block": 22992175,
"op": [
"comment",
{
"author": "davidpm",
"body": "This is the last in a two-part post on creating a Ruby cli gem from scratch. Part one <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>found here</a>.\n\n---\n\nPicking up from our <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>last post</a>, we want to start working on our user interface. In this case we want a cli that looks something like this:\n```\n#-> what would you like to do?\n 1 - Build a matrix\n 2 - Generate a random matrix\n\n#-> now that we have a matrix, would you like to spiralize it?\n 1 - Spiralize it\n 2 - Stare at it\n```\n### Thor\n\n\nWe're going to use <a href='https://github.com/erikhuda/thor'>Thor</a>, which is a toolkit for building command-line tools and applications. In fact, `Bundler` itself was written using `Thor`. Let's add it to the bottom of our `gemspec` and re-install:\n\n```\n# spiralizer.gemspec\nspec.add_dependency \"thor\"\n---\n# from cmd line:\n> bundle install\n```\n\nNow, we just need to require it in and start using it!\n\n```\nrequire 'thor'\n...\n```\n\nBut, this is also a good time to start separating our code a bit, so we don't let things get to messy. So, let's create a file called `lib/spiralizer/cli.rb`, and move our `require` statement into this file. Then we will stub out a class under the `Spiralizer` namespace and inherit from `Thor`:\n\n```\nrequire 'thor'\nrequire 'spiralizer'\n\nclass Spiralizer::CLI < Thor\nend\n```\n\nBack in our main module we will need to require in this new file:\n\n```\nrequire \"spiralizer/version\"\nrequire \"spiralizer/cli\"\n\nmodule Spiralizer\n...\nend\n```\n\nOur cli is gem that users can install on their systems. We want them to have an executable that will take a simple command to spin up prompt we defined earlier:\n\nSay the user enters `spiralizer go` for example. We would want output similar to what we envisioned above:\n```\n$ >spiralizer go\n\n#-> what would you like to do?\n 1 - Build a matrix\n 2 - Generate a random matrix\n```\n\nThor gives us some easy-to-use tooling to get this thing going in no time. We'll get started by defining our `go` method, and use the `desc` macro annotation get some magic documentation:\n```\nrequire 'thor'\nrequire 'spiralizer'\n\nclass Spiralizer::CLI < Thor\n desc 'go', 'starts a prompt that brings users to spiral heaven'\n def go\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n action = ask '> '\n say \"\\nyou picked #{action}\"\n exit!\n end\nend\n```\n`Thor` provides some intuitive methods like `say` to output something to the user, and `ask` to prompt the user for input. They work just as expected. Notice that you can assign the result of `ask` to a variable.\n\nTo start playing around with it, we just need to create a new directory called `exe` (alternatively, we could have scaffolded our gem with a `--exe` option but here we are). It should be a sibling directory of `lib` and `bin`. `cd exe && touch spiralizer` to create a file. Then, give it executable permissions: `> chmod +x exe/spiralizer`, and dump this inside:\n```\n#!/usr/bin/env ruby\n\nrequire 'spiralizer/cli'\nSpiralizer::CLI.start\n```\n\n And let's give 'er a whirl:\n\n```\n> bundle exec exe/spiralizer\n\nCommands:\n spiralizer go # starts a prompt that brings users to spiral heaven\n spiralizer help [COMMAND] # Describe available commands or one specific command\n```\n\nNotice the helpful output Thor gives us. We have two commands available: `go` and `help`. Let's try them out:\n\n```\n> bundle exec exe/spiralizer help\n\nCommands:\n spiralizer go # starts a prompt that brings users to spiral heaven\n spiralizer help [COMMAND] # Describe available commands or one specific command\n\n> bundle exec exe/spiralizer go\n\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 1\n\nyou picked 1\n```\nSo, `help` is the default action, and we can see our new `go` command is registered properly. Right now our prompt gives us two options, let's add functionality for our second option. When a user enters `'2'` we want to spit out a random matrix. We can do a quick and dirty version of this and just hard code some matrix range/dimension pairs that we will feed to our matrix factory. Let's make a constant called `MATRICES` that itself is a matrix:\n\n```\nMATRICES = [\n ['A-L', '4x3'],\n ['M-X', '3x4'],\n ['1-8', '2x4'],\n ['1-144', '12x12'],\n ['C-J', '4x2'],\n ['A-Z', '13x2']\n].freeze\n```\n\nWe now need a semi-random way to pick one of these: `range_response, dimensions = MATRICES[rand(6)]`. This will give us a range value as a string like 'C-j' and dimensions like `4x2`. We'll then split that range string up, so we can feed it into our factory:\n```\nrange = range_response.split('-')\nmatrix = Spiralizer::Matrix.the_matrix(range: (range.first..range.last), dimensions: dimensions)\n```\n\n`Thor` wants you to put helper methods for your class in a `no_commands` block, so let's create one of those\nand put all this together so its somewhat presentable. Here's what we have so far:\n\n```\nclass Spiralizer::CLI < Thor\n attr_reader :matrix\n\n MATRICES = [\n ['A-L', '4x3'],\n ['M-X', '3x4'],\n ['1-8', '2x4'],\n ['1-144', '12x12'],\n ['C-J', '4x2'],\n ['A-Z', '13x2']\n ].freeze\n\n desc 'go', 'starts a prompt that brings users to spiral heaven'\n def go\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n action = ask '> '\n say \"\\n\"\n\n range, dimensions = random_range_and_dimensions\n @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)\n output_matrix\n end\n\n no_commands do\n def random_range_and_dimensions\n say \"\\ngenerating...\"\n sleep 1\n range_response, dimensions = MATRICES[rand(6)]\n range = range_response.split('-')\n return (range.first..range.last), dimensions\n end\n\n def output_matrix\n say \"\\nHere is your M A T R I X\\n------------------------\"\n matrix.each { |inner| say inner.join(\"\\t\") }\n exit!\n end\n end\nend\n```\n\nYou'll notice that any input we give the prompt at this point will just generate a random matrix from our list. We're making progress!\n\n```\n> bundle exec exe/spiralizer go\n\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 2\n\n\ngenerating...\n\nHere is your M A T R I X\n------------------------\n1 2\n3 4\n5 6\n7 8\n```\n\nNext, let's hook up a follow up prompt that asks us if we want to spiralize the matrix. Update your `output_matrix` method:\n\n```\ndef output_matrix\n say \"\\nHere is your M A T R I X\\n------------------------\"\n matrix.each { |arr| say arr.join(\"\\t\") }\n\n say \"\\nWould you like to spiralize it? (choose a number)\\n 1 - Yes\\n 2 - No\"\n action = ask '> '\n say \"\\n\"\n if action == '1'\n say Spiralizer::Spiralize.new(matrix: matrix).perform\n end\n\n exit!\nend\n```\n\nWe are now prompted for more input after the matrix has been generated! Pretty neat.\n\n```\n> bundle exec exe/spiralizer go\n\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 2\n\n\ngenerating...\n\nHere is your M A T R I X\n------------------------\nM N O\nP Q R\nS T U\nV W X\n\nWould you like to spiralize it? (choose a number)\n 1 - Yes\n 2 - No\n> 1\n\nm n o r u x w v s p q t\n```\n\nThis is looking good but we still need to handle our first option to allow users to create their own matrix. Let's add a method to handle that input now:\n\n```\n def user_range_and_dimensions\n range_response = ask(\"Please provide range. Acceptable format: (A-L) or (1-6)\\n> \")\n values = range_response.split('-')\n dimensions = ask(\"Please provide dimensions. Acceptable format: (4x3) or (3x2)\\n> \")\n return (values.first..values.last), dimensions\n end\n```\n\nWe want this to trigger when the user chooses `'1'` from our introduction prompt so we need to re-work our `go` method:\n\n```\ndef go\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n action = ask '> '\n say \"\\n\"\n\n case action\n when '1' then range, dimensions = user_range_and_dimensions\n when '2' then range, dimensions = random_range_and_dimensions\n else exit!\n end\n\n range, dimensions = random_range_and_dimensions\n @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)\n output_matrix\nrescue Spiralizer::InvalidInput => e\n say \"\\nUh oh! #{e.message}\"\nensure\n say \"\\nexiting...\"\n exit!\nend\n```\n\nYou should be getting the hang of how to use `Thor`. Our class is need of some cleanup, but with some refactoring we end up with something like this:\n\n\n```\nrequire 'thor'\nrequire 'spiralizer'\n\nclass Spiralizer::CLI < Thor\n MATRICES = [\n ['A-L', '4x3'],\n ['M-X', '3x4'],\n ['1-8', '2x4'],\n ['1-144', '12x12'],\n ['C-J', '4x2'],\n ['A-Z', '13x2']\n ].freeze\n\n attr_reader :action, :matrix\n\n desc 'go', 'starts a prompt that brings users to spiral heaven'\n def go\n clear_screen!\n intro_prompt\n build_matrix\n output_matrix\n action_prompt\n spiralize!\n rescue Spiralizer::InvalidInput => e\n say \"\\nUh oh! #{e.message}\"\n ensure\n quit_softly\n end\n\n no_commands do\n def intro_prompt\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n @action = ask '> '\n end\n\n def build_matrix\n say \"\\n\"\n\n case action\n when '1' then range, dimensions = user_range_and_dimensions\n when '2' then range, dimensions = random_range_and_dimensions\n else quit_softly\n end\n\n @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)\n end\n\n def user_range_and_dimensions\n range_response = ask(\"Please provide range. Acceptable format: (A-L) or (1-6)\\n> \")\n values = range_response.split('-')\n dimensions = ask(\"Please provide dimensions. Acceptable format: (4x3) or (3x2)\\n> \")\n return (values.first..values.last), dimensions\n end\n\n def random_range_and_dimensions\n say \"\\ngenerating...\"\n pause_for_effect\n range_response, dimensions = MATRICES[rand(6)]\n range = range_response.split('-')\n return (range.first..range.last), dimensions\n end\n\n def action_prompt\n say \"\\nWhat would you like to do with it? (choose a number)\\n 1 - Spiralize it\\n 2 - Look at it\"\n @action = ask '> '\n end\n\n def output_matrix\n say \"\\nHere is your M A T R I X\\n------------------------\"\n matrix.each { |inner| say inner.join(\"\\t\") }\n end\n\n def spiralize!\n say \"\\n\"\n say Spiralizer::Spiralize.new(matrix: matrix).perform if action == '1'\n end\n\n def clear_screen!\n return system 'cls' if Gem.win_platform?\n system 'clear'\n end\n\n def pause_for_effect\n sleep 0.5\n end\n\n def quit_softly\n say \"\\nexiting...\"\n pause_for_effect\n exit!\n end\n end\nend\n```\n\nNow, let's try it all out:\n\n```\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 1\n\nPlease provide range. Acceptable format: (A-L) or (1-6)\n> 1-6\nPlease provide dimensions. Acceptable format: (4x3) or (3x2)\n> 2x3\n\nHere is your M A T R I X\n------------------------\n1 2\n3 4\n5 6\n\nWhat would you like to do with it? (choose a number)\n 1 - Spiralize it\n 2 - Look at it\n> 1\n\n1 2 4 6 5 3\n\nexiting...\n```\n\nOur little gem is complete! There is a lot of cleanup we can and should do, but this is where this blog post ends.\nFeel free to add new functionality and experiment further with Thor. I've gone ahead and added a `Crisscross` class for\nexample in my <a href=\"https://github.com/david-pm/matrix-spiralizer\">repo</a>.\n\nOh! One more thing, if yoy run `bundle exec rake install` bundler will install this gem on your system as a global executable so you can then call it like this:\n```\n> spiralizer go\n```\n\nIf you ever need to uninstall it for some insane reason just run `gem uninstall spiralizer`.\n\nYou can find a finalized version of the code <a href=\"https://github.com/david-pm/matrix-spiralizer\">here</a>.\n\nWell, there you have it. We've built a cli gem from scratch using `Bundler` and `Thor`. I hope this post serves you well and until next time!\n\n\n### (づ。◕‿‿◕。)づ *:・゚✧ ✧゚・*:・゚✧ ✧゚・",
"json_metadata": "{\"tags\":[\"technology\",\"ruby\",\"thor\",\"programming\"],\"image\":[\"https://ae01.alicdn.com/kf/HTB1FjN5KFXXXXa6XFXXq6xXFXXXx/1-1-Scale-Full-Metal-Thor-Hammer-Mjolnir-1-1-Replica-Thor-Custom-Cosplay-Hammer-Collection.jpg_640x640.jpg\"],\"links\":[\"https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor\",\"https://github.com/erikhuda/thor\",\"https://github.com/david-pm/matrix-spiralizer\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor-part-two",
"title": "Create a command-line gem from scratch with Thor (part two)"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T07:17:48",
"trx_id": "7f9d967d5f2df769eec5c62c34679339a2ffca2a",
"trx_in_block": 30,
"virtual_op": 0
}davidpmpublished a new post: blockchain-proof-of-concept2018/06/03 07:16:51
davidpmpublished a new post: blockchain-proof-of-concept
2018/06/03 07:16:51
| author | davidpm |
| body |  If you are a programmer and have been hearing all of the fuss about `THE BLOCKCHAIN` but haven't yet looked into it, and were looking for a starting point, this is the post for you! Feel free to skip the buzzword section and get right to the code if you already understand what a blockchain data structure is ʕ•ᴥ•ʔ ### Buzzword *Blockchain* has become a buzzword that is often used in a way that only confuses newcomers. You'll find definitions in the wild like, "a blockchain is a digital ledger in which transactions made in a cryptocurrency are recorded chronologically and publicly." That is correct, but... do you know what it *is* yet? I didn't. Most often it is used to refer to some platform or decentralized application (dapp), but a blockchain is really just an immutable data structure. Tokens, coins, dapps, etc. are built using this data structure. This data structure is mainly used to distribute a ledger of transactions to every participant in the network. It is a series of records that are linked, or *chained*, together. What these records store is transaction data. The transactions are verifiable by anybody who has the data and/or the application to use that data, due to the way the data structure works. So, the word itself changes meaning based on context, whether it be the micro data structure or the macro network of decentralized ledgers. The data structure itself is the basic unit of all of this crypto-madness and that is the context of this post. ### Building blocks It helps me to flesh out abstract concepts by taking something apart and putting it back together. In doing so with a few repos that leverage a blockchain data structure, I thought it would prove useful to others if I made a simple one in Ruby. Hopefully, this post will help reify the concept of a blockchain through code. Let's create a `Block` (this will all be done in [Ruby](https://www.ruby-lang.org/en/), but you shouldn't have any problem following along regardless of your programming background): ``` class Block attr_reader :index, :parent, :created_at, :data, :hash def initialize(index:, data:, parent: '') @index = index.to_i @data = data @parent = parent.empty? ? '0' : parent @created_at = Time.now.to_s @hash = '' end end ``` A `Block` is just something that stores data and some essential metadata. It's really that simple. Our `Block` class contains some `data`, an `index`, a reference to a `parent` block, a `created_at` date, and a `hash`. These properties will all be instantiated whenever we create a new block object. A key component to these blocks, and something we've purposefully glossed over to this point, is their immutability. Every block gets a cryptographic hash applied to it. Every protocol has its own way of applying this hash, but we're not so interested in how it gets applied in our little proof-of-concept. We're just gonna use Ruby's standard [Digest](https://ruby-doc.org/stdlib-2.5.0/libdoc/digest/rdoc/Digest/SHA2.html) class and slap a hash onto a block when it gets instantiated. ``` class Block ... def initialize(index:, data:, parent: '') ... @hash = hashsum end private def hashsum hashable = "#{index.to_s} #{parent} #{created_at} #{data.to_s}" Digest::SHA2.new(256).hexdigest(hashable) end end ``` `hashsum` is just a private method that takes the index from the block currently being created, the parent hash, the created at date, and data and feed that to Ruby's `Digest::SHA2(256).hexdigest`. Trying it out yields results that look like this: ``` (main)> Block.new(index: 0, data: 'your mom') => #<Block:0x00007ff6c4cf1f80 @created_at="2018-05-28 17:33:03 -0700", @data="your mom", @hash="ae8f611206e621c3a50cdb695c58adda78b3432986ed0f8dd4e1246456c332a2", @index=0, @parent="0"> (main)> Block.new(index: 1, data: 'your dad', parent: _.hash) => #<Block:0x00007ff6c477c5f0 @created_at="2018-05-28 17:34:27 -0700", @data="your dad", @hash="981acd98146a22d724524df236fb57161dec013c9820d683d098d5c05880852e", @index=1, @parent="ae8f611206e621c3a50cdb695c58adda78b3432986ed0f8dd4e1246456c332a2"> ``` We can now create blocks! What's more our blocks know about each other. Notice that our second block stores a reference to its parent block by storing the parent's hash value. Now we just need a convenient wrapper to chain them altogether, as well as provide some accessors and validators. We'll do this by creating a new class `Blockchain` which will store a series of blocks: ``` class Blockchain def initialize @chain = [] end end ``` Simple enough, but we'll want an easy way to push new blocks into this chain. Let's add a method that will take arbitrary data for storage, calculate the position of the newly created block, create it and push it onto the blockchain. This isn't very [SOLID](https://en.wikipedia.org/wiki/SOLID) code but we're hurrying to finish our proof-of-concept (PoC) and can always refactor this later. ``` class Blockchain ... # not very SOLID but this just a PoC def add_block(data) chain << Block.new(index: chain.size, data: data, parent: last_hash) end def last_hash chain.last&.hash || '' end private attr_reader :chain end ``` Trying it out in `pry` works like a charm: ``` (main)> load 'blockchain.rb' => true (main)> bc = Blockchain.new => #<Blockchain:0x00007fe248878638 @chain=[]> (main)> bc.add_block({ start_over: true }) => [#<Block:0x00007fe248bc8770 @created_at="2018-05-28 17:51:44 -0700", @data={:start_over=>true}, @hash="a52c05c50d0a024acbb965f5d7f78fddcd54166b7154dfc487fd2f54d428a208", @index=0, @parent="0">] (main)> bc.add_block({ start_over: false }) => [#<Block:0x00007fe248bc8770 @created_at="2018-05-28 17:51:44 -0700", @data={:start_over=>true}, @hash="a52c05c50d0a024acbb965f5d7f78fddcd54166b7154dfc487fd2f54d428a208", @index=0, @parent="0">, #<Block:0x00007fe245635950 @created_at="2018-05-28 17:51:51 -0700", @data={:start_over=>false}, @hash="a159d1e698518af46f82938c2f7e830a80d16e527a1e667fdd298417004f525e", @index=1, @parent="a52c05c50d0a024acbb965f5d7f78fddcd54166b7154dfc487fd2f54d428a208">] ``` Et voila! The meat of our blockchain is now implemented! No blockchain would be complete without the ability to validate its integrity. So, let's add a quick valid? method which will loop thru all the blocks on the chain and make sure the hash values match and that each child has the proper parent. If this method ever fails, our structure's immutability has been compromised and is a very bad thing. ``` class Blockchain ... def valid? return true if chain.size <= 1 link = chain.each # coerce to an enumerator loop do # loops rescue StopIteration errors previous = link.next return false if previous.hash != link.next.parent end return true end def last chain.last end def size chain.size end ... end ``` With this simple validator in place we really only have some a few convenient frills to add. I'd like a welcome message and a base block to be instantiated whenever a new blockchain object is created. Let's take care of that now to wrap this up: ``` class Blockchain def initialize welcome @chain = [] add_first_block! end ... private ... def add_first_block! chain << Block.new(index: 0, data: 'Genenis Block') end def welcome puts <<~BLCKCHN Welcome, to Blockchain! To add to the chain, call `#add_block` and pass in a data\n\n BLCKCHN end end ``` We have created two private methods that will be invoked whenever a new blockchain is created (in the real world, your blockchain should only every be created one time). We'll print a welcome message to the screen and create the first block of the chain. There you have it, a simple but functional blockchain data structure! The [finished product](https://gist.github.com/david-pm/4791fbb0aa2a0cf4efce9adb044352d3) is well under one hundred lines of code. This is by no means ready for any kind of production application. You will want additional safe guards against tampering, and robust authentication etc. but I hope this has been a helpful introduction to how this data structure works. With thousands of applications already in use the blockchain isn't going away, but as you can see it is really nothing to be afraid of and isn't even terribly novel. Until next time! ### ༼ ºل͟º ༼ ºل͟º ༼ ºل͟º ༽ ºل͟º ༽ ºل͟º ༽ |
| json metadata | {"tags":["blockchain","technology","ruby","crypto","programming"],"image":["https://themerkle.com/wp-content/uploads/2017/08/shutterstock_679065259.jpg"],"links":["https://www.ruby-lang.org/en/","https://ruby-doc.org/stdlib-2.5.0/libdoc/digest/rdoc/Digest/SHA2.html","https://en.wikipedia.org/wiki/SOLID","https://gist.github.com/david-pm/4791fbb0aa2a0cf4efce9adb044352d3"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | blockchain |
| permlink | blockchain-proof-of-concept |
| title | Blockchain Proof of Concept |
| Transaction Info | Block #22992156/Trx 87b5cf3ad14463288b130d51b077043019b10ccb |
View Raw JSON Data
{
"block": 22992156,
"op": [
"comment",
{
"author": "davidpm",
"body": "\n\nIf you are a programmer and have been hearing all of the fuss about `THE BLOCKCHAIN` but haven't yet looked into it, and were looking for a starting point, this is the post for you! Feel free to skip the buzzword section and get right to the code if you already understand what a blockchain data structure is ʕ•ᴥ•ʔ\n\n### Buzzword\n\n*Blockchain* has become a buzzword that is often used in a way that only confuses newcomers. You'll find definitions in the wild like, \"a blockchain is a digital ledger in which transactions made in a cryptocurrency are recorded chronologically and publicly.\" That is correct, but... do you know what it *is* yet? I didn't.\n\nMost often it is used to refer to some platform or decentralized application (dapp), but a blockchain is really just an immutable data structure. Tokens, coins, dapps, etc. are built using this data structure. This data structure is mainly used to distribute a ledger of transactions to every participant in the network. It is a series of records that are linked, or *chained*, together. What these records store is transaction data. The transactions are verifiable by anybody who has the data and/or the application to use that data, due to the way the data structure works.\n\nSo, the word itself changes meaning based on context, whether it be the micro data structure or the macro network of decentralized ledgers. The data structure itself is the basic unit of all of this crypto-madness and that is the context of this post.\n\n### Building blocks\n\nIt helps me to flesh out abstract concepts by taking something apart and putting it back together. In doing so with a few repos that leverage a blockchain data structure, I thought it would prove useful to others if I made a simple one in Ruby. Hopefully, this post will help reify the concept of a blockchain through code. \n\nLet's create a `Block` (this will all be done in [Ruby](https://www.ruby-lang.org/en/), but you shouldn't have any problem following along regardless of your programming background):\n\n```\nclass Block\n attr_reader :index, :parent, :created_at, :data, :hash\n\n def initialize(index:, data:, parent: '')\n @index = index.to_i\n @data = data\n @parent = parent.empty? ? '0' : parent\n @created_at = Time.now.to_s\n @hash = ''\n end\nend\n\n```\n\nA `Block` is just something that stores data and some essential metadata. It's really that simple. Our `Block` class contains some `data`, an `index`, a reference to a `parent` block, a `created_at` date, and a `hash`. These properties will all be instantiated whenever we create a new block object.\n\nA key component to these blocks, and something we've purposefully glossed over to this point, is their immutability. Every block gets a cryptographic hash applied to it. Every protocol has its own way of applying this hash, but we're not so interested in how it gets applied in our little proof-of-concept. We're just gonna use Ruby's standard [Digest](https://ruby-doc.org/stdlib-2.5.0/libdoc/digest/rdoc/Digest/SHA2.html) class and slap a hash onto a block when it gets instantiated.\n\n```\nclass Block\n ...\n def initialize(index:, data:, parent: '')\n ...\n @hash = hashsum\n end\n\n private\n\n def hashsum\n hashable = \"#{index.to_s} #{parent} #{created_at} #{data.to_s}\"\n Digest::SHA2.new(256).hexdigest(hashable)\n end\nend\n```\n\n`hashsum` is just a private method that takes the index from the block currently being created, the parent hash, the created at date, and data and feed that to Ruby's `Digest::SHA2(256).hexdigest`. Trying it out yields results that look like this:\n\n```\n(main)> Block.new(index: 0, data: 'your mom')\n=> #<Block:0x00007ff6c4cf1f80\n @created_at=\"2018-05-28 17:33:03 -0700\",\n @data=\"your mom\",\n @hash=\"ae8f611206e621c3a50cdb695c58adda78b3432986ed0f8dd4e1246456c332a2\",\n @index=0,\n @parent=\"0\">\n(main)> Block.new(index: 1, data: 'your dad', parent: _.hash)\n=> #<Block:0x00007ff6c477c5f0\n @created_at=\"2018-05-28 17:34:27 -0700\",\n @data=\"your dad\",\n @hash=\"981acd98146a22d724524df236fb57161dec013c9820d683d098d5c05880852e\",\n @index=1,\n @parent=\"ae8f611206e621c3a50cdb695c58adda78b3432986ed0f8dd4e1246456c332a2\">\n```\n\nWe can now create blocks! What's more our blocks know about each other. Notice that our second block stores a reference to its parent block by storing the parent's hash value. Now we just need a convenient wrapper to chain them altogether, as well as provide some accessors and validators. \n\nWe'll do this by creating a new class `Blockchain` which will store a series of blocks:\n\n```\nclass Blockchain\n def initialize\n @chain = []\n end \nend\n```\n\nSimple enough, but we'll want an easy way to push new blocks into this chain. Let's add a method that will take arbitrary data for storage, calculate the position of the newly created block, create it and push it onto the blockchain. This isn't very [SOLID](https://en.wikipedia.org/wiki/SOLID) code but we're hurrying to finish our proof-of-concept (PoC) and can always refactor this later.\n\n\n```\nclass Blockchain\n ...\n # not very SOLID but this just a PoC\n def add_block(data)\n chain << Block.new(index: chain.size, data: data, parent: last_hash)\n end\n\n def last_hash\n chain.last&.hash || ''\n end\n\n private\n\n attr_reader :chain\nend\n```\n\nTrying it out in `pry` works like a charm:\n\n\n```\n(main)> load 'blockchain.rb'\n=> true\n(main)> bc = Blockchain.new\n=> #<Blockchain:0x00007fe248878638 @chain=[]>\n(main)> bc.add_block({ start_over: true })\n=> [#<Block:0x00007fe248bc8770\n @created_at=\"2018-05-28 17:51:44 -0700\",\n @data={:start_over=>true},\n @hash=\"a52c05c50d0a024acbb965f5d7f78fddcd54166b7154dfc487fd2f54d428a208\",\n @index=0,\n @parent=\"0\">]\n(main)> bc.add_block({ start_over: false })\n=> [#<Block:0x00007fe248bc8770\n @created_at=\"2018-05-28 17:51:44 -0700\",\n @data={:start_over=>true},\n @hash=\"a52c05c50d0a024acbb965f5d7f78fddcd54166b7154dfc487fd2f54d428a208\",\n @index=0,\n @parent=\"0\">,\n #<Block:0x00007fe245635950\n @created_at=\"2018-05-28 17:51:51 -0700\",\n @data={:start_over=>false},\n @hash=\"a159d1e698518af46f82938c2f7e830a80d16e527a1e667fdd298417004f525e\",\n @index=1,\n @parent=\"a52c05c50d0a024acbb965f5d7f78fddcd54166b7154dfc487fd2f54d428a208\">]\n\n```\n\nEt voila! The meat of our blockchain is now implemented! No blockchain would be complete without the ability to validate its integrity. So, let's add a quick valid? method which will loop thru all the blocks on the chain and make sure the hash values match and that each child has the proper parent. If this method ever fails, our structure's immutability has been compromised and is a very bad thing.\n\n```\nclass Blockchain\n ...\n def valid?\n return true if chain.size <= 1\n link = chain.each # coerce to an enumerator\n loop do # loops rescue StopIteration errors\n previous = link.next\n return false if previous.hash != link.next.parent\n end\n return true\n end\n\n def last\n chain.last\n end\n\n def size\n chain.size\n end\n...\nend\n```\n\nWith this simple validator in place we really only have some a few convenient frills to add. I'd like a welcome message and a base block to be instantiated whenever a new blockchain object is created. Let's take care of that now to wrap this up:\n\n```\nclass Blockchain\n def initialize\n welcome\n @chain = []\n add_first_block!\n end\n\n ...\n private\n ...\n def add_first_block!\n chain << Block.new(index: 0, data: 'Genenis Block')\n end\n\n def welcome\n puts <<~BLCKCHN\n Welcome, to Blockchain!\n To add to the chain, call `#add_block` and pass in a data\\n\\n\n BLCKCHN\n end\nend\n```\n\nWe have created two private methods that will be invoked whenever a new blockchain is created (in the real world, your blockchain should only every be created one time). We'll print a welcome message to the screen and create the first block of the chain. There you have it, a simple but functional blockchain data structure! The [finished product](https://gist.github.com/david-pm/4791fbb0aa2a0cf4efce9adb044352d3) is well under one hundred lines of code.\n\nThis is by no means ready for any kind of production application. You will want additional safe guards against tampering, and robust authentication etc. but I hope this has been a helpful introduction to how this data structure works. With thousands of applications already in use the blockchain isn't going away, but as you can see it is really nothing to be afraid of and isn't even terribly novel. Until next time!\n\n### ༼ ºل͟º ༼ ºل͟º ༼ ºل͟º ༽ ºل͟º ༽ ºل͟º ༽",
"json_metadata": "{\"tags\":[\"blockchain\",\"technology\",\"ruby\",\"crypto\",\"programming\"],\"image\":[\"https://themerkle.com/wp-content/uploads/2017/08/shutterstock_679065259.jpg\"],\"links\":[\"https://www.ruby-lang.org/en/\",\"https://ruby-doc.org/stdlib-2.5.0/libdoc/digest/rdoc/Digest/SHA2.html\",\"https://en.wikipedia.org/wiki/SOLID\",\"https://gist.github.com/david-pm/4791fbb0aa2a0cf4efce9adb044352d3\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "blockchain",
"permlink": "blockchain-proof-of-concept",
"title": "Blockchain Proof of Concept"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T07:16:51",
"trx_id": "87b5cf3ad14463288b130d51b077043019b10ccb",
"trx_in_block": 34,
"virtual_op": 0
}davidpmupvoted (100.00%) @armacener / daily-dose-of-seinfeld-03-06-20182018/06/03 07:15:30
davidpmupvoted (100.00%) @armacener / daily-dose-of-seinfeld-03-06-2018
2018/06/03 07:15:30
| author | armacener |
| permlink | daily-dose-of-seinfeld-03-06-2018 |
| voter | davidpm |
| weight | 10000 (100.00%) |
| Transaction Info | Block #22992129/Trx 2331e3a6a636642dc53d558548e8b044058bded0 |
View Raw JSON Data
{
"block": 22992129,
"op": [
"vote",
{
"author": "armacener",
"permlink": "daily-dose-of-seinfeld-03-06-2018",
"voter": "davidpm",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T07:15:30",
"trx_id": "2331e3a6a636642dc53d558548e8b044058bded0",
"trx_in_block": 7,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor2018/06/03 06:59:09
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor
2018/06/03 06:59:09
| author | davidpm |
| body | @@ -205,16 +205,17 @@ here%3C/a%3E +. %0A%0A---%0A%0AL |
| json metadata | {"tags":["ruby","gem","thor","cli","matrix"],"image":["https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif","https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png"],"links":["https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two","https://bundler.io/","https://git-scm.com/","http://rspec.info/","https://github.com/ruby/rake","https://github.com/erikhuda/thor","https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58","https://en.wikipedia.org/wiki/Ouroboros"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| title | Create a command-line gem from scratch with Thor |
| Transaction Info | Block #22991802/Trx 0de75e7e45d6812f3fe9196e5a65b1d4ce7b3461 |
View Raw JSON Data
{
"block": 22991802,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -205,16 +205,17 @@\n here%3C/a%3E\n+.\n %0A%0A---%0A%0AL\n",
"json_metadata": "{\"tags\":[\"ruby\",\"gem\",\"thor\",\"cli\",\"matrix\"],\"image\":[\"https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif\",\"https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png\"],\"links\":[\"https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two\",\"https://bundler.io/\",\"https://git-scm.com/\",\"http://rspec.info/\",\"https://github.com/ruby/rake\",\"https://github.com/erikhuda/thor\",\"https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58\",\"https://en.wikipedia.org/wiki/Ouroboros\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"title": "Create a command-line gem from scratch with Thor"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:59:09",
"trx_id": "0de75e7e45d6812f3fe9196e5a65b1d4ce7b3461",
"trx_in_block": 13,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor2018/06/03 06:58:51
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor
2018/06/03 06:58:51
| author | davidpm |
| body | @@ -95,16 +95,17 @@ o %3Ca hre +f ='https: |
| json metadata | {"tags":["ruby","gem","thor","cli","matrix"],"image":["https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif","https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png"],"links":["https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two","https://bundler.io/","https://git-scm.com/","http://rspec.info/","https://github.com/ruby/rake","https://github.com/erikhuda/thor","https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58","https://en.wikipedia.org/wiki/Ouroboros"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| title | Create a command-line gem from scratch with Thor |
| Transaction Info | Block #22991796/Trx 108f2e890fdbffa2aa0e143c7f104e4866f5679b |
View Raw JSON Data
{
"block": 22991796,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -95,16 +95,17 @@\n o %3Ca hre\n+f\n ='https:\n",
"json_metadata": "{\"tags\":[\"ruby\",\"gem\",\"thor\",\"cli\",\"matrix\"],\"image\":[\"https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif\",\"https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png\"],\"links\":[\"https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two\",\"https://bundler.io/\",\"https://git-scm.com/\",\"http://rspec.info/\",\"https://github.com/ruby/rake\",\"https://github.com/erikhuda/thor\",\"https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58\",\"https://en.wikipedia.org/wiki/Ouroboros\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"title": "Create a command-line gem from scratch with Thor"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:58:51",
"trx_id": "108f2e890fdbffa2aa0e143c7f104e4866f5679b",
"trx_in_block": 31,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor2018/06/03 06:57:42
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor
2018/06/03 06:57:42
| author | davidpm |
| body | @@ -79,16 +79,141 @@ scratch. + Part two %3Ca hre='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two'%3Efound here%3C/a%3E %0A%0A---%0A%0AL |
| json metadata | {"tags":["ruby","gem","thor","cli","matrix"],"image":["https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif","https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png"],"links":["https://bundler.io/","https://git-scm.com/","http://rspec.info/","https://github.com/ruby/rake","https://github.com/erikhuda/thor","https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58","https://en.wikipedia.org/wiki/Ouroboros"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| title | Create a command-line gem from scratch with Thor |
| Transaction Info | Block #22991773/Trx e35eb45ba5b804d83e03f009f5e275dee7c0d4bd |
View Raw JSON Data
{
"block": 22991773,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -79,16 +79,141 @@\n scratch.\n+ Part two %3Ca hre='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor-part-two'%3Efound here%3C/a%3E\n %0A%0A---%0A%0AL\n",
"json_metadata": "{\"tags\":[\"ruby\",\"gem\",\"thor\",\"cli\",\"matrix\"],\"image\":[\"https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif\",\"https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png\"],\"links\":[\"https://bundler.io/\",\"https://git-scm.com/\",\"http://rspec.info/\",\"https://github.com/ruby/rake\",\"https://github.com/erikhuda/thor\",\"https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58\",\"https://en.wikipedia.org/wiki/Ouroboros\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"title": "Create a command-line gem from scratch with Thor"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:57:42",
"trx_id": "e35eb45ba5b804d83e03f009f5e275dee7c0d4bd",
"trx_in_block": 6,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor-part-two2018/06/03 06:55:54
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor-part-two
2018/06/03 06:55:54
| author | davidpm |
| body | This is the last in a two-part post on creating a Ruby cli gem from scratch. Part one <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>found here</a>. --- Picking up from our <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>last post</a>, we want to start working on our user interface. In this case we want a cli that looks something like this: ``` #-> what would you like to do? 1 - Build a matrix 2 - Generate a random matrix #-> now that we have a matrix, would you like to spiralize it? 1 - Spiralize it 2 - Stare at it ``` ### Thor  We're going to use <a href='https://github.com/erikhuda/thor'>Thor</a>, which is a toolkit for building command-line tools and applications. In fact, `Bundler` itself was written using `Thor`. Let's add it to the bottom of our `gemspec` and re-install: ``` # spiralizer.gemspec spec.add_dependency "thor" --- # from cmd line: > bundle install ``` Now, we just need to require it in and start using it! ``` require 'thor' ... ``` But, this is also a good time to start separating our code a bit, so we don't let things get to messy. So, let's create a file called `lib/spiralizer/cli.rb`, and move our `require` statement into this file. Then we will stub out a class under the `Spiralizer` namespace and inherit from `Thor`: ``` require 'thor' require 'spiralizer' class Spiralizer::CLI < Thor end ``` Back in our main module we will need to require in this new file: ``` require "spiralizer/version" require "spiralizer/cli" module Spiralizer ... end ``` Our cli is gem that users can install on their systems. We want them to have an executable that will take a simple command to spin up prompt we defined earlier: Say the user enters `spiralizer go` for example. We would want output similar to what we envisioned above: ``` $ >spiralizer go #-> what would you like to do? 1 - Build a matrix 2 - Generate a random matrix ``` Thor gives us some easy-to-use tooling to get this thing going in no time. We'll get started by defining our `go` method, and use the `desc` macro annotation get some magic documentation: ``` require 'thor' require 'spiralizer' class Spiralizer::CLI < Thor desc 'go', 'starts a prompt that brings users to spiral heaven' def go say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" action = ask '> ' say "\nyou picked #{action}" exit! end end ``` `Thor` provides some intuitive methods like `say` to output something to the user, and `ask` to prompt the user for input. They work just as expected. Notice that you can assign the result of `ask` to a variable. To start playing around with it, we just need to create a new directory called `exe` (alternatively, we could have scaffolded our gem with a `--exe` option but here we are). It should be a sibling directory of `lib` and `bin`. `cd exe && touch spiralizer` to create a file. Then, give it executable permissions: `> chmod +x exe/spiralizer`, and dump this inside: ``` #!/usr/bin/env ruby require 'spiralizer/cli' Spiralizer::CLI.start ``` And let's give 'er a whirl: ``` > bundle exec exe/spiralizer Commands: spiralizer go # starts a prompt that brings users to spiral heaven spiralizer help [COMMAND] # Describe available commands or one specific command ``` Notice the helpful output Thor gives us. We have two commands available: `go` and `help`. Let's try them out: ``` > bundle exec exe/spiralizer help Commands: spiralizer go # starts a prompt that brings users to spiral heaven spiralizer help [COMMAND] # Describe available commands or one specific command > bundle exec exe/spiralizer go What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 1 you picked 1 ``` So, `help` is the default action, and we can see our new `go` command is registered properly. Right now our prompt gives us two options, let's add functionality for our second option. When a user enters `'2'` we want to spit out a random matrix. We can do a quick and dirty version of this and just hard code some matrix range/dimension pairs that we will feed to our matrix factory. Let's make a constant called `MATRICES` that itself is a matrix: ``` MATRICES = [ ['A-L', '4x3'], ['M-X', '3x4'], ['1-8', '2x4'], ['1-144', '12x12'], ['C-J', '4x2'], ['A-Z', '13x2'] ].freeze ``` We now need a semi-random way to pick one of these: `range_response, dimensions = MATRICES[rand(6)]`. This will give us a range value as a string like 'C-j' and dimensions like `4x2`. We'll then split that range string up, so we can feed it into our factory: ``` range = range_response.split('-') matrix = Spiralizer::Matrix.the_matrix(range: (range.first..range.last), dimensions: dimensions) ``` `Thor` wants you to put helper methods for your class in a `no_commands` block, so let's create one of those and put all this together so its somewhat presentable. Here's what we have so far: ``` class Spiralizer::CLI < Thor attr_reader :matrix MATRICES = [ ['A-L', '4x3'], ['M-X', '3x4'], ['1-8', '2x4'], ['1-144', '12x12'], ['C-J', '4x2'], ['A-Z', '13x2'] ].freeze desc 'go', 'starts a prompt that brings users to spiral heaven' def go say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" action = ask '> ' say "\n" range, dimensions = random_range_and_dimensions @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions) output_matrix end no_commands do def random_range_and_dimensions say "\ngenerating..." sleep 1 range_response, dimensions = MATRICES[rand(6)] range = range_response.split('-') return (range.first..range.last), dimensions end def output_matrix say "\nHere is your M A T R I X\n------------------------" matrix.each { |inner| say inner.join("\t") } exit! end end end ``` You'll notice that any input we give the prompt at this point will just generate a random matrix from our list. We're making progress! ``` > bundle exec exe/spiralizer go What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 2 generating... Here is your M A T R I X ------------------------ 1 2 3 4 5 6 7 8 ``` Next, let's hook up a follow up prompt that asks us if we want to spiralize the matrix. Update your `output_matrix` method: ``` def output_matrix say "\nHere is your M A T R I X\n------------------------" matrix.each { |arr| say arr.join("\t") } say "\nWould you like to spiralize it? (choose a number)\n 1 - Yes\n 2 - No" action = ask '> ' say "\n" if action == '1' say Spiralizer::Spiralize.new(matrix: matrix).perform end exit! end ``` We are now prompted for more input after the matrix has been generated! Pretty neat. ``` > bundle exec exe/spiralizer go What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 2 generating... Here is your M A T R I X ------------------------ M N O P Q R S T U V W X Would you like to spiralize it? (choose a number) 1 - Yes 2 - No > 1 m n o r u x w v s p q t ``` This is looking good but we still need to handle our first option to allow users to create their own matrix. Let's add a method to handle that input now: ``` def user_range_and_dimensions range_response = ask("Please provide range. Acceptable format: (A-L) or (1-6)\n> ") values = range_response.split('-') dimensions = ask("Please provide dimensions. Acceptable format: (4x3) or (3x2)\n> ") return (values.first..values.last), dimensions end ``` We want this to trigger when the user chooses `'1'` from our introduction prompt so we need to re-work our `go` method: ``` def go say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" action = ask '> ' say "\n" case action when '1' then range, dimensions = user_range_and_dimensions when '2' then range, dimensions = random_range_and_dimensions else exit! end range, dimensions = random_range_and_dimensions @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions) output_matrix rescue Spiralizer::InvalidInput => e say "\nUh oh! #{e.message}" ensure say "\nexiting..." exit! end ``` You should be getting the hang of how to use `Thor`. Our class is need of some cleanup, but with some refactoring we end up with something like this: ``` require 'thor' require 'spiralizer' class Spiralizer::CLI < Thor MATRICES = [ ['A-L', '4x3'], ['M-X', '3x4'], ['1-8', '2x4'], ['1-144', '12x12'], ['C-J', '4x2'], ['A-Z', '13x2'] ].freeze attr_reader :action, :matrix desc 'go', 'starts a prompt that brings users to spiral heaven' def go clear_screen! intro_prompt build_matrix output_matrix action_prompt spiralize! rescue Spiralizer::InvalidInput => e say "\nUh oh! #{e.message}" ensure quit_softly end no_commands do def intro_prompt say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix" @action = ask '> ' end def build_matrix say "\n" case action when '1' then range, dimensions = user_range_and_dimensions when '2' then range, dimensions = random_range_and_dimensions else quit_softly end @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions) end def user_range_and_dimensions range_response = ask("Please provide range. Acceptable format: (A-L) or (1-6)\n> ") values = range_response.split('-') dimensions = ask("Please provide dimensions. Acceptable format: (4x3) or (3x2)\n> ") return (values.first..values.last), dimensions end def random_range_and_dimensions say "\ngenerating..." pause_for_effect range_response, dimensions = MATRICES[rand(6)] range = range_response.split('-') return (range.first..range.last), dimensions end def action_prompt say "\nWhat would you like to do with it? (choose a number)\n 1 - Spiralize it\n 2 - Look at it" @action = ask '> ' end def output_matrix say "\nHere is your M A T R I X\n------------------------" matrix.each { |inner| say inner.join("\t") } end def spiralize! say "\n" say Spiralizer::Spiralize.new(matrix: matrix).perform if action == '1' end def clear_screen! return system 'cls' if Gem.win_platform? system 'clear' end def pause_for_effect sleep 0.5 end def quit_softly say "\nexiting..." pause_for_effect exit! end end end ``` Now, let's try it all out: ``` What would you like to do? (choose a number) 1 - Build a matrix 2 - Generate a random matrix > 1 Please provide range. Acceptable format: (A-L) or (1-6) > 1-6 Please provide dimensions. Acceptable format: (4x3) or (3x2) > 2x3 Here is your M A T R I X ------------------------ 1 2 3 4 5 6 What would you like to do with it? (choose a number) 1 - Spiralize it 2 - Look at it > 1 1 2 4 6 5 3 exiting... ``` Our little gem is complete! There is a lot of cleanup we can and should do, but this is where this blog post ends. Feel free to add new functionality and experiment further with Thor. I've gone ahead and added a `Crisscross` class for example in my <a href="https://github.com/david-pm/matrix-spiralizer">repo</a>. Oh! One more thing, if yoy run `bundle exec rake install` bundler will install this gem on your system as a global executable so you can then call it like this: ``` > spiralizer go ``` If you ever need to uninstall it for some insane reason just run `gem uninstall spiralizer`. You can find a finalized version of the code <a href="https://github.com/david-pm/matrix-spiralizer">here</a>. Well, there you have it. We've built a cli gem from scratch using `Bundler` and `Thor`. I hope this post serves you well and until next time! ### (づ。◕‿‿◕。)づ *:・゚✧ ✧゚・*:・゚✧ ✧゚・ |
| json metadata | {"tags":["ruby","thor","gem","cli","programming"],"image":["https://ae01.alicdn.com/kf/HTB1FjN5KFXXXXa6XFXXq6xXFXXXx/1-1-Scale-Full-Metal-Thor-Hammer-Mjolnir-1-1-Replica-Thor-Custom-Cosplay-Hammer-Collection.jpg_640x640.jpg"],"links":["https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor","https://github.com/erikhuda/thor","https://github.com/david-pm/matrix-spiralizer"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor-part-two |
| title | Create a command-line gem from scratch with Thor (part two) |
| Transaction Info | Block #22991737/Trx 492811ff976988cb2ca6b2f94a3b463e9e7ab833 |
View Raw JSON Data
{
"block": 22991737,
"op": [
"comment",
{
"author": "davidpm",
"body": "This is the last in a two-part post on creating a Ruby cli gem from scratch. Part one <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>found here</a>.\n\n---\n\nPicking up from our <a href='https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor'>last post</a>, we want to start working on our user interface. In this case we want a cli that looks something like this:\n```\n#-> what would you like to do?\n 1 - Build a matrix\n 2 - Generate a random matrix\n\n#-> now that we have a matrix, would you like to spiralize it?\n 1 - Spiralize it\n 2 - Stare at it\n```\n### Thor\n\n\nWe're going to use <a href='https://github.com/erikhuda/thor'>Thor</a>, which is a toolkit for building command-line tools and applications. In fact, `Bundler` itself was written using `Thor`. Let's add it to the bottom of our `gemspec` and re-install:\n\n```\n# spiralizer.gemspec\nspec.add_dependency \"thor\"\n---\n# from cmd line:\n> bundle install\n```\n\nNow, we just need to require it in and start using it!\n\n```\nrequire 'thor'\n...\n```\n\nBut, this is also a good time to start separating our code a bit, so we don't let things get to messy. So, let's create a file called `lib/spiralizer/cli.rb`, and move our `require` statement into this file. Then we will stub out a class under the `Spiralizer` namespace and inherit from `Thor`:\n\n```\nrequire 'thor'\nrequire 'spiralizer'\n\nclass Spiralizer::CLI < Thor\nend\n```\n\nBack in our main module we will need to require in this new file:\n\n```\nrequire \"spiralizer/version\"\nrequire \"spiralizer/cli\"\n\nmodule Spiralizer\n...\nend\n```\n\nOur cli is gem that users can install on their systems. We want them to have an executable that will take a simple command to spin up prompt we defined earlier:\n\nSay the user enters `spiralizer go` for example. We would want output similar to what we envisioned above:\n```\n$ >spiralizer go\n\n#-> what would you like to do?\n 1 - Build a matrix\n 2 - Generate a random matrix\n```\n\nThor gives us some easy-to-use tooling to get this thing going in no time. We'll get started by defining our `go` method, and use the `desc` macro annotation get some magic documentation:\n```\nrequire 'thor'\nrequire 'spiralizer'\n\nclass Spiralizer::CLI < Thor\n desc 'go', 'starts a prompt that brings users to spiral heaven'\n def go\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n action = ask '> '\n say \"\\nyou picked #{action}\"\n exit!\n end\nend\n```\n`Thor` provides some intuitive methods like `say` to output something to the user, and `ask` to prompt the user for input. They work just as expected. Notice that you can assign the result of `ask` to a variable.\n\nTo start playing around with it, we just need to create a new directory called `exe` (alternatively, we could have scaffolded our gem with a `--exe` option but here we are). It should be a sibling directory of `lib` and `bin`. `cd exe && touch spiralizer` to create a file. Then, give it executable permissions: `> chmod +x exe/spiralizer`, and dump this inside:\n```\n#!/usr/bin/env ruby\n\nrequire 'spiralizer/cli'\nSpiralizer::CLI.start\n```\n\n And let's give 'er a whirl:\n\n```\n> bundle exec exe/spiralizer\n\nCommands:\n spiralizer go # starts a prompt that brings users to spiral heaven\n spiralizer help [COMMAND] # Describe available commands or one specific command\n```\n\nNotice the helpful output Thor gives us. We have two commands available: `go` and `help`. Let's try them out:\n\n```\n> bundle exec exe/spiralizer help\n\nCommands:\n spiralizer go # starts a prompt that brings users to spiral heaven\n spiralizer help [COMMAND] # Describe available commands or one specific command\n\n> bundle exec exe/spiralizer go\n\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 1\n\nyou picked 1\n```\nSo, `help` is the default action, and we can see our new `go` command is registered properly. Right now our prompt gives us two options, let's add functionality for our second option. When a user enters `'2'` we want to spit out a random matrix. We can do a quick and dirty version of this and just hard code some matrix range/dimension pairs that we will feed to our matrix factory. Let's make a constant called `MATRICES` that itself is a matrix:\n\n```\nMATRICES = [\n ['A-L', '4x3'],\n ['M-X', '3x4'],\n ['1-8', '2x4'],\n ['1-144', '12x12'],\n ['C-J', '4x2'],\n ['A-Z', '13x2']\n].freeze\n```\n\nWe now need a semi-random way to pick one of these: `range_response, dimensions = MATRICES[rand(6)]`. This will give us a range value as a string like 'C-j' and dimensions like `4x2`. We'll then split that range string up, so we can feed it into our factory:\n```\nrange = range_response.split('-')\nmatrix = Spiralizer::Matrix.the_matrix(range: (range.first..range.last), dimensions: dimensions)\n```\n\n`Thor` wants you to put helper methods for your class in a `no_commands` block, so let's create one of those\nand put all this together so its somewhat presentable. Here's what we have so far:\n\n```\nclass Spiralizer::CLI < Thor\n attr_reader :matrix\n\n MATRICES = [\n ['A-L', '4x3'],\n ['M-X', '3x4'],\n ['1-8', '2x4'],\n ['1-144', '12x12'],\n ['C-J', '4x2'],\n ['A-Z', '13x2']\n ].freeze\n\n desc 'go', 'starts a prompt that brings users to spiral heaven'\n def go\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n action = ask '> '\n say \"\\n\"\n\n range, dimensions = random_range_and_dimensions\n @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)\n output_matrix\n end\n\n no_commands do\n def random_range_and_dimensions\n say \"\\ngenerating...\"\n sleep 1\n range_response, dimensions = MATRICES[rand(6)]\n range = range_response.split('-')\n return (range.first..range.last), dimensions\n end\n\n def output_matrix\n say \"\\nHere is your M A T R I X\\n------------------------\"\n matrix.each { |inner| say inner.join(\"\\t\") }\n exit!\n end\n end\nend\n```\n\nYou'll notice that any input we give the prompt at this point will just generate a random matrix from our list. We're making progress!\n\n```\n> bundle exec exe/spiralizer go\n\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 2\n\n\ngenerating...\n\nHere is your M A T R I X\n------------------------\n1 2\n3 4\n5 6\n7 8\n```\n\nNext, let's hook up a follow up prompt that asks us if we want to spiralize the matrix. Update your `output_matrix` method:\n\n```\ndef output_matrix\n say \"\\nHere is your M A T R I X\\n------------------------\"\n matrix.each { |arr| say arr.join(\"\\t\") }\n\n say \"\\nWould you like to spiralize it? (choose a number)\\n 1 - Yes\\n 2 - No\"\n action = ask '> '\n say \"\\n\"\n if action == '1'\n say Spiralizer::Spiralize.new(matrix: matrix).perform\n end\n\n exit!\nend\n```\n\nWe are now prompted for more input after the matrix has been generated! Pretty neat.\n\n```\n> bundle exec exe/spiralizer go\n\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 2\n\n\ngenerating...\n\nHere is your M A T R I X\n------------------------\nM N O\nP Q R\nS T U\nV W X\n\nWould you like to spiralize it? (choose a number)\n 1 - Yes\n 2 - No\n> 1\n\nm n o r u x w v s p q t\n```\n\nThis is looking good but we still need to handle our first option to allow users to create their own matrix. Let's add a method to handle that input now:\n\n```\n def user_range_and_dimensions\n range_response = ask(\"Please provide range. Acceptable format: (A-L) or (1-6)\\n> \")\n values = range_response.split('-')\n dimensions = ask(\"Please provide dimensions. Acceptable format: (4x3) or (3x2)\\n> \")\n return (values.first..values.last), dimensions\n end\n```\n\nWe want this to trigger when the user chooses `'1'` from our introduction prompt so we need to re-work our `go` method:\n\n```\ndef go\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n action = ask '> '\n say \"\\n\"\n\n case action\n when '1' then range, dimensions = user_range_and_dimensions\n when '2' then range, dimensions = random_range_and_dimensions\n else exit!\n end\n\n range, dimensions = random_range_and_dimensions\n @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)\n output_matrix\nrescue Spiralizer::InvalidInput => e\n say \"\\nUh oh! #{e.message}\"\nensure\n say \"\\nexiting...\"\n exit!\nend\n```\n\nYou should be getting the hang of how to use `Thor`. Our class is need of some cleanup, but with some refactoring we end up with something like this:\n\n\n```\nrequire 'thor'\nrequire 'spiralizer'\n\nclass Spiralizer::CLI < Thor\n MATRICES = [\n ['A-L', '4x3'],\n ['M-X', '3x4'],\n ['1-8', '2x4'],\n ['1-144', '12x12'],\n ['C-J', '4x2'],\n ['A-Z', '13x2']\n ].freeze\n\n attr_reader :action, :matrix\n\n desc 'go', 'starts a prompt that brings users to spiral heaven'\n def go\n clear_screen!\n intro_prompt\n build_matrix\n output_matrix\n action_prompt\n spiralize!\n rescue Spiralizer::InvalidInput => e\n say \"\\nUh oh! #{e.message}\"\n ensure\n quit_softly\n end\n\n no_commands do\n def intro_prompt\n say \"What would you like to do? (choose a number)\\n 1 - Build a matrix\\n 2 - Generate a random matrix\"\n @action = ask '> '\n end\n\n def build_matrix\n say \"\\n\"\n\n case action\n when '1' then range, dimensions = user_range_and_dimensions\n when '2' then range, dimensions = random_range_and_dimensions\n else quit_softly\n end\n\n @matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)\n end\n\n def user_range_and_dimensions\n range_response = ask(\"Please provide range. Acceptable format: (A-L) or (1-6)\\n> \")\n values = range_response.split('-')\n dimensions = ask(\"Please provide dimensions. Acceptable format: (4x3) or (3x2)\\n> \")\n return (values.first..values.last), dimensions\n end\n\n def random_range_and_dimensions\n say \"\\ngenerating...\"\n pause_for_effect\n range_response, dimensions = MATRICES[rand(6)]\n range = range_response.split('-')\n return (range.first..range.last), dimensions\n end\n\n def action_prompt\n say \"\\nWhat would you like to do with it? (choose a number)\\n 1 - Spiralize it\\n 2 - Look at it\"\n @action = ask '> '\n end\n\n def output_matrix\n say \"\\nHere is your M A T R I X\\n------------------------\"\n matrix.each { |inner| say inner.join(\"\\t\") }\n end\n\n def spiralize!\n say \"\\n\"\n say Spiralizer::Spiralize.new(matrix: matrix).perform if action == '1'\n end\n\n def clear_screen!\n return system 'cls' if Gem.win_platform?\n system 'clear'\n end\n\n def pause_for_effect\n sleep 0.5\n end\n\n def quit_softly\n say \"\\nexiting...\"\n pause_for_effect\n exit!\n end\n end\nend\n```\n\nNow, let's try it all out:\n\n```\nWhat would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix\n> 1\n\nPlease provide range. Acceptable format: (A-L) or (1-6)\n> 1-6\nPlease provide dimensions. Acceptable format: (4x3) or (3x2)\n> 2x3\n\nHere is your M A T R I X\n------------------------\n1 2\n3 4\n5 6\n\nWhat would you like to do with it? (choose a number)\n 1 - Spiralize it\n 2 - Look at it\n> 1\n\n1 2 4 6 5 3\n\nexiting...\n```\n\nOur little gem is complete! There is a lot of cleanup we can and should do, but this is where this blog post ends.\nFeel free to add new functionality and experiment further with Thor. I've gone ahead and added a `Crisscross` class for\nexample in my <a href=\"https://github.com/david-pm/matrix-spiralizer\">repo</a>.\n\nOh! One more thing, if yoy run `bundle exec rake install` bundler will install this gem on your system as a global executable so you can then call it like this:\n```\n> spiralizer go\n```\n\nIf you ever need to uninstall it for some insane reason just run `gem uninstall spiralizer`.\n\nYou can find a finalized version of the code <a href=\"https://github.com/david-pm/matrix-spiralizer\">here</a>.\n\nWell, there you have it. We've built a cli gem from scratch using `Bundler` and `Thor`. I hope this post serves you well and until next time!\n\n\n### (づ。◕‿‿◕。)づ *:・゚✧ ✧゚・*:・゚✧ ✧゚・",
"json_metadata": "{\"tags\":[\"ruby\",\"thor\",\"gem\",\"cli\",\"programming\"],\"image\":[\"https://ae01.alicdn.com/kf/HTB1FjN5KFXXXXa6XFXXq6xXFXXXx/1-1-Scale-Full-Metal-Thor-Hammer-Mjolnir-1-1-Replica-Thor-Custom-Cosplay-Hammer-Collection.jpg_640x640.jpg\"],\"links\":[\"https://steemit.com/ruby/@davidpm/create-a-command-line-gem-from-scratch-with-thor\",\"https://github.com/erikhuda/thor\",\"https://github.com/david-pm/matrix-spiralizer\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor-part-two",
"title": "Create a command-line gem from scratch with Thor (part two)"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:55:54",
"trx_id": "492811ff976988cb2ca6b2f94a3b463e9e7ab833",
"trx_in_block": 40,
"virtual_op": 0
}dick.sledgeupvoted (0.85%) @davidpm / create-a-command-line-gem-from-scratch-with-thor2018/06/03 06:46:45
dick.sledgeupvoted (0.85%) @davidpm / create-a-command-line-gem-from-scratch-with-thor
2018/06/03 06:46:45
| author | davidpm |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| voter | dick.sledge |
| weight | 85 (0.85%) |
| Transaction Info | Block #22991554/Trx bf4fc9bd2eea84b9e35eb7f78b93d02f6f61fe1d |
View Raw JSON Data
{
"block": 22991554,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"voter": "dick.sledge",
"weight": 85
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:46:45",
"trx_id": "bf4fc9bd2eea84b9e35eb7f78b93d02f6f61fe1d",
"trx_in_block": 33,
"virtual_op": 0
}2018/06/03 06:41:27
2018/06/03 06:41:27
| author | tomask-de |
| body | Nice read. I leave an upvote for this article *thumbsup* |
| json metadata | {} |
| parent author | davidpm |
| parent permlink | create-a-command-line-gem-from-scratch-with-thor |
| permlink | re-davidpm-create-a-command-line-gem-from-scratch-with-thor-20180603t064129266z |
| title | fossbot voter comment |
| Transaction Info | Block #22991448/Trx dac608219c6e88363c4747d0bc6fb8591042ee41 |
View Raw JSON Data
{
"block": 22991448,
"op": [
"comment",
{
"author": "tomask-de",
"body": "Nice read. I leave an upvote for this article *thumbsup*",
"json_metadata": "{}",
"parent_author": "davidpm",
"parent_permlink": "create-a-command-line-gem-from-scratch-with-thor",
"permlink": "re-davidpm-create-a-command-line-gem-from-scratch-with-thor-20180603t064129266z",
"title": "fossbot voter comment"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:41:27",
"trx_id": "dac608219c6e88363c4747d0bc6fb8591042ee41",
"trx_in_block": 49,
"virtual_op": 0
}tomask-deupvoted (100.00%) @davidpm / create-a-command-line-gem-from-scratch-with-thor2018/06/03 06:41:21
tomask-deupvoted (100.00%) @davidpm / create-a-command-line-gem-from-scratch-with-thor
2018/06/03 06:41:21
| author | davidpm |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| voter | tomask-de |
| weight | 10000 (100.00%) |
| Transaction Info | Block #22991446/Trx cad4647180f532ee2bc40789ba1c048b9d6be5d8 |
View Raw JSON Data
{
"block": 22991446,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"voter": "tomask-de",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:41:21",
"trx_id": "cad4647180f532ee2bc40789ba1c048b9d6be5d8",
"trx_in_block": 15,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor2018/06/03 06:28:54
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor
2018/06/03 06:28:54
| author | davidpm |
| body | @@ -16161,9 +16161,29 @@ reading -. +!%0A%0A### (%E0%B8%87 %CD%A0%C2%B0 %CD%9F%D9%84%CD%9C %CD%A1%C2%B0)%E0%B8%87 |
| json metadata | {"tags":["ruby","gem","thor","cli","matrix"],"image":["https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif","https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png"],"links":["https://bundler.io/","https://git-scm.com/","http://rspec.info/","https://github.com/ruby/rake","https://github.com/erikhuda/thor","https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58","https://en.wikipedia.org/wiki/Ouroboros"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| title | Create a command-line gem from scratch with Thor |
| Transaction Info | Block #22991197/Trx 5c7678b75d9ca6c4ede51d4e9258fce84a8ab24c |
View Raw JSON Data
{
"block": 22991197,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -16161,9 +16161,29 @@\n reading\n-.\n+!%0A%0A### (%E0%B8%87 %CD%A0%C2%B0 %CD%9F%D9%84%CD%9C %CD%A1%C2%B0)%E0%B8%87\n",
"json_metadata": "{\"tags\":[\"ruby\",\"gem\",\"thor\",\"cli\",\"matrix\"],\"image\":[\"https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif\",\"https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png\"],\"links\":[\"https://bundler.io/\",\"https://git-scm.com/\",\"http://rspec.info/\",\"https://github.com/ruby/rake\",\"https://github.com/erikhuda/thor\",\"https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58\",\"https://en.wikipedia.org/wiki/Ouroboros\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"title": "Create a command-line gem from scratch with Thor"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:28:54",
"trx_id": "5c7678b75d9ca6c4ede51d4e9258fce84a8ab24c",
"trx_in_block": 33,
"virtual_op": 0
}davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor2018/06/03 06:17:51
davidpmpublished a new post: create-a-command-line-gem-from-scratch-with-thor
2018/06/03 06:17:51
| author | davidpm |
| body | This is the first in a two-part post on creating a Ruby command-line gem from scratch. --- Let's write a command-line interface (cli) as a Ruby gem. We need the gem to do something slightly challenging so let's take a popular interview question: ### The Matrix Spiralizer  Create an application that takes a matrix (two-dimensional array) and returns a string. The matrix may be of arbitrary size and must consist of uppercase English letters. The returned string must consist of all elements of the matrix ordered in a clockwise spiral pattern starting with element [0, 0]. Each letter must be converted to lowercase and separated by a single whitespace character. For example, given the following matrix: ``` [ [A B C D], [E F G H], [I J K L] ] A B C D E F G H I J K L The resulting string would be: "a b c d h l k j i e f g" ``` Sound fun? Let's jump in. ### Getting Started There are many algorithms you can use to solve this little puzzle, but before we get to the implementation that I went with we need to scaffold our gem. Make sure you have <a href='https://bundler.io/'>Bundler</a> installed (`gem install bundler`) and then: ``` > bundle gem spiralizer && cd spiralizer ``` This command creates a scaffold directory for our new gem and, if we have <a href='https://git-scm.com/'>Git</a> installed, initializes a Git repository in this directory. If this is the first time running the <b>bundle gem</b> command, you will be asked about what kind of default files you'd like to include in all future gems. Let's use <a href='http://rspec.info/'>RSpec</a> for testing our gem. Notice that Bundler scaffolded out a `spec/` directory for our convenience. We need to make sure we specify a recent version of RSpec as a development dependency. Open the <b>spiralizer.gemspec</b> file and bump up the version at the bottom, if needed. While I'm at it, I'm just gonna bump up the <a href='https://github.com/ruby/rake'>Rake</a> and Bundler versions, as well as update the stubbed out summary and description. ``` > spec.summary = %q{A gem that takes a matrix and returns a formatted string} > spec.description = spec.summary ... > spec.add_development_dependency "bundler", "~> 1.16" > spec.add_development_dependency "rake", "~> 11.2" > spec.add_development_dependency "rspec", "~> 3.6" > spec.add_development_dependency "pry" ``` Feel free to bump these versions and add to that description. Later on we will be installing the <a href='https://github.com/erikhuda/thor'>Thor</a> gem, so you could all install that now if you feel like it. In any case, we'll circle back to that later. You may have noticed that we have a `Gemfile`. Open it up and its pretty empty. All you should see is: ``` source "https://rubygems.org" gemspec ``` That `gemspec` method is defined in the Bundler gem under <a href='https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58'>lib/bundler/dsl.rb</a>. Because we call that `gemspec` method in our Gemfile, Bundler will automatically add this gem to a group called "development" which we can then reference any time we want to load the gems defined in our gemspec file. Additionally, anybody who now runs `gem install spiralizer --dev` will get these dev dependencies installed too. Run `bundle install` and the gems will be installed, and generate our `Gemfile.lock` (which is responsible for ensuring that every system this library is developed on has the exact same gems). Bundler detects the spiralizer gem, loads the gemspec and bundles just like any other gem. With all this boilerplate out of the way, we can now start addressing our algorithm.  So, how do we solve our little problem? The solution I came up with works like the <a href="https://en.wikipedia.org/wiki/Ouroboros">Ouroboros</a> above. Say we have with this: ``` [ [A B C D], [E F G H], [I J K L] ] ``` We start at the first index which is this inner array: `[A B C D]`. All we need to do is join that whole row together and we have the beginnings of our desired string. Next, we just need to grab the next index of our outer array, reverse it and join that together. Then we concatenate the result to our previous string, and so on. The approach I'd like to take here is to run this whole thing in a loop that will deconstruct, consume, and discard as we go. When there is nothing left, we know we are done. Hence the reference to the Ouroboros. Here's a semi-visual breakdown of how I want the algorithm to make its way through our matrix: #### Step One: go from left to right until you hit the end, then discard ``` a b c d E F G H I J K L ``` #### Step Two: grab last index of remaining inner arrays, then discard ``` E F G h I J K l ``` #### Step Three: take the last array, reverse crawl it, then discard ``` E F G i j k ``` #### Step Four: grab first index of remaining inner arrays, climb the ladder, then discard ``` e F G ``` Then repeat until there is nothing left to consume. Let's write a simple test to get this going. Open up `spec/spiralizer_spec.rb` and you should see some examples scaffolded for you. The first one is useful, so we'll keep it around: ``` it "has a version number" do expect(Spiralizer::VERSION).not_to be nil end ``` If you run the spec (`rspec spec/spiralizer_spec.rb:2`) it will pass. That is because bundler defined a `VERSION` constant for us already in `lib/spiralizer/version.rb`. So, that's nice. Let's delete that second example that "does something useful", and get back to our implementation. We want to define a useful example that will help us work towards our algorithm we defined above. Let's start by defining a matrix of chars and an expected result string: ``` RSpec.describe Spiralizer do context 'character matrix' do let(:matrix) do [%w[A B C D], %w[E F G H], %w[I J K L], %w[M N O P], %w[Q R S T]] end let(:expected_result) { %Q{a b c d h l p t s r q m i e f g k o n j} } it 'returns a string in spiraling order' do end end end ``` Now, we just need an assertion. We want to pass our matrix in, and get back our expected string. So, what do we want our interface to look like? In `lib/spiralizer.rb` you'll notice a comment from bundler saying: `# Your code goes here...` We will get to work in there. Let's add a nested class called `class Spiralizer` that will have a `perform` method that does the work. Here's our assertion: ``` RSpec.describe Spiralizer do context 'character matrix' do let(:matrix) do [%w[A B C D], %w[E F G H], %w[I J K L], %w[M N O P], %w[Q R S T]] end let(:expected_result) { %Q{a b c d h l p t s r q m i e f g k o n j} } it 'returns a string in spiraling order' do expect(Spiralizer::Spiralize.new(matrix: matrix).perform).to eq expected_result end end end ``` Let's add one more example for handling a matrix of integers: ``` RSpec.describe Spiralizer do ... context 'integer matrix' do let(:matrix) do [[1,2,3], [10, 11, 4], [9, 12, 5], [8, 7, 6]] end let(:expected_result) { %Q{1 2 3 4 5 6 7 8 9 10 11 12} } it 'handles numbers with multiple digits' do expect(Spiralizer::Spiralize.new(matrix: matrix).perform).to eq expected_result end end end ``` It's pretty much the same thing. We want to pass in our pre-defined matrix (note the keyword argument) and spiralize it. Right now this test is red. If you want to try your own solution, now would be the time to pause reading further and give it a try. --- Skipping ahead for brevity, here is a solution I came up with: ``` class Spiralizer::Spiralize def initialize(matrix:) @matrix = matrix @result = String.new @padding = String.new ' ' end def perform until matrix.empty? do @result += @matrix.shift.join(padding).concat(padding) break if matrix.empty? @result += @matrix.map(&:pop).join(padding).concat(padding) @result += @matrix.pop.reverse.join(padding).concat(padding) @result += @matrix.map(&:shift).reverse.join(padding).concat(padding) end @result.downcase.strip end private attr_reader :padding, :result, :matrix end ``` Our `Spiralizer::Spiralize` class creates a few instance variables for our pre-defined matrix, and a couple of strings: one empty, and one with a space for adding padding to our string output. `perform` does the work by looping over each inner array of characters, shifting and popping as it goes until the matrix is consumed. This took a little trial and error to figure out, but I think the result is simple enough to follow. Our specs should now be passing. So, this is working just fine but what happens if we enter some bad input? Let's add some naive validations to at least make sure we are only accept valid matrices. I'm imagining we want our validations to run immediately when we receive input from a user. We can define a simple error class called `Spiralizer::InvalidInput`, and a class method on our spiralizer module that take our matrix in as an argument and analyzes its <em>matrixiness</em>. If it is not a matrix it will raise our error class. Pretty straight forward. Here's my test: ``` RSpec.describe Spiralizer do ... context 'invalid input' do let(:notamatrix) { [{a: 'b'}, 'bye'] } it 'only takes matrices' do expect{ Spiralizer::Spiralize.new(matrix: notamatrix) } .to raise_error Spiralizer::InvalidInput end end end ``` Our tests are red again, so let's add our code: ``` module Spiralizer class InvalidInput < StandardError; end def self.validate_input(matrix) unless matrix.respond_to?(:first) && matrix.first.respond_to?(:join) fail Spiralizer::InvalidInput.new("spiralize only accepts a matrix") end end class Spiralize def initialize(matrix:) Spiralizer.validate_input(matrix) ... end ... end end ``` ...and run our tests again. Green. This is a good time to think a little about what kind of application we'd like. Let's go for a command line interface that prompts a user for input to spiralize. When we start up the program we'd like a list of options to choose from, so the user can know what they are able to do. For example: ``` #-> what would you like to do? 1 - Build a matrix 2 - Generate a random matrix #-> now that we have a matrix, would you like to spiralize it? 1 - spiralize it, duh 2 - nope ``` In order to get this going were going to have to add a few missing pieces to our baby gem. We're going to want to add a matrix factory that can handle user input. Without matrices to spiralize this gem is no good. We'll also need to build out our user facing cli. Let's add some specs for a matrix factory. Our factory will be class within the same namespace called Matrix. We would like to pass in some values and paramaters on how we'd like this matrix to look. Let's say a range of characters and the dimensions of the matrix. For example, if we had a range of characters `('A'..'L')` we could build a `3x4`, `2x6`, or a `4x3` matrix: ``` (2 x 6) A B C D E F G H I J K L (3x4) A B C D E F G H I J K L (4x3) A B C D E F G H I J K L ``` So, we need to be able to accept a range of values, and the desired dimensions of our matrix. Let's mix it up and make it a class method called `the_matrix` just 'cause. We'll add our first assertion right under the others for now: ``` RSpec.describe Spiralizer do ... describe 'Matrix' do context 'character matrix' do let(:matrix) do Spiralizer::Matrix.the_matrix(range: 'A'..'L', dimensions: '4x3') end let(:expected_matrix) do [%w[A B C D], %w[E F G H], %w[I J K L]] end it 'returns a matrix from a given range of letters' do expect(matrix).to eq expected_matrix end end end ``` The happy path is easy enough but we are talking about user input so we need to add tests for as many cases we can conceive of and get to work. I'm not trying to be exhaustive here, since this is just for fun. Here is where I stopped: ``` RSpec.describe Spiralizer do ... describe 'Matrix' do ... context 'uneven range of numbers' do let(:matrix) do Spiralizer::Matrix.the_matrix(range: 1..7, dimensions: '3x2') end let(:expected_matrix) do [[1,2,3], [4,5,6]] end it 'returns a matrix from a given range of numbers, excluding last number' do expect(matrix).to eq expected_matrix end end context 'invalid input' do it 'raises when range not passed' do expect{ Spiralizer::Matrix.the_matrix(range: 'hi', dimensions: '3x2') } .to raise_error Spiralizer::InvalidInput, 'valid range expected' end it 'raises when mal-formatted dimensions are passed' do expect{ Spiralizer::Matrix.the_matrix(range: (1..4), dimensions: '3 2') } .to raise_error Spiralizer::InvalidInput, 'dimensions must be a string in \'2x4\' format' end it 'raises when matrix cannot be built with given input' do expect{ Spiralizer::Matrix.the_matrix(range: (1..4), dimensions: '3x2') } .to raise_error Spiralizer::InvalidInput, 'can\'t build matrix with range/dimensions pair' end end end end ``` As you can see, I'm anticipating a user could pass in an uneven total of characters. We don't want to deal with odd numbers, so we'll just lop that last char off and move on. We also guard against malformatted ranges, and dimensions, and just give up when we can't build a matrix with the input supplied. And our tests are now red again. Let's get those passing: ``` class Matrix attr_reader :range, :dimensions INVALID_PAIR = 'can\'t build matrix with range/dimensions pair'.freeze INVALID_RANGE = 'valid range expected'.freeze INVALID_DIMENSIONS = 'dimensions must be a string in \'2x4\' format'.freeze def self.the_matrix(range:, dimensions:) new(range, dimensions).build end def initialize(range, dimensions) @range = range @dimensions = dimensions validate_range_and_dimenstions end def validate_range_and_dimenstions fail Spiralizer::InvalidInput.new(INVALID_RANGE) unless valid_range? fail Spiralizer::InvalidInput.new(INVALID_DIMENSIONS) unless valid_dimensions? fail Spiralizer::InvalidInput.new(INVALID_PAIR) unless valid_pair? puts('WARNING: The last character will be excluded.') if range.count.odd? end def build range_enum = range.each Array.new(cols) { Array.new(rows) { range_enum.next } } end private def valid_range? range.respond_to?(:to_a) end def valid_dimensions? dimensions.respond_to?(:split) && dimensions =~ /\d+\s?x+\s?\d+/ end def valid_pair? (range.count / rows) == cols || (range.count / cols) == rows end def split_dimensions @split_dimensions ||= dimensions.split('x').map(&:to_i) end def rows @rows ||= split_dimensions.first end def cols @cols ||= split_dimensions.last end end ``` As mentioned above, our entry point will be from `the_matrix` class method. Feel free not to do that, I just wanted to have a method called `the_matrix`. :) We new up the object from there passing in our params and validate the input with the `validate_range_and_dimenstions` method. If the input passes the validations we return back to `the_matrix` and call `build` on our newly instantiated object. `build` created an enum out of our range and calls Enumerator's `next` method to fill in the values of our matrix nested inside of the inner columns of our initial array. I've glossed over some of the complexities involved, but going into Ruby's enum classes is outside of the scope of this post. Take a few minutes to look over the code as needed. At this point, our tests are green again. We can now create matrices and spiralize them, and we have decent test coverage to boot. --- In part two of this series, we will begin working on our cli making use of the Thor gem. Until then, thanks for reading. |
| json metadata | {"tags":["ruby","gem","thor","cli","matrix"],"image":["https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif","https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png"],"links":["https://bundler.io/","https://git-scm.com/","http://rspec.info/","https://github.com/ruby/rake","https://github.com/erikhuda/thor","https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58","https://en.wikipedia.org/wiki/Ouroboros"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | ruby |
| permlink | create-a-command-line-gem-from-scratch-with-thor |
| title | Create a command-line gem from scratch with Thor |
| Transaction Info | Block #22990976/Trx 5093a62f53ca9d1ef8dc353e574df643adf2c7a1 |
View Raw JSON Data
{
"block": 22990976,
"op": [
"comment",
{
"author": "davidpm",
"body": "This is the first in a two-part post on creating a Ruby command-line gem from scratch.\n\n---\n\nLet's write a command-line interface (cli) as a Ruby gem. We need the gem to do something slightly challenging so let's take a popular interview question: \n\n### The Matrix Spiralizer\n\n\n\nCreate an application that takes a matrix (two-dimensional array) and returns a string. The matrix may be of arbitrary size and must consist of uppercase English letters. The returned string must consist of all elements of the matrix ordered in a clockwise spiral pattern starting with element [0, 0]. Each letter must be converted to lowercase and separated by a single whitespace character.\n\nFor example, given the following matrix:\n```\n[\n [A B C D],\n [E F G H],\n [I J K L]\n]\n\nA B C D\nE F G H\nI J K L\n\nThe resulting string would be:\n\"a b c d h l k j i e f g\"\n```\n\nSound fun? Let's jump in.\n\n\n### Getting Started\n\nThere are many algorithms you can use to solve this little puzzle, but before we get to the implementation that I went with we need to scaffold our gem. Make sure you have <a href='https://bundler.io/'>Bundler</a> installed (`gem install bundler`) and then:\n\n```\n> bundle gem spiralizer && cd spiralizer\n```\n\nThis command creates a scaffold directory for our new gem and, if we have <a href='https://git-scm.com/'>Git</a> installed, initializes a Git repository in this directory. If this is the first time running the <b>bundle gem</b> command, you will be asked about what kind of default files you'd like to include in all future gems. Let's use <a href='http://rspec.info/'>RSpec</a> for testing our gem. Notice that Bundler scaffolded out a `spec/` directory for our convenience. We need to make sure we specify a recent version of RSpec as a development dependency.\n\nOpen the <b>spiralizer.gemspec</b> file and bump up the version at the bottom, if needed. While I'm at it, I'm just gonna bump up the <a href='https://github.com/ruby/rake'>Rake</a> and Bundler versions, as well as update the stubbed out summary and description.\n\n```\n> spec.summary = %q{A gem that takes a matrix and returns a formatted string}\n> spec.description = spec.summary\n...\n> spec.add_development_dependency \"bundler\", \"~> 1.16\"\n> spec.add_development_dependency \"rake\", \"~> 11.2\"\n> spec.add_development_dependency \"rspec\", \"~> 3.6\"\n> spec.add_development_dependency \"pry\"\n```\n\nFeel free to bump these versions and add to that description. Later on we will be installing the <a href='https://github.com/erikhuda/thor'>Thor</a> gem, so you could all install that now if you feel like it. In any case, we'll circle back to that later.\n\nYou may have noticed that we have a `Gemfile`. Open it up and its pretty empty. All you should see is:\n```\nsource \"https://rubygems.org\"\ngemspec\n```\n\nThat `gemspec` method is defined in the Bundler gem under <a href='https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58'>lib/bundler/dsl.rb</a>. Because we call that `gemspec` method in our Gemfile, Bundler will automatically add this gem to a group called \"development\" which we can then reference any time we want to load the gems defined in our gemspec file. Additionally, anybody who now runs `gem install spiralizer --dev` will get these dev dependencies installed too.\n\nRun `bundle install` and the gems will be installed, and generate our `Gemfile.lock` (which is responsible for ensuring that every system this library is developed on has the exact same gems). Bundler detects the spiralizer gem, loads the gemspec and bundles just like any other gem. With all this boilerplate out of the way, we can now start addressing our algorithm.\n\n\n\nSo, how do we solve our little problem? The solution I came up with works like the <a href=\"https://en.wikipedia.org/wiki/Ouroboros\">Ouroboros</a> above. Say we have with this:\n\n```\n[\n [A B C D],\n [E F G H],\n [I J K L]\n]\n```\nWe start at the first index which is this inner array: `[A B C D]`. All we need to do is join that whole row together and we have the beginnings of our desired string. Next, we just need to grab the next index of our outer array, reverse it and join that together. Then we concatenate the result to our previous string, and so on. The approach I'd like to take here is to run this whole thing in a loop that will deconstruct, consume, and discard as we go. When there is nothing left, we know we are done. Hence the reference to the Ouroboros. Here's a semi-visual breakdown of how I want the algorithm to make its way through our matrix:\n\n#### Step One:\ngo from left to right until you hit the end, then discard\n```\n a b c d\n E F G H\n I J K L\n```\n#### Step Two:\ngrab last index of remaining inner arrays, then discard\n```\n E F G h\n I J K l\n```\n#### Step Three:\ntake the last array, reverse crawl it, then discard\n```\n E F G\n i j k\n```\n#### Step Four:\ngrab first index of remaining inner arrays, climb the ladder, then discard\n```\n e F G\n```\n\nThen repeat until there is nothing left to consume.\n\nLet's write a simple test to get this going. Open up `spec/spiralizer_spec.rb` and you should see some examples scaffolded for you. The first one is useful, so we'll keep it around:\n\n```\n it \"has a version number\" do\n expect(Spiralizer::VERSION).not_to be nil\n end\n```\n\nIf you run the spec (`rspec spec/spiralizer_spec.rb:2`) it will pass. That is because bundler defined a `VERSION` constant for us already in `lib/spiralizer/version.rb`. So, that's nice. Let's delete that second example that \"does something useful\", and get back to our implementation.\n\nWe want to define a useful example that will help us work towards our algorithm we defined above. Let's start by defining a matrix of chars and an expected result string:\n\n```\nRSpec.describe Spiralizer do\n context 'character matrix' do\n let(:matrix) do\n [%w[A B C D], %w[E F G H], %w[I J K L], %w[M N O P], %w[Q R S T]]\n end\n\n let(:expected_result) { %Q{a b c d h l p t s r q m i e f g k o n j} }\n\n it 'returns a string in spiraling order' do\n end\n end\nend\n```\n\nNow, we just need an assertion. We want to pass our matrix in, and get back our expected string. So, what do we want our interface to look like? In `lib/spiralizer.rb` you'll notice a comment from bundler saying: `# Your code goes here...`\n\nWe will get to work in there. Let's add a nested class called `class Spiralizer` that will have a `perform` method that does the work. Here's our assertion:\n\n```\nRSpec.describe Spiralizer do\n context 'character matrix' do\n let(:matrix) do\n [%w[A B C D], %w[E F G H], %w[I J K L], %w[M N O P], %w[Q R S T]]\n end\n\n let(:expected_result) { %Q{a b c d h l p t s r q m i e f g k o n j} }\n\n it 'returns a string in spiraling order' do\n expect(Spiralizer::Spiralize.new(matrix: matrix).perform).to eq expected_result\n end\n end\nend\n```\n\nLet's add one more example for handling a matrix of integers:\n\n```\nRSpec.describe Spiralizer do\n ...\n context 'integer matrix' do\n let(:matrix) do\n [[1,2,3], [10, 11, 4], [9, 12, 5], [8, 7, 6]]\n end\n\n let(:expected_result) { %Q{1 2 3 4 5 6 7 8 9 10 11 12} }\n\n it 'handles numbers with multiple digits' do\n expect(Spiralizer::Spiralize.new(matrix: matrix).perform).to eq expected_result\n end\n end\nend\n```\n\nIt's pretty much the same thing. We want to pass in our pre-defined matrix (note the keyword argument) and spiralize it. Right now this test is red. If you want to try your own solution, now would be the time to pause reading further and give it a try.\n\n---\n\nSkipping ahead for brevity, here is a solution I came up with:\n\n```\nclass Spiralizer::Spiralize\n def initialize(matrix:)\n @matrix = matrix\n @result = String.new\n @padding = String.new ' '\n end\n\n def perform\n until matrix.empty? do\n @result += @matrix.shift.join(padding).concat(padding)\n break if matrix.empty?\n @result += @matrix.map(&:pop).join(padding).concat(padding)\n @result += @matrix.pop.reverse.join(padding).concat(padding)\n @result += @matrix.map(&:shift).reverse.join(padding).concat(padding)\n end\n\n @result.downcase.strip\n end\n\n private\n\n attr_reader :padding, :result, :matrix\nend\n```\n\nOur `Spiralizer::Spiralize` class creates a few instance variables for our pre-defined matrix, and a couple of strings: one empty, and one with a space for adding padding to our string output. `perform` does the work by looping over each inner array of characters, shifting and popping as it goes until the matrix is consumed. This took a little trial and error to figure out, but I think the result is simple enough to follow. Our specs should now be passing.\n\nSo, this is working just fine but what happens if we enter some bad input? Let's add some naive validations to at least make sure we are only accept valid matrices. I'm imagining we want our validations to run immediately when we receive input from a user. We can define a simple error class called `Spiralizer::InvalidInput`, and a class method on our\nspiralizer module that take our matrix in as an argument and analyzes its <em>matrixiness</em>. If it is not a matrix it will raise our error class. Pretty straight forward. Here's my test:\n\n```\nRSpec.describe Spiralizer do\n ...\n context 'invalid input' do\n let(:notamatrix) { [{a: 'b'}, 'bye'] }\n\n it 'only takes matrices' do\n expect{ Spiralizer::Spiralize.new(matrix: notamatrix) }\n .to raise_error Spiralizer::InvalidInput\n end\n end\nend\n```\n\nOur tests are red again, so let's add our code: \n\n```\nmodule Spiralizer\n class InvalidInput < StandardError; end\n\n def self.validate_input(matrix)\n unless matrix.respond_to?(:first) && matrix.first.respond_to?(:join)\n fail Spiralizer::InvalidInput.new(\"spiralize only accepts a matrix\")\n end\n end\n\n class Spiralize\n def initialize(matrix:)\n Spiralizer.validate_input(matrix)\n ...\n end\n ...\n end\nend\n```\n\n...and run our tests again. Green.\n\nThis is a good time to think a little about what kind of application we'd like. Let's go for a command line interface that prompts a user for input to spiralize. When we start up the program we'd like a list of options to choose from, so the user can know what they are able to do. For example:\n\n```\n#-> what would you like to do?\n 1 - Build a matrix\n 2 - Generate a random matrix\n\n#-> now that we have a matrix, would you like to spiralize it?\n 1 - spiralize it, duh\n 2 - nope\n```\n\nIn order to get this going were going to have to add a few missing pieces to our baby gem. We're going to want to add a matrix factory that can handle user input. Without matrices to spiralize this gem is no good. We'll also need to build out our user facing cli. Let's add some specs for a matrix factory. Our factory will be class within the same namespace called Matrix. We would like to pass in some values and paramaters on how we'd like this matrix to look. Let's say a range of characters and the dimensions of the matrix.\n\nFor example, if we had a range of characters `('A'..'L')` we could build a `3x4`, `2x6`, or a `4x3` matrix:\n\n```\n(2 x 6)\n\nA B\nC D\nE F\nG H\nI J\nK L\n\n(3x4)\n\nA B C\nD E F\nG H I\nJ K L\n\n(4x3)\n\nA B C D\nE F G H\nI J K L\n```\n\nSo, we need to be able to accept a range of values, and the desired dimensions of our matrix. Let's mix it up and make it a class method called `the_matrix` just 'cause. We'll add our first assertion right under the others for now:\n```\nRSpec.describe Spiralizer do\n ...\n describe 'Matrix' do\n context 'character matrix' do\n let(:matrix) do\n Spiralizer::Matrix.the_matrix(range: 'A'..'L', dimensions: '4x3')\n end\n\n let(:expected_matrix) do\n [%w[A B C D], %w[E F G H], %w[I J K L]]\n end\n\n it 'returns a matrix from a given range of letters' do\n expect(matrix).to eq expected_matrix\n end\n end\n end\n```\nThe happy path is easy enough but we are talking about user input so we need to add tests for as many cases we can conceive of and get to work. I'm not trying to be exhaustive here, since this is just for fun. Here is where I stopped:\n\n```\nRSpec.describe Spiralizer do\n ...\n describe 'Matrix' do\n ...\n context 'uneven range of numbers' do\n let(:matrix) do\n Spiralizer::Matrix.the_matrix(range: 1..7, dimensions: '3x2')\n end\n\n let(:expected_matrix) do\n [[1,2,3], [4,5,6]]\n end\n\n it 'returns a matrix from a given range of numbers, excluding last number' do\n expect(matrix).to eq expected_matrix\n end\n end\n\n context 'invalid input' do\n it 'raises when range not passed' do\n expect{ Spiralizer::Matrix.the_matrix(range: 'hi', dimensions: '3x2') }\n .to raise_error Spiralizer::InvalidInput, 'valid range expected'\n end\n\n it 'raises when mal-formatted dimensions are passed' do\n expect{ Spiralizer::Matrix.the_matrix(range: (1..4), dimensions: '3 2') }\n .to raise_error Spiralizer::InvalidInput, 'dimensions must be a string in \\'2x4\\' format'\n end\n\n it 'raises when matrix cannot be built with given input' do\n expect{ Spiralizer::Matrix.the_matrix(range: (1..4), dimensions: '3x2') }\n .to raise_error Spiralizer::InvalidInput, 'can\\'t build matrix with range/dimensions pair'\n end\n end\n end\nend\n```\nAs you can see, I'm anticipating a user could pass in an uneven total of characters. We don't want to deal with odd numbers, so we'll just lop that last char off and move on. We also guard against malformatted ranges, and dimensions, and just give up when we can't build a matrix with the input supplied. And our tests are now red again.\n\nLet's get those passing:\n```\nclass Matrix\n attr_reader :range, :dimensions\n\n INVALID_PAIR = 'can\\'t build matrix with range/dimensions pair'.freeze\n INVALID_RANGE = 'valid range expected'.freeze\n INVALID_DIMENSIONS = 'dimensions must be a string in \\'2x4\\' format'.freeze\n\n def self.the_matrix(range:, dimensions:)\n new(range, dimensions).build\n end\n\n def initialize(range, dimensions)\n @range = range\n @dimensions = dimensions\n validate_range_and_dimenstions\n end\n\n def validate_range_and_dimenstions\n fail Spiralizer::InvalidInput.new(INVALID_RANGE) unless valid_range?\n fail Spiralizer::InvalidInput.new(INVALID_DIMENSIONS) unless valid_dimensions?\n fail Spiralizer::InvalidInput.new(INVALID_PAIR) unless valid_pair?\n\n puts('WARNING: The last character will be excluded.') if range.count.odd?\n end\n\n def build\n range_enum = range.each\n Array.new(cols) { Array.new(rows) { range_enum.next } }\n end\n\n private\n\n def valid_range?\n range.respond_to?(:to_a)\n end\n\n def valid_dimensions?\n dimensions.respond_to?(:split) && dimensions =~ /\\d+\\s?x+\\s?\\d+/\n end\n\n def valid_pair?\n (range.count / rows) == cols || (range.count / cols) == rows\n end\n\n def split_dimensions\n @split_dimensions ||= dimensions.split('x').map(&:to_i)\n end\n\n def rows\n @rows ||= split_dimensions.first\n end\n\n def cols\n @cols ||= split_dimensions.last\n end\nend\n```\n\nAs mentioned above, our entry point will be from `the_matrix` class method. Feel free not to do that, I just wanted to have a method called `the_matrix`. :)\n\nWe new up the object from there passing in our params and validate the input with the `validate_range_and_dimenstions` method. If the input passes the validations we return back to `the_matrix` and call `build` on our newly instantiated object. `build` created an enum out of our range and calls Enumerator's `next` method to fill in the values of our matrix nested inside of the inner columns of our initial array.\n\nI've glossed over some of the complexities involved, but going into Ruby's enum classes is outside of the scope of this post. Take a few minutes to look over the code as needed. At this point, our tests are green again. We can now create matrices and spiralize them, and we have decent test coverage to boot. \n\n---\n\nIn part two of this series, we will begin working on our cli making use of the Thor gem. Until then, thanks for reading.",
"json_metadata": "{\"tags\":[\"ruby\",\"gem\",\"thor\",\"cli\",\"matrix\"],\"image\":[\"https://media.giphy.com/media/S4HKH9KgRGMdq/giphy.gif\",\"https://upload.wikimedia.org/wikipedia/commons/3/32/Chrysopoea_of_Cleopatra_1.png\"],\"links\":[\"https://bundler.io/\",\"https://git-scm.com/\",\"http://rspec.info/\",\"https://github.com/ruby/rake\",\"https://github.com/erikhuda/thor\",\"https://github.com/bundler/bundler/blob/master/lib/bundler/dsl.rb#L58\",\"https://en.wikipedia.org/wiki/Ouroboros\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "ruby",
"permlink": "create-a-command-line-gem-from-scratch-with-thor",
"title": "Create a command-line gem from scratch with Thor"
}
],
"op_in_trx": 0,
"timestamp": "2018-06-03T06:17:51",
"trx_id": "5093a62f53ca9d1ef8dc353e574df643adf2c7a1",
"trx_in_block": 16,
"virtual_op": 0
}lionindayardupvoted (0.49%) @davidpm / blockchain-proof-of-concept2018/05/29 01:57:00
lionindayardupvoted (0.49%) @davidpm / blockchain-proof-of-concept
2018/05/29 01:57:00
| author | davidpm |
| permlink | blockchain-proof-of-concept |
| voter | lionindayard |
| weight | 49 (0.49%) |
| Transaction Info | Block #22841809/Trx 61c8d06fef8cc940ff53588112356d48ba253c60 |
View Raw JSON Data
{
"block": 22841809,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "blockchain-proof-of-concept",
"voter": "lionindayard",
"weight": 49
}
],
"op_in_trx": 0,
"timestamp": "2018-05-29T01:57:00",
"trx_id": "61c8d06fef8cc940ff53588112356d48ba253c60",
"trx_in_block": 20,
"virtual_op": 0
}dick.sledgeupvoted (0.49%) @davidpm / blockchain-proof-of-concept2018/05/29 01:56:57
dick.sledgeupvoted (0.49%) @davidpm / blockchain-proof-of-concept
2018/05/29 01:56:57
| author | davidpm |
| permlink | blockchain-proof-of-concept |
| voter | dick.sledge |
| weight | 49 (0.49%) |
| Transaction Info | Block #22841808/Trx 43bc6124dd46b5b53ef2caca7f0d169b6c0c5bfd |
View Raw JSON Data
{
"block": 22841808,
"op": [
"vote",
{
"author": "davidpm",
"permlink": "blockchain-proof-of-concept",
"voter": "dick.sledge",
"weight": 49
}
],
"op_in_trx": 0,
"timestamp": "2018-05-29T01:56:57",
"trx_id": "43bc6124dd46b5b53ef2caca7f0d169b6c0c5bfd",
"trx_in_block": 15,
"virtual_op": 0
}davidpmpublished a new post: blockchain-proof-of-concept2018/05/29 01:34:12
davidpmpublished a new post: blockchain-proof-of-concept
2018/05/29 01:34:12
| author | davidpm |
| body | @@ -409,12 +409,8 @@ ord%0A -%3Cbr%3E %0A*Bl @@ -3181,16 +3181,281 @@ iated.%0A%0A +%60%60%60%0Aclass Block%0A ...%0A def initialize(index:, data:, parent: '')%0A ...%0A @hash = hashsum%0A end%0A%0A private%0A%0A def hashsum%0A hashable = %22#%7Bindex.to_s%7D #%7Bparent%7D #%7Bcreated_at%7D #%7Bdata.to_s%7D%22%0A Digest::SHA2.new(256).hexdigest(hashable)%0A end%0Aend%0A%60%60%60%0A%0A %60hashsum |
| json metadata | {"tags":["blockchain","ruby","crypto","programming"],"image":["https://themerkle.com/wp-content/uploads/2017/08/shutterstock_679065259.jpg"],"links":["https://www.ruby-lang.org/en/","https://ruby-doc.org/stdlib-2.5.0/libdoc/digest/rdoc/Digest/SHA2.html","https://en.wikipedia.org/wiki/SOLID","https://gist.github.com/david-pm/4791fbb0aa2a0cf4efce9adb044352d3"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | blockchain |
| permlink | blockchain-proof-of-concept |
| title | Blockchain Proof of Concept |
| Transaction Info | Block #22841353/Trx 42c1a2ddb067eed5c1bf860dda62f0e61671cb92 |
View Raw JSON Data
{
"block": 22841353,
"op": [
"comment",
{
"author": "davidpm",
"body": "@@ -409,12 +409,8 @@\n ord%0A\n-%3Cbr%3E\n %0A*Bl\n@@ -3181,16 +3181,281 @@\n iated.%0A%0A\n+%60%60%60%0Aclass Block%0A ...%0A def initialize(index:, data:, parent: '')%0A ...%0A @hash = hashsum%0A end%0A%0A private%0A%0A def hashsum%0A hashable = %22#%7Bindex.to_s%7D #%7Bparent%7D #%7Bcreated_at%7D #%7Bdata.to_s%7D%22%0A Digest::SHA2.new(256).hexdigest(hashable)%0A end%0Aend%0A%60%60%60%0A%0A\n %60hashsum\n",
"json_metadata": "{\"tags\":[\"blockchain\",\"ruby\",\"crypto\",\"programming\"],\"image\":[\"https://themerkle.com/wp-content/uploads/2017/08/shutterstock_679065259.jpg\"],\"links\":[\"https://www.ruby-lang.org/en/\",\"https://ruby-doc.org/stdlib-2.5.0/libdoc/digest/rdoc/Digest/SHA2.html\",\"https://en.wikipedia.org/wiki/SOLID\",\"https://gist.github.com/david-pm/4791fbb0aa2a0cf4efce9adb044352d3\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "blockchain",
"permlink": "blockchain-proof-of-concept",
"title": "Blockchain Proof of Concept"
}
],
"op_in_trx": 0,
"timestamp": "2018-05-29T01:34:12",
"trx_id": "42c1a2ddb067eed5c1bf860dda62f0e61671cb92",
"trx_in_block": 46,
"virtual_op": 0
}Manabar
Voting Power100.00%
Downvote Power100.00%
Resource Credits100.00%
Reputation Progress26.99%
{
"voting_manabar": {
"current_mana": "8143659806",
"last_update_time": 1779059865
},
"downvote_manabar": {
"current_mana": 2035914951,
"last_update_time": 1779059865
},
"rc_account": {
"account": "davidpm",
"max_rc": "10164408779",
"max_rc_creation_adjustment": {
"amount": "2020748973",
"nai": "@@000000037",
"precision": 6
},
"rc_manabar": {
"current_mana": "10164408779",
"last_update_time": 1779059865
}
}
}Account Metadata
| POSTING JSON METADATA | |
| profile | {"profile_image":"https://avatars3.githubusercontent.com/u/6234571?s=400&u=81563e5c8010d6ffdb26be7d77dad7c555c6066f&v=4","website":"http://davidpm.io","cover_image":"https://pbs.twimg.com/profile_banners/948220550/1503333176/1500x500","about":"quick posts about programming"} |
| JSON METADATA | |
| profile | {"profile_image":"https://avatars3.githubusercontent.com/u/6234571?s=400&u=81563e5c8010d6ffdb26be7d77dad7c555c6066f&v=4","website":"http://davidpm.io","cover_image":"https://pbs.twimg.com/profile_banners/948220550/1503333176/1500x500","about":"quick posts about programming"} |
{
"posting_json_metadata": {
"profile": {
"profile_image": "https://avatars3.githubusercontent.com/u/6234571?s=400&u=81563e5c8010d6ffdb26be7d77dad7c555c6066f&v=4",
"website": "http://davidpm.io",
"cover_image": "https://pbs.twimg.com/profile_banners/948220550/1503333176/1500x500",
"about": "quick posts about programming"
}
},
"json_metadata": {
"profile": {
"profile_image": "https://avatars3.githubusercontent.com/u/6234571?s=400&u=81563e5c8010d6ffdb26be7d77dad7c555c6066f&v=4",
"website": "http://davidpm.io",
"cover_image": "https://pbs.twimg.com/profile_banners/948220550/1503333176/1500x500",
"about": "quick posts about programming"
}
}
}Auth Keys
Owner
Single Signature
Public Keys
STM5DEkfgR4JvnCFgdPyqbyseWJS4ePqHj9VnBbLVadAJuCQErBpy1/1
Active
Single Signature
Public Keys
STM8kxLaTeG6HXYMuVtjr6R1yFcDpob1H4R7HuWj3CpQDKB2rhRnZ1/1
Posting
Single Signature
Public Keys
STM5D1ZCtab5A5DzioThZSGKCASa4QuYXsegaFYr4E2edcKa85ciP1/1
Memo
STM6P3LvVZjSRnWetfbWcXeEcF6smw5xpW87s43oEd21Lea53ovPb
{
"owner": {
"account_auths": [],
"key_auths": [
[
"STM5DEkfgR4JvnCFgdPyqbyseWJS4ePqHj9VnBbLVadAJuCQErBpy",
1
]
],
"weight_threshold": 1
},
"active": {
"account_auths": [],
"key_auths": [
[
"STM8kxLaTeG6HXYMuVtjr6R1yFcDpob1H4R7HuWj3CpQDKB2rhRnZ",
1
]
],
"weight_threshold": 1
},
"posting": {
"account_auths": [],
"key_auths": [
[
"STM5D1ZCtab5A5DzioThZSGKCASa4QuYXsegaFYr4E2edcKa85ciP",
1
]
],
"weight_threshold": 1
},
"memo": "STM6P3LvVZjSRnWetfbWcXeEcF6smw5xpW87s43oEd21Lea53ovPb"
}Witness Votes
0 / 30
No active witness votes.
[]