{"id":22242684,"url":"https://github.com/gabrielecanepa/medium-pocket","last_synced_at":"2025-06-26T00:04:13.797Z","repository":{"id":116538338,"uuid":"168414832","full_name":"gabrielecanepa/medium-pocket","owner":"gabrielecanepa","description":"Medium-like Ruby CLI app using the MVC pattern @lewagon","archived":false,"fork":false,"pushed_at":"2019-01-31T16:09:22.000Z","size":4,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-06-26T00:03:45.214Z","etag":null,"topics":["mvc","mvc-pattern","ruby","ruby-exercises"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gabrielecanepa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-30T21:02:26.000Z","updated_at":"2025-03-05T00:41:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"828ce1fa-3b6f-4e3a-b99c-7f65c5145266","html_url":"https://github.com/gabrielecanepa/medium-pocket","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gabrielecanepa/medium-pocket","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielecanepa%2Fmedium-pocket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielecanepa%2Fmedium-pocket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielecanepa%2Fmedium-pocket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielecanepa%2Fmedium-pocket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gabrielecanepa","download_url":"https://codeload.github.com/gabrielecanepa/medium-pocket/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielecanepa%2Fmedium-pocket/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261973725,"owners_count":23238586,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mvc","mvc-pattern","ruby","ruby-exercises"],"created_at":"2024-12-03T04:16:49.426Z","updated_at":"2025-06-26T00:04:13.765Z","avatar_url":"https://github.com/gabrielecanepa.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Background and Objectives\n\nLet's code an entire MVC app. We will create an app to save [Medium](https://medium.com) posts and read them later:\n\n```\nAs a user, I can list all posts I saved\nAs a user, I can add a post I want to read later\nAs a user, I can read a post I saved\nAs a user, I can mark a post as read\n```\n\nA demo's worth a thousand words, so this is the app we want to code:\n\n#### List all the saved posts\n\n```\n------------------------------------\nWelcome to your Medium pocket reader\n------------------------------------\n\n----------------------------\nWhat do you want to do next?\n----------------------------\n1. List posts\n2. Save post for later\n3. Read post\n4. Mark post as read\n5. Exit\n\u003e 1\n\n1. [x] - What’s your goal for learning to code? (Boris Paillard)\n2. [ ] - Teaching Programming is Hard (Boris Paillard)\n```\n\n#### Save a post for later\n\n```\n----------------------------\nWhat do you want to do next?\n----------------------------\n1. List posts\n2. Save post for later\n3. Read post\n4. Mark post as read\n5. Exit\n\u003e 2\n\nPath?\n\u003e le-wagon/this-is-what-a-developer-looks-like-379c261db24d\n\n1. [x] - What’s your goal for learning to code? (Boris Paillard)\n2. [ ] - Teaching Programming is Hard (Boris Paillard)\n3. [ ] - This is what a developer looks like (Rebecca Menat)\n```\n\n#### Read a post\n\n```\n----------------------------\nWhat do you want to do next?\n----------------------------\n1. List posts\n2. Save post for later\n3. Read post\n4. Mark post as read\n5. Exit\n\u003e 3\n\n1. [x] - What’s your goal for learning to code? (Boris Paillard)\n2. [ ] - Teaching Programming is Hard (Boris Paillard)\n3. [ ] - This is what a developer looks like (Rebecca Menat)\nIndex?\n\u003e 2\n\n[...] # this should display the post's entire content with linebreaks between paragraphs!\n```\n\n#### Mark a post as read\n\n```\n----------------------------\nWhat do you want to do next?\n----------------------------\n1. List posts\n2. Save post for later\n3. Read post\n4. Mark post as read\n5. Exit\n\u003e 4\n\n1. [x] - What’s your goal for learning to code? (Boris Paillard)\n2. [ ] - Teaching Programming is Hard (Boris Paillard)\n3. [ ] - This is what a developer looks like (Rebecca Menat)\nWhat index?\n\u003e 2\n\n1. [x] - What’s your goal for learning to code? (Boris Paillard)\n2. [x] - Teaching Programming is Hard (Boris Paillard)\n3. [ ] - This is what a developer looks like (Rebecca Menat)\n```\n\n#### Exit gracefully\n\n```\n----------------------------\nWhat do you want to do next?\n----------------------------\n1. List posts\n2. Save post for later\n3. Read post\n4. Mark post as read\n5. Exit\n\u003e 5\n\nBye bye!\n```\n\nAs you can see, the user only types the `path` of the Medium article. The `path` is the text that comes after the domain name in a url.\n\nFor instance, to save [this article](https://medium.com/le-wagon/this-is-what-a-developer-looks-like-379c261db24d), the path our users need to enter is everything that is after `https://medium.com/` i.e. `le-wagon/this-is-what-a-developer-looks-like-379c261db24d`.\n\nSo how are we going to retrieve the post's author, title and content...?\n\nObviously, we're going to scrape them!\n\n**Question: where are we going to code the scraping part?**\n\nYou'll find the answer at the end of this README.\n\n## Specs\n\nSome specs have already been defined.\nCheck all the specs with the `rake` command or a singol one with `rspec spec/spec_name.rb`.\n\n### Model\n\nAs you know now, you should always start with your model. The model is the Ruby class we need in order to manipulate the data in our program.\n\nHere we want to play around with Medium **posts**, so let's go ahead and create a `Post` class. Before coding it, take the time to ask yourself about:\n\nIts state:\n\n- What do we need to store in a `post` **to be able to serve the user stories**?\n\nThe answer will give you the instance variables.\n\nIts behavior:\n\n- What transformations will we need to perform on a post?\n- Which pieces of state will we need to expose to reading? To writing?\n\nThe answers will give you the public instance methods.\n\nDon't force too much if you can't find all instance variables and methods, you'll find them later when the need emerges while coding the repo and the controller. When you think you're done, test your class in `irb`, fix bugs, and move on to the next class.\n\n### Repository\n\nWe need a repository to **store** our posts in-memory and on our hard drives. This class needs to be coded right after the model, both classes being part of the same **data** brick.\n\nImplement a `Repository` class which will act as a fake database. It should be connected to a `posts.csv` file to make our app persistent.\n\n### Controller\n\nThe controller serves the user stories. Let's have a look at them:\n\n```\nAs a user, I can list all posts I saved\nAs a user, I can add a post I want to read later\nAs a user, I can read a post I saved\nAs a user, I can mark a post as read\n```\n\nRemember that the controller has a role of pivot in the MVC pattern. Having access to the repo and the view from within every action is a necessity (this should help you define instance variables).\n\nFor each user story, you need to code an action (an instance method) in the controller.\n\nThis is the process you need to follow for each action:\n\n- Write pseudo-code to breakdown the problem in small steps that you can easily translate in ruby\n- Remember that each instruction having to do with the data will be delegated to the repo, and every `puts` and `gets` will be handled by the view\n- Coding your actions will make you code your `View` class and its instance methods naturally, when the necessity arises\n- Every time there's an emerging need (we need a new method in the repo or in the model), follow the flow and code it right away\n- Test regularly your code (every 2 or 3 lines of code)\n\n### Router\n\nTry to code the router by yourself! Remember that at the end, we want to call `router.run` in `app.rb` and this should launch our app!\n\n### Tying it all together\n\nWe know that the purpose of the `app.rb` file is to call `router.run`.\nThis means you need to instantiate a `router` which is an instance of our `Router` class. OK so that's a `Router.new(controller)`. That means we need a `controller`... Following this train of thoughts will lead you to the whole code.\n\nWhen you are ready, you can run your program with:\n\n```bash\nruby lib/app.rb\n```\n\n### By the way...\n\nSo where should you code the scraping part of the program? Well, let's re-formulate our question. Our program should be able to instantiate a `Post` with only a `path`.\n\nBut when we instantiate the `post`, we want it to be automatically populated with its title, content, and author. A good place to code it could be in the `Post`'s `initialize` method. But that's not where we're going to code it.\n\nLet's imagine that we add an `Author` model in the picture, and that we want to scrape info about the post's author when scraping the post. The `Post#initialize` method wouldn't be a good choice anymore... Leaving it in the **controller** (where we have access to models and repositories) would be a necessity, so let's code it there!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielecanepa%2Fmedium-pocket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabrielecanepa%2Fmedium-pocket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielecanepa%2Fmedium-pocket/lists"}