├── README.md ├── bugs.md ├── other ├── concepts.md ├── css.md ├── figma.md ├── git.md ├── gitconfig.md ├── html.md ├── terminal.md └── vscode.md ├── rails ├── ajax.md ├── debug.md ├── devise.md ├── forms.md ├── migrations.md ├── models.md ├── react.md ├── resources.md ├── routes.md ├── rspec.md ├── search.md ├── seed.md ├── simple.md ├── style.md ├── tasks_controller.rb ├── testing.md ├── views.md └── websocket.md ├── ruby ├── methods.md ├── oop.md └── operators.md └── setup.md /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## Getting Started with Ruby on Rails 4 | 5 |

6 | 7 | 8 | 9 |

10 | 11 | 🚧 = incomplete
12 | 13 | This guide covers how best to start a new project and implement certain features with Ruby on Rails. It's far from complete, so hit me up if you'd like to contribute! Owing to other commitments, I'm unlikely to maintain it beyond April 2023. 14 | 15 | ## Ruby on Rails 16 | - [Setup](/setup.md) 17 | - [Devise](/rails/devise.md) 18 | - [Models](/rails/models.md) 🚧 19 | - [Migrations](/rails/migrations.md) 20 | - [Rails as a backend API](/rails/react.md) 21 | - [Resources](/rails/resources.md) 🚧 22 | - [Routes](/rails/routes.md) 23 | - [Seed](/rails/seed.md) 🚧 24 | - [Search - with and without AJAX](/rails/search.md) 🚧 25 | - [Simple Form](/rails/simple.md) 🚧 26 | - [Testing](/rails/testing.md) 🚧 27 | - [Websocket](/rails/websocket.md) 🚧 28 | - [AJAX](/rails/ajax.md) 🚧 29 | 35 | 36 | ## Ruby 37 | - [Methods](/ruby/methods.md) 🚧 38 | - [OOP](/ruby/oop.md) 🚧 39 | - [Operators](/ruby/operators.md) 🚧 40 | 41 | ## Other 42 | 43 | #### Figma 44 | - [basics](/other/figma.md) 45 | 46 | #### Terminal 47 | - [terminal commands](/other/terminal.md) 48 | 49 | #### Git 50 | - [git commands](/other/git.md) 51 | - [git concepts](/other/concepts.md) 52 | - [gitconfig](/other/gitconfig.md) 53 | 54 | #### HTML & CSS 55 | - [CSS cheatsheet](/other/css.md) 56 | - [HTML cheatsheet](/other/html.md) 57 | 58 | #### VSCode 59 | - [VSCode shortcuts](/other/vscode.md) 60 | 61 | 76 | -------------------------------------------------------------------------------- /bugs.md: -------------------------------------------------------------------------------- 1 | #### rbenv global 2 | - if using an M1 chip: https://github.com/rbenv/ruby-build/issues/1691) 3 | - more M1: https://stackoverflow.com/questions/65487249/getting-a-warning-when-installing-homebrew-on-macos-big-sur-m1-chip 4 | - check your path: https://stackoverflow.com/questions/10940736/rbenv-not-changing-ruby-version 5 | 6 | #### cannot read/write 7 | `ls -alrth /Users/myusername/` 8 | `ls -alrth /Users/myusername/.config` check if user has permission, or just root 9 | `sudo chown -R username /path-to-folder` 10 | -R here means recursively. 11 | -------------------------------------------------------------------------------- /other/concepts.md: -------------------------------------------------------------------------------- 1 | #### Pull Requests 2 | ##### Commit your branch 3 | 1. (my-feature) `git add .` 4 | 2. (my-feature) `git commit -m 'add this feature'` 5 | 3. (my-feature) `git status` 6 | 7 | ##### Check out master and pull the latest version 8 | 4. (my-feature) `git checkout master` 9 | 5. (master) `git pull origin master` 10 | 11 | ##### Check out your branch again and merge 12 | 6. (master) `git checkout my-feature` 13 | 7. (my-feature) `git merge master` 14 | ##### If there's a conflict 15 | 8. open editor and resolve conflicts 16 | 9. `git add .` 17 | 10. `git commit --no-edit` # commit using the default commit message 18 | 19 |
20 | 21 | #### Git Workflow 22 | - 💜 [Typical Workflow](https://www.doabledanny.com/git-workflows)
23 | - [good habits when working in a team](https://betterprogramming.pub/six-rules-for-good-git-hygiene-5006cf9e9e2) 24 | 25 | #### Rebase 26 | 27 | - [Illustration: Git Rebase vs Merge](https://www.gitkraken.com/learn/git/problems/git-rebase-vs-merge)
28 | - 💜 [What's the difference between 'git merge' and 'git rebase'?](https://stackoverflow.com/questions/16666089/whats-the-difference-between-git-merge-and-git-rebase/16666418#16666418)
29 | - [What's the difference between git pull and git pull --rebase](https://stackoverflow.com/questions/18930527/difference-between-git-pull-and-git-pull-rebase)
30 | - [Stop using git pull, use git fetch and git merge instead](https://longair.net/blog/2009/04/16/git-fetch-and-merge/)
31 | - [git pull --rebase vs --merge](https://sdq.kastel.kit.edu/wiki/Git_pull_--rebase_vs._--merge)
32 | 33 | #### Squash 34 | 35 | - 💜 [What is the difference between merge --squash and rebase?](https://stackoverflow.com/questions/2427238/what-is-the-difference-between-merge-squash-and-rebase) 36 | - [How to squash your commits](https://www.git-tower.com/learn/git/faq/git-squash)
37 | -------------------------------------------------------------------------------- /other/css.md: -------------------------------------------------------------------------------- 1 | ### [Naming conventions](https://www.freecodecamp.org/news/css-naming-conventions-that-will-save-you-hours-of-debugging-35cea737d849/) 2 | ##### section 3 | > .stick-man
4 | ##### components 5 | > .stick-man__head, .stick-man__arms
6 | ##### variants 7 | > .stick-man--blue, stick-man--red
8 | ##### variants of components 9 | > .stick-man__head--small, .stick-man__head--big
10 |
11 | 12 | ### Class selectors 13 | 14 | ##### has one class 15 | > .class-name { 16 | } 17 | 18 | ##### must have multiple classes 19 | > .first-class.second-class { 20 | } 21 | 22 | ##### is in the child element of the parent class 23 | > .parent-class .child-class { 24 | } 25 | -------------------------------------------------------------------------------- /other/figma.md: -------------------------------------------------------------------------------- 1 | # Figma-tips 2 | 3 | ## Common shortcuts 4 | 5 | - Frame `F` or `A` 6 | - Rectangle `R` 7 | - Circle `O` 8 | - Text `T` 9 | - Move tool `V` 10 | - Type tool `T` 11 | - Zoom in `⌘ +` 12 | - Zoom out `⌘ -` 13 | - Shrink `K` 14 | - Zoom 100% `⌘ + 0` 15 | - Toggle grid `⇧ + G` 16 | - Group Selection `⌘ + G` 17 | - Big Nudge `⇧ + direction` 18 | - Duplicate `Option + drag` 19 | - Show/hide tool panel `⌘ + .` 20 | - Show outlines only `⇧ + O` 21 | 22 | ### Additional Shortcuts 23 | - [Figma shortcuts](https://shortcuts.design/toolspage-figma.html) 24 | 25 | ### Padding and Spacing 26 | 27 | 28 | 29 | ## Learn about layout and the 8pt grid system 30 | 31 | Designers love grids, they help create rhythm and structure to your layouts. 32 | 33 | A good place to start is with the 8pt grid system. The 8pt grid is a spacing system which uses multiples of 8. 8, 16, 32, 64, 128 and so on. 34 | 35 | Using a system for sizing and spacing elements creates constraints, prevents the use of arbitrary values and improves the consistency in your designs. 36 | 37 | How does this work in Figma? 38 | 39 | ### Setting up Layout Grids 40 | 41 | First click the hamburger icon in the top left corner of the Figma app. Scroll down to _preferences_ > _nudge amount_ and make sure the value for _small nudge_ is set to 1 and the value for _big nudge_ is set to 8. 42 | 43 | Now create a new frame by pressing `f` on your keyboard, and selecting _Desktop_ > _MacBook Pro_ from the properties panel on the right side of the app. 44 | 45 | With the frame selected, you will see the Layout Grid option, greyed out in the same properties panel on the right. Clicking on the `+` icon or the name will create a new 8pt grid. 46 | 47 | Now when you draw out shapes they will snap to the grid. 48 | 49 | ### Layering grids to create columns and rows 50 | 51 | With the frame selected click the `+` button next to Layout grid once more to create another grid on top. This time hit the icon to the left of ‘Grid (8px)’ and switch grid to columns. 52 | 53 | To keep things simple set _Count_ to 4, _Margin_ to 64 and _Gutter_ to 32. Notice these values all work with the 8pt grid. 54 | 55 | Your columns should be nicely aligned with your 8pt grid below. 56 | 57 | ### Nesting your frames to create interesting layouts 58 | 59 | Unlike with other software, which use fixed artboards, in Figma you can nest frames within frames to create interesting layouts. This is useful designing components with different layout or grids. 60 | 61 | ### Grid and layout resources 62 | - [Intro to The 8-Point Grid System](https://builttoadapt.io/intro-to-the-8-point-grid-system-d2573cde8632) 63 | - [Frames within frames](https://www.figma.com/blog/grid-systems-for-screen-design/#frames-within-frames) 64 | 65 | ## Create a type system 66 | 67 | Oliver Reichenstein once said; “Web design is 95% typography, so it’s only logical to say that a web designer should get good training in the main discipline of shaping written information.“ 68 | 69 | I tend start with a free typeface designed for UI, such as Inter or San Fransisco UI, which are both ideal for prototyping and have a range of weights. 70 | 71 | Approach type exactly how you would in code. Before you start designing, first create a scale of type sizes and line heights, starting with the body text, as this is what there will be most of. Sizes don’t have to be spot on at this stage, you can always change them later. 72 | 73 | For example if my body copy is 20px, I might multiple the text size by 1.6 to get a line-height of 32px. You can actually do this maths directly in the _Line height_ field: `20*1.6` + `Enter`. This is a good line-height for body copy. 74 | 75 | Next tackle the scale for your headings. I tend to set headings in a bolder weight to provide enough contrast between the regular weight of the body text. A line height of 1.2 is usually a good bet for larger heading sizes. 76 | 77 | Creating a system like this creates constraints in your work and helps to keep your designs feeling consistent. 78 | 79 | 80 | ### Type system resources 81 | - [Typography systems in Figma](https://www.figma.com/best-practices/typography-systems-in-figma/) 82 | - [Web Design is 95% Typography](https://ia.net/topics/the-web-is-all-about-typography-period) 83 | 84 | 85 | ## Use the dimensions palette to size things 86 | 87 | You can do maths in the dimensions palette too. This is ideal for sizing elements on your page to your 8pt or 4pt system. 88 | 89 | 90 | ## Understand colour and color styles 91 | 92 | Create a primary palette including brand colour and tints. 93 | 94 | Create greyscale palettes - one for surface colours (light greys or very dark greys for a dark theme) and one for text (dark greys or very light/white for dark themes). 95 | 96 | If the darkest grey you use is say 90% black and your lightest grey is 5% black, then avoid middle greys (40%, 50%, 60%). These are an accessibility nightmare. 97 | 98 | Make sure you use a contrast checker plugin like Able or Stark to check the contrast between your text and the background layer are accessible. 99 | 100 | Create a secondary palette for UI feedback colours (success, error, warning, info alerts). 101 | 102 | If you’re not confident with colour, keep colours to a minimum 103 | 104 | 105 | ### Colour resources 106 | - [Color in Design Systems](https://medium.com/eightshapes-llc/color-in-design-systems-a1c80f65fa3) 107 | - [Color in UI design a practical framework](https://medium.com/@erikdkennedy/color-in-ui-design-a-practical-framework-e18cacd97f9e) 108 | - [A guide to color accessibility in product design](https://medium.com/inside-design/a-guide-to-color-accessibility-in-product-design-516e734c160c) 109 | 110 | ## Use Auto Layout 111 | 112 | Auto layout is a powerful feature that can help you improve your workflow for anything from simple UI elements like buttons to complex responsive UI patterns list navigation and cards. 113 | 114 | Figma has a great demo of how this work. I recommend you have a look and play around. It’s the only way to learn! 😎 115 | 116 | ### Auto layout resources 117 | - [Create dynamic designs with Auto Layout](https://help.figma.com/hc/en-us/articles/360040451373-Create-dynamic-designs-with-Auto-Layout) 118 | - [Quick tips for Auto Layout in Figma](https://uxdesign.cc/quick-tips-for-auto-layout-in-figma-411c639a51b0) 119 | 120 | 121 | ## Use plugins 122 | 123 | Figma has a great developer community who have built some awesome tools that you can use for free. 124 | 125 | So before you start designing flows you might want to check out the Autoflow plugin, which makes it easy to draw flows automatically. Or uses the Unsplash plugin to insert licence free images straight into your designs. 126 | 127 | Another great plugin is 128 | 129 | Plugins can be installed from the project overview in the top left sidebar. Use them, they will save you time and make you more efficient. 130 | 131 | 132 | ### Plugin resources 133 | - [The 15 best Figma plugins for designers](https://uxdesign.cc/the-15-best-figma-plugins-for-designers-so-far-84332ab1a61) 134 | - [Design for everyone with these accessibility-focused plugins](https://www.figma.com/blog/design-for-everyone-with-these-accessibility-focused-plugins/) 135 | - [Behind the Plugins: Matt DesLauriers, Generative Artist & Creative Coder](https://www.figma.com/blog/behind-the-plugins-matt-deslauriers/) 136 | 137 | 138 | ## Join the Figma community 139 | 140 | [On Spectrum](https://spectrum.chat/figma/) 141 | 142 | 143 | -------------------------------------------------------------------------------- /other/git.md: -------------------------------------------------------------------------------- 1 | #### git workflow 2 | 3 |
4 | 5 |
6 | 7 | 💖 [see](https://www.doabledanny.com/git-workflows) 8 |

9 | 10 | #### master > main 11 | * `git config --global init.defaultBranch main` set main as the default branch name for all new Git repositories that you create in the future. 12 | * `git branch -m master main` renames the existing master branch to main 13 | 14 | 15 | #### forking 16 | 17 | 1. fork a branch 18 | 2. `git remote add upstream git@github.com:adrianHards/rails-guide.git` add the remote, call it "upstream" 19 | 3. `git fetch upstream` 20 | 4. `git checkout main` 21 | 5. `git rebase upstream/main` modify your main branch such that any commits you've made that are not already present in upstream/main will be applied on top of it. This effectively rewrites the history of your branch, making it look as though your changes were made directly on top of the latest version of upstream/main. 22 | 23 | #### git remote 24 | 1. create a repo on gitHub 25 | 2. `git init` in folder on local machine 26 | 3. `git remote -v` to check if there are any remotes (a remote is simply a record in a local repository that it's linked to another one) 27 | 4. `git remote rm origin` remove origin from your remotes if necessary 28 | 5. `git remote add origin ` modifying .git/config file in your local repository to link to your GitHub repo 29 | 6. `git push -u origin main` transfer the code from the local repository to the one on GitHub 30 | * the `-u` switch means that these parameters should be saved as default, so next time you won't have to type "origin main", we can simply type 'git push' 31 | 32 | #### git init 33 | `git init` git creates a hidden directory called `.git` that it uses to track all changes to the files in the directory
34 | `rm -rf .git` to remove this file
35 | 36 | #### git add -p 37 | `git add -p` to stage parts of a changed file; 38 | > `y` stage the chunk 39 | > `n` ignore the chunk 40 | > `s` split it into smaller chunks 41 | > `e` manually edit the chunk 42 | > `q` to exit. 43 | 44 | #### git stash 45 | Accidentally been working in your master branch? 46 | * `git stash` 47 | * `git checkout -b new-branch` 48 | * `git stash pop` to bring over all of the changes you made in the master branch into the new branch 49 | 50 | #### git commit 51 | 52 | ##### editor 53 | if you accidentally type `git commit` without `-m`: 54 | * `i` to open editor 55 | * `:wq` to get out 56 | * `nano text.txt` to create a txt file and open the nano editor, `^` means ctrl 57 | * `cat text.txt` will show contents of file 58 | 59 | ##### [git revert or git reset](https://www.freecodecamp.org/news/git-revert-commit-how-to-undo-the-last-commit/) 60 | `git reset --soft HEAD~1` if commit exists only on local; will re-write commit history!
61 | `git revert ` if commit exists remotely; use `git lg` to find commit you want to revert to
62 | 63 | #### git merge 64 | if the terminal asks you to enter a message: 65 | 1. press `i` for insert 66 | 2. write your merge message 67 | 3. `esc` to escape 68 | 4. type `:wq` to write & quit 69 | 5. hit `enter` 70 |
71 | 72 | If you have made a PR and there are conflicts, it is better to resolve them locally. 73 | 1. `git pull origin main` 74 | 2. `git checkout pr-branch` 75 | 3. `git merge main` 76 |
77 | 78 | If there are changes on main you don't want to bring over, then do the following instead: 79 | 1. `git pull origin main` 80 | 2. `git checkout -b resolve-conflict` 81 | 3. `git merge pr-branch` 82 | 4. resolve conflicts, `git add` and `git commit --no-edit` (commit using the default commit message) 83 | 5. `git checkout pr-branch` 84 | 6. `git merge resolve-conflict` 85 | 86 | #### git branch 87 | `git checkout branch-name` to switch to a local branch
88 | `git checkout -b name-of-branch` create new branch and immediately check into it
89 | `git branch -m new-branch-name` change name of current branch
90 | `git branch` to see all local branches
91 | `git branch -r` to see all remote branches
92 | `git branch -a` to see both local and remote branches
93 | `git branch -d branch-name` to delete a local branch
94 | 95 | ##### switch to a remote branch 96 | 1. `git pull` 97 | 2. `git checkout --track origin/branch-name` 98 | 99 | ##### setup remote connections 100 | `git remote -v`
101 | `git remote add origin https://github.com/....git`
102 | 103 | #### git checkout 104 | `git checkout --patch main` checkout specific hunks or lines from a file in the "main" branch from branch you're in 105 | 106 | #### git reset 107 | resetting your local main to remote
108 | 1. create a branch and backup your work before `git checkout main` 109 | 2. `git fetch origin` 110 | 3. `git reset --hard origin/main` to reset the local main branch to the remote repository 111 | 4. `git clean -n` to see which files will be deleted
112 | 5. `git clean -xdf` `-x` removes ignored files, `-d` removes untracked folders, `-f` removes untracked files 113 | 114 | #### git diff 115 | `git diff branch1..branch2` see all differences between two branches
116 | `git difftool` to see differences in VSCode (assuming you have it set up in `code ~/.gitconfig`) 117 | 118 | #### git status 119 | 120 | #### git log 121 | `git cherry -v main` to see logs of branch you're in 122 | `git lg` to see single 123 | 124 | #### RSpec 125 | `bundle exec rspec spec/models/filename_spec.rb` to run a specific file
126 | note, `bundle exec` will run specific versions of Ruby and gems that are specified in your application's Gemfile 127 | -------------------------------------------------------------------------------- /other/gitconfig.md: -------------------------------------------------------------------------------- 1 | #### Le Wagon config 2 | `code ~ /.gitconfig` and [modify](https://github.com/lewagon/dotfiles/blob/master/gitconfig) for custom terminal commands, such as `git lg` and `git sweep` 3 |

4 | 5 | #### Using VSCode to resolve conflicts and see differences 6 | VSCode gives us access to a [merge editor](https://code.visualstudio.com/updates/v1_69#_3-way-merge-editor). Alternatively, we can run something similar from our terminal. First `code ~/.gitconfig` add the following: 7 | 8 | ```c# 9 | [merge] 10 | tool = code 11 | [mergetool "code"] 12 | cmd = code --wait --merge $REMOTE $LOCAL $BASE $MERGED 13 | [mergetool] 14 | keepBackup = false 15 | 16 | [diff] 17 | tool = code 18 | [difftool "code"] 19 | cmd = code --wait --diff $LOCAL $REMOTE 20 | ``` 21 | 22 | now, whenever you run `git mergetool` or `git difftool` from your terminal, you can resolve conflicts or see differences between files in VSCode, respectively. 23 | -------------------------------------------------------------------------------- /other/html.md: -------------------------------------------------------------------------------- 1 | #### [HTML elements reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) 2 | 3 | `` and `` tags look like they have had `display: flex` already applied to them! (i.e. consecutive tags appear side by side) 4 | -------------------------------------------------------------------------------- /other/terminal.md: -------------------------------------------------------------------------------- 1 | #### Navigation 2 | 3 | - `cd ..` change the current working directory to the parent directory 4 | - `cd /` change the current working directory to the root directory of the file system 5 | - `cd -` switch back to the previous working directory that was used before the current one 6 | - `/user/folder/file.py` absolute path, can be used from anywhere 7 | - `user/folder/file.py` relative path 8 | - `more hello.txt | grep 'hello'` displays the contents of the "hello.txt" file, and filters the output to display only the lines that contain the word "hello" 9 | - `find . -name 'example.txt'` there are several things you can do with `find`; here, we're looking for a specific file within the current directory. 10 | 11 | #### [Brew](https://mac.install.guide/homebrew/4.html) 12 | 13 | - `brew update` (update brew) 14 | - `brew upgrade` (upgrade packages) 15 | - `brew list` (list of packages installed) 16 | - `brew deps --tree --installed` (list packages and dependencies) 17 | 18 | #### Previous Commands 19 | 20 | - `fn arrow keys` move the cursor to the beginning or end of a line in the terminal 21 | - `history` display a list of commands that have been executed previously in the terminal session 22 | - `!1041` to select one of these commands by its index 23 | - `ctrl r` and start typing a previously used command; type `ctrl r` again to move on to the next suggestion 24 | - `echo $PATH` displays the value of PATH, a list of directories that the operating system searches when you enter a command in the terminal 25 | - `/user/folder/file.py` the absolute path (complete path that specifies the exact location of a file or directory in the file system, starting from the root directory) 26 | - `user/folder/file.py` the relative path (location of a file or directory relative to the current working directory) 27 | - `which pipenv` display the location of an executable file in the system 28 | 29 | #### Directory 30 | 31 | - `pwd` print current directory 32 | - `open ` to open folder from terminal of target directory 33 | - `ls Desktop` list all contents of `Desktop` 34 | - `ls -a` list with -A flag to see all hidden dotfiles i.e. `.` files 35 | - `ls -l` the -l flag shows us the `long format` of files, that is, their extra information, which we can combine with others flags e.g. `ls -lA` 36 | - `man ls` man is the `manual` command to get help with another command; type `q` when done 37 | - `pipenv --help` sometimes instead of manual we can use the `--help` command 38 | 39 | #### Creation 40 | 41 | - `touch ` to create a file 42 | - `echo "Git is Awesome" > gitText` to create a file and add text to it in one line 43 | - `rm ` to remove the file 44 | - `cp someFile newFile` to copy a particular file 45 | - `mkdir ` to create a directory 46 | - `rmdir ` to remove the directory, but if the directory contains files ... 47 | - `rm -r ` the switch, `-r` and tells `rm` to recursively remove all files within the directory as well as the directory itself 48 | - `mv newFile ../newFile` move a file to one directory above 49 | - `mv newFile ../someFile` as above, but rename said file to `someFile` 50 | - `mv newFile newerFile` simply rename the file without moving it 51 | 52 | #### Node 53 | 54 | - `nvm install --lts` (install the latest Long-Term Support (LTS) version of Node.js available through nvm) 55 | - `nvm install 16.15.1` (install a particular version of Node; nvm allows us install and switch between multiple versions of Node.js) 56 | - `nvm alias default 16.15.1` (set default version when you open a new terminal window) 57 | - `nvm use default` (switches to the default version of Node.js that you set with the previous command) 58 | 59 | #### Gem 60 | 61 | - `gem -v` (version of RubyGems that is currently installed) 62 | - `gem update --system` (updates RubyGems itself to the latest version available) 63 | - `gem list` (lists all of the gems that are currently installed on your system) 64 | - `gem outdated` (lists all of the gems that are currently installed on your system, but have a newer version available) 65 | - `gem update` (updates all of the gems that are currently installed on your system to their latest version) 66 | - `gem update ` (update a specific gem) 67 | 68 | #### Python 69 | 70 | - `python3` load a Python REPL 71 | - `exit()` to exit 72 | 73 | #### PID 74 | 75 | - `lsof -i :3000` list all open connections on port 3000 76 | - `lsof -i | grep ruby` list all open connections with string ruby 77 | - `kill -9 ` send the SIGKILL signal, which forces the process to terminate immediately (last resort) 78 | 79 | #### Ruby 80 | 81 | - `which ruby` where the Ruby executable is located on your system 82 | - `which -a ruby` where all Ruby executables are located on your system 83 | - `ruby -v` version number of the Ruby interpreter that is currently installed on your system 84 | - `irb` "interactive Ruby" and starts a console session where you can enter Ruby code 85 | - `rbenv install -l` list available versions of Ruby 86 | - `rbenv install ` install the specified version of Ruby 87 | - `rbenv global ` set the installed version as the global version 88 | - `gem env home` see location of installed gems 89 | -------------------------------------------------------------------------------- /other/vscode.md: -------------------------------------------------------------------------------- 1 | ### VSCode/General Keyboard Shortcuts 2 | 3 | * drag file into terminal to get its path 4 | * `cmd ⇧ F` to search all files in current directory for a particular bit of text 5 | * `cmd P` to find file in current directory with particular name
6 | 7 | Select a line or multiple lines of code and then click:
8 | * `fn backspace` backwards deletion 9 | * `cmd x` deletes the entire line 10 | * `option arrow key` to move the code up ↑ or down ↓ a line 11 | * `⌘ /` to comment out your code, no matter which language you are using 12 | * `⇥` to indent the line(s) to the right or `⇧ ⇥` to reverse indent 13 | * `⌃ ~` to open a new terminal. Alternatively, you can use Replit to play around with small snippets of code. 14 | * click on a word and then `⌘ d` to find and select all matching words in your code, then delete or start typing what you want to replace the word with 15 | fn ⌫ to reverse delete 16 | * `option z` toggle size to content width (for when you have lines that are far too long!) 17 | 18 | ### Shortcuts 19 | 20 | #### HTML 21 | * `div.classname` followed with tab will automatically generate class names with a tag 22 | * `pe` tab for `<%= %>` 23 | * `er` tab for `<% %>` 24 | -------------------------------------------------------------------------------- /rails/ajax.md: -------------------------------------------------------------------------------- 1 | ### routes 2 | ```ruby 3 | # no nesting 4 | post "upload", to: "messages#upload", as: upload 5 | ``` 6 | 7 | ### HTML 8 | ```html.erb 9 | # messages/new 10 | 11 | 12 | 13 | 14 |
15 | ``` 16 | 17 | ### rails controller 18 | ```ruby 19 | def upload 20 | # puts params.inspect 21 | 22 | if params[:file].content_type.start_with?("image") 23 | # Upload image to Cloudinary 24 | result = Cloudinary::Uploader.upload(params[:file]) 25 | # Get the secure URL of the uploaded image 26 | file_url = result["secure_url"] 27 | elsif params[:file].content_type.start_with?("video") 28 | result = Cloudinary::Uploader.upload_large(params[:file], resource_type: :video) 29 | file_url = result["secure_url"] 30 | else 31 | # Unsupported file type 32 | render json: {error: "Unsupported file type"}, status: 400 33 | end 34 | 35 | # Respond with the image URL as JSON 36 | respond_to do |format| 37 | format.html 38 | format.json { render json: {file_url: file_url} } 39 | end 40 | rescue => error 41 | # Log the error and respond with an error message 42 | Rails.logger.error("Error uploading image to Cloudinary: #{error.message}") 43 | render json: {error: "Unable to upload image"}, status: 500 44 | end 45 | ``` 46 | 47 | ### cloudinary.js 48 | ```js 49 | const cloudinary = () => { 50 | const imageUpload = document.getElementById("image-upload"); 51 | const uploadButton = document.getElementById("upload-button"); 52 | const mediaPreview = document.getElementById("media-preview"); 53 | 54 | uploadButton.addEventListener("click", function() { 55 | const file = imageUpload.files[0]; 56 | const formData = new FormData(); 57 | formData.append('file', file, encodeURIComponent(file.name)); 58 | // console.log(formData.get("image")) 59 | 60 | const reader = new FileReader(); 61 | let isImage = true; 62 | reader.readAsDataURL(file); 63 | reader.onload = function() { 64 | if (reader.result.startsWith("data:video")) { 65 | isImage = false; 66 | } 67 | } 68 | 69 | fetch(`/upload`, { 70 | method: "POST", 71 | headers: { 72 | "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 73 | // why not to include content type!, see: https://stackoverflow.com/questions/40685120/invalid-request-parameters-invalid-encoding-when-upload-file-to-rails-api-onl 74 | // "Content-Type": "multipart/form-data", 75 | "Accept": "application/json" 76 | }, 77 | body: formData 78 | }) 79 | .then(response => response.json()) 80 | .then(data => { 81 | const fileUrl = data.file_url; 82 | 83 | if (isImage) { 84 | const imageElement = document.createElement("img"); 85 | imageElement.src = fileUrl; 86 | mediaPreview.appendChild(imageElement); 87 | } else { 88 | const videoElement = document.createElement("video"); 89 | videoElement.src = fileUrl; 90 | videoElement.controls = true; 91 | mediaPreview.appendChild(videoElement); 92 | } 93 | 94 | }) 95 | .catch(error => { 96 | console.error("Error:", error); 97 | }); 98 | }); 99 | } 100 | 101 | export { cloudinary } 102 | 103 | ``` 104 | ### application.js 105 | ```js 106 | import "@hotwired/turbo-rails" 107 | import "./controllers" 108 | import "bootstrap" 109 | 110 | import { addMail } from './messages.js' 111 | import { fireHower } from './firefly_hover.js' 112 | import { dateToAll } from './date_to_all.js' 113 | import { cloudinary } from './cloudinary.js' 114 | 115 | document.addEventListener('turbo:load', () => { 116 | addMail() 117 | fireHower() 118 | dateToAll() 119 | cloudinary() 120 | }); 121 | ``` 122 | 123 | 169 | -------------------------------------------------------------------------------- /rails/debug.md: -------------------------------------------------------------------------------- 1 | https://www.bootrails.com/blog/rails-debug-with-ruby-debug/ 2 | -------------------------------------------------------------------------------- /rails/devise.md: -------------------------------------------------------------------------------- 1 | #### Gemfile 2 | `gem "devise"` 3 | #### Terminal 4 | `bundle install`
5 | `rails generate devise:install`
6 | `rails generate devise User`
7 | `rails db:migrate`
8 | ##### customise devise views: 9 | `rails generate devise:views` 10 | 11 | #### protect all routes by default: 12 | ```ruby 13 | # app/controllers/application_controller.rb 14 | class ApplicationController < ActionController::Base 15 | before_action :authenticate_user! 16 | end 17 | ``` 18 | 19 | #### skip for some pages 20 | ```ruby 21 | # app/controllers/pages_controller.rb 22 | class PagesController < ApplicationController 23 | skip_before_action :authenticate_user!, only: :home 24 | 25 | def home 26 | end 27 | end 28 | ``` 29 | #### adding additional attributes to User model 30 | 1. Create a migration to add the additional columns to the users table. 31 | 2. Add new fields in Devise’s sign up and account update forms. 32 | 3. Update the controller: 33 | ```ruby 34 | class ApplicationController < ActionController::Base 35 | # [...] 36 | before_action :configure_permitted_parameters, if: :devise_controller? 37 | 38 | def configure_permitted_parameters 39 | # For additional fields in app/views/devise/registrations/new.html.erb 40 | devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name]) 41 | 42 | # For additional in app/views/devise/registrations/edit.html.erb 43 | devise_parameter_sanitizer.permit(:account_update, keys: [:first_name, :last_name]) 44 | end 45 | end 46 | ``` 47 | #### Routes 48 | With Devise, actions, such as sign_in action, are not defined in the User controller. Instead, it is defined in the Devise SessionsController, which is automatically generated when you run the rails generate devise User command. 49 | 50 | When a user submits their login credentials, Devise automatically routes the request to the appropriate controller action. By default, the create action in the SessionsController handles the login logic. 51 | 52 | `devise_for :users` in routes is doing the following: 53 | 54 | ``` 55 | new_user_session GET /users/sign_in devise/sessions#new 56 | user_session POST /users/sign_in devise/sessions#create 57 | destroy_user_session DELETE /users/sign_out devise/sessions#destroy 58 | new_user_password GET /users/password/new devise/passwords#new 59 | edit_user_password GET /users/password/edit devise/passwords#edit 60 | user_password PATCH /users/password devise/passwords#update 61 | POST /users/password devise/passwords#create 62 | cancel_user_registration GET /users/cancel devise/registrations#cancel 63 | new_user_registration GET /users/sign_up devise/registrations#new 64 | edit_user_registration GET /users/edit devise/registrations#edit 65 | user_registration PATCH /users devise/registrations#update 66 | DELETE /users devise/registrations#destroy 67 | POST /users devise/registrations#create 68 | ``` 69 | -------------------------------------------------------------------------------- /rails/forms.md: -------------------------------------------------------------------------------- 1 | ### [Form With](https://guides.rubyonrails.org/form_helpers.html) 2 | 3 | ### [Simple Form](https://github.com/heartcombo/simple_form) 4 | -------------------------------------------------------------------------------- /rails/migrations.md: -------------------------------------------------------------------------------- 1 | #### Add to an existing table 2 | `rails generate migration AddAdminToUsers admin:boolean` 3 | 4 | ```ruby 5 | def change 6 | add_column :users, :admin, :boolean, default: false 7 | end 8 | ``` 9 | 10 | #### Update a column's data type 11 | `rails generate migration ChangeColumnTypeInTable` 12 | 13 | ```ruby 14 | def change 15 | change_column :bookings, :content, :text 16 | end 17 | ``` 18 | #### Remove a column 19 | `rails generate migration RemoveColumnFromTable` 20 | ```ruby 21 | def change 22 | remove_column :table_name, :column_name 23 | end 24 | ``` 25 | #### Add reference and foreign key 26 | `rails generate migration AddForeignKeyToPosts` 27 | ```ruby 28 | def change 29 | add_reference :reviews, :booking, null: false, foreign_key: true 30 | end 31 | ``` 32 | 33 | #### Add a foreign key 34 | `rails generate migration AddForeignKeyToPosts` 35 | ```ruby 36 | def change 37 | add_foreign_key :posts, :users 38 | end 39 | ``` 40 | #### Rename a table 41 | `rails generate migration RenameOldTableNameToNewTableName` 42 | ```ruby 43 | def change 44 | rename_table :old_table_name, :new_table_name 45 | end 46 | ``` 47 | 48 | #### Rename a column 49 | `rails generate migration RenameOldColumnNameToNewColumnNameInTableName` 50 | ```ruby 51 | def change 52 | rename_column :table_name, :old_column_name, :new_column_name 53 | end 54 | ``` 55 | #### If all else fails ... 56 | 1. delete your `schema.rb` file 57 | 2. edit existing migration files as needed 58 | 3. `rails db:drop` 59 | 4. `rails db:create db:migrate` 60 | -------------------------------------------------------------------------------- /rails/models.md: -------------------------------------------------------------------------------- 1 | #### Enum 2 | 3 | Use enum to define a limited set of options for an attribute and helps ensure that only valid values are assigned to it. 4 | ```ruby 5 | class User < ApplicationRecord 6 | enum role: { admin: 0, moderator: 1, user: 2 } 7 | end 8 | ``` 9 | The keys in the hash represent the named values, and their values represent the integer values that will be stored in the database. You can assign values to the attribute using the named values: 10 | ```ruby 11 | user = User.new(role: :admin) 12 | user.role # => "admin" 13 | user.role = :moderator 14 | user.role # => "moderator" 15 | ``` 16 | You can also use various methods generated by the enum to check which values are available and to retrieve records that have a particular value: 17 | ```ruby 18 | User.roles # => { admin: 0, moderator: 1, user: 2 } 19 | User.admins # => returns all users with role == 0 (i.e. "admin") 20 | user.admin? # => true if user.role == "admin" 21 | ``` 22 | -------------------------------------------------------------------------------- /rails/react.md: -------------------------------------------------------------------------------- 1 | # Rails as a Backend API 2 | 3 | **Rails** will serve as the backend/server-side component of our application. The backend will handle the data storage and retrieval, processing, and other logic for our application. **React** will serve as the frontend/client-side component of our application. The frontend will handle the user interface, interaction, and display of data for our application. 4 | 5 | To allow the frontend and backend components to communicate and exchange data, we will be using an API (Application Programming Interface). An API is a set of rules and protocols that define how the frontend and backend components will interact with each other. The frontend will send HTTP requests to the backend, and the backend will respond with the requested data. See [here](https://syllabus.codeyourfuture.io/js-core-3/week-2/lesson). 6 | 7 |
8 | 9 | # Rails 10 | 11 | ## Step 1. Creating the [Rails API](https://guides.rubyonrails.org/api_app.html)
12 | 13 | ``` 14 | rails new rails-backend --api -d postgresql 15 | cd rails-backend 16 | ``` 17 | 18 | - `--api` inherits ApplicationController from ActionController::API instead of ActionController::Base (thus omitting views, helpers, and assets) 19 | 20 |
21 | 22 | ## Step 2. Create a resource 23 | Here, `rails g resource` generates a migration file, a model, a controller and the routes for the given class, but does not populate these files with anything else (unlike scaffold). 24 | 25 | ``` 26 | bin/rails g resource movie title:string description:text 27 | bin/rails db:create 28 | bin/rails db:migrate 29 | ``` 30 | 31 | This will generate: 32 | 33 | ``` 34 | invoke active_record 35 | create db/migrate/20230204163820_create_movies.rb 36 | create app/models/movie.rb 37 | invoke test_unit 38 | create test/models/movie_test.rb 39 | create test/fixtures/movies.yml 40 | invoke controller 41 | create app/controllers/movies_controller.rb 42 | invoke test_unit 43 | create test/controllers/movies_controller_test.rb 44 | invoke resource_route 45 | route resources :movies 46 | ``` 47 | 48 | ### Step 2.1 Setup the routes 49 | Without specifying the format, Rails will return data in the HTML format by default, so: 50 | ```ruby 51 | #config/routes.rb 52 | Rails.application.routes.draw do 53 | resources :movies, defaults: {format: :json} 54 | end 55 | ``` 56 | or 57 | ```ruby 58 | defaults format: :json do 59 | resources :movies 60 | end 61 | ``` 62 | 63 | ### Step 2.2 Seed the database 64 | 65 | ```ruby 66 | # db/seeds.rb 67 | puts "destroying all movies" 68 | 69 | Movie.destroy_all 70 | 71 | puts "there are #{Movie.all.count} movies" 72 | 73 | puts "creating 3 movies" 74 | 75 | increment = 1 76 | 3.times do 77 | Movie.create(title: "Movie #{increment}", description: "Description of movie #{increment}") 78 | increment += 1 79 | end 80 | 81 | puts "created #{Movie.all.count} movies" 82 | ``` 83 | 84 | ``` 85 | bin/rails db:seed 86 | ``` 87 | 88 | ### Step 2.3 Setup the controller 89 | 90 | ```ruby 91 | def index 92 | @movies = Movie.all 93 | render json: @movies 94 | end 95 | ``` 96 | Or add `gem 'responders'` to your Gemfile, run `bin/bundle install` and add: 97 | 98 | ```ruby 99 | # controllers/movies_controller.rb 100 | class MoviesController < ApplicationController 101 | respond_to :json, :xml, :html 102 | 103 | def index 104 | @movies = Movie.all 105 | respond_with @movies 106 | end 107 | end 108 | ``` 109 | 110 | Run `bin/rails s` to start the server and visit http://localhost:3000/movies; you should see JSON (in this example, an array of each of our movie instances). 111 | 112 | #### 113 | 114 | ## Step 3. Setup CORS 115 | 116 | CORS stands for Cross-Origin Resource Sharing. It is a security feature implemented by web browsers that restricts the ability of a web page to make requests to a domain different to the one that served the web page (in our case, localhost:3000 vs 3001). This is done to prevent malicious websites from making unauthorized requests to other domains, which could result in the theft of sensitive data or other security risks. 117 | 118 | Add `gem 'rack-cors'` to your gemfile then run `bin/bundle install` 119 | 120 | `touch config/initializers/cors.rb` and add the following: 121 | 122 | ```ruby 123 | # initializers/cors.rb 124 | Rails.application.config.middleware.insert_before 0, Rack::Cors do 125 | allow do 126 | origins 'http://localhost:3001' 127 | resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] 128 | end 129 | end 130 | ``` 131 | 132 | ## Step 4. Change your default port in development 133 | 134 | Let's change the default port for Rails so it doesn't conflict with React (which we'll allow to occupy 3000). 135 | 136 | By specifying a different port, we can run multiple web applications on the same device, each accessible via a unique port number. For example, we will have a Rails API running on port 3001 and a React front-end running on port 3000. We'll then configure the front-end to communicate with the API by sending requests to http://localhost:3001. 137 | 138 | ```ruby 139 | # config/puma.rb 140 | port ENV.fetch("PORT", 3001) 141 | ``` 142 | 143 | # React 144 | 145 | ## Step 5. create-react-app 146 | 147 | ``` 148 | npx create-react-app react-frontend 149 | cd react-frontend 150 | ``` 151 | 152 | ## Step 6. Fetch data 153 | 154 | ```js 155 | # src/App.js 156 | import React, { useState, useEffect } from 'react'; 157 | 158 | function App() { 159 | const [movies, setMovies] = useState([]); 160 | 161 | useEffect(() => { 162 | fetch('http://localhost:3001/movies') 163 | .then(response => response.json()) 164 | .then(data => setMovies(data)); 165 | }, []); 166 | 167 | return ( 168 |
    169 | {movies.map(movie => ( 170 |
  • 171 | {movie.title}: {movie.description} 172 |
  • 173 | ))} 174 |
175 | ); 176 | } 177 | 178 | export default App; 179 | ``` 180 | 181 | Restart both the Rails and React servers, each in a different terminal session starting with Rails: 182 | 183 | ``` 184 | bin/rails s 185 | npm start 186 | ``` 187 | 188 | And we're done! Check http://localhost:3000/ to see a list of all your movies! 189 | 190 | ## Improved with Axios 191 | 192 | ```js 193 | import React, { useState, useEffect } from 'react'; 194 | import axios from 'axios'; 195 | 196 | function App() { 197 | const [movies, setMovies] = useState([]); 198 | 199 | useEffect(() => { 200 | axios 201 | .get('http://localhost:3001/movies') 202 | .then(response => { 203 | console.log(response.data); 204 | setMovies(response.data); 205 | }) 206 | .catch(error => { 207 | console.log(error); 208 | }); 209 | }, []); 210 | 211 | return ( 212 |
    213 | {movies.map(movie => ( 214 |
  • 215 | {movie.title}: {movie.description} 216 |
  • 217 | ))} 218 |
219 | ); 220 | } 221 | 222 | export default App; 223 | ``` 224 | 225 |

226 | 227 |

228 | -------------------------------------------------------------------------------- /rails/resources.md: -------------------------------------------------------------------------------- 1 | #### Custom routes 2 | Get method to define a route that maps the URL /contact to the contact action of the PagesController. `as:` creates a named route that you can use with a path helper. 3 | ```ruby 4 | get 'contact', to: 'pages#contact' as: :contact_us 5 | ``` 6 | 7 | ```ruby 8 | # erb 9 | <%= link_to 'Contact Us', contact_us_path %> 10 | ``` 11 | 12 | #### Delete 13 | In general, you should nest a resource if it belongs to or is owned by another resource. But in the case of DELETE, you only need to capture the ID of the child resource, and not the ID of the parent resource, so you can define the route outside of the nested block: 14 | 15 | ```ruby 16 | resources :posts 17 | delete '/posts/:id', to: 'posts#destroy', as: 'delete_post' 18 | ``` 19 | The as: `delete_post` option gives the route a named helper method, so you can generate links or redirects to it using `delete_post_path(:id)`. 20 | -------------------------------------------------------------------------------- /rails/routes.md: -------------------------------------------------------------------------------- 1 | #### Member and Collection 2 | ```ruby 3 | resources :users do 4 | member do 5 | get :profile 6 | end 7 | 8 | collection do 9 | get :created_after 10 | end 11 | end 12 | ``` 13 | 14 | ##### Member 15 | The profile route is added to the member block, which means it operates on a single user. This route maps to the profile action in the UsersController, and can be accessed via the URL `/users/:id/profile`. For example, if the id of the user is 123, the URL would be `/users/123/profile`. 16 | 17 | ##### Collection 18 | The created_after route is added to the collection block, which means it operates on the entire collection of users. This route maps to the search action in the UsersController, and can be accessed via the URL /users/created_after. 19 | 20 |
21 | 22 | #### Custom route 23 | 24 | `get 'users/:id/bookings', to: 'users#bookings', as: 'user_bookings'`
25 | `<%= link_to 'My Bookings', user_bookings_path(current_user) %>` 26 | 27 | ```ruby 28 | # user controller 29 | def bookings 30 | @user = User.find(params[:id]) 31 | @bookings = @user.bookings 32 | end 33 | ``` 34 |
35 | 36 | #### Manually 37 | ```ruby 38 | #%i[new create] 39 | resources :users, only: [:new, :create] do 40 | resources :posts, except: :destroy 41 | end 42 | resources :posts, only: :destroy 43 | ``` 44 | 45 | ```ruby 46 | # Users 47 | get '/users/new', to: 'users#new' 48 | post '/users', to: 'users#create' 49 | get '/users/:id', to: 'users#show', as: 'user' 50 | get '/users/:id/edit',to: 'users#edit', as: 'edit_user' 51 | patch '/users/:id', to: 'users#update' 52 | delete '/users/:id', to: 'users#destroy' 53 | 54 | # Posts 55 | get '/users/:user_id/posts', to: 'posts#index' 56 | post '/users/:user_id/posts', to: 'posts#create' 57 | get '/users/:user_id/posts/new', to: 'posts#new', as: 'new_post' 58 | get '/users/:user_id/posts/:id', to: 'posts#show', as: 'post' 59 | get '/users/:user_id/posts/:id/edit', to: 'posts#edit', as: 'edit_post' 60 | patch '/users/:user_id/posts/:id', to: 'posts#update' 61 | delete '/posts/:id', to: 'posts#destroy', as: 'destroy_post' 62 | ``` 63 | In the case of the update action in Rails controllers, the default behavior is to use a PATCH request. 64 | PATCH verb is used to update an existing resource. Any fields that are not included in the request will be left unchanged. 65 | -------------------------------------------------------------------------------- /rails/rspec.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rails/search.md: -------------------------------------------------------------------------------- 1 | 2 | ## Movies 3 | Lets assume we want to search our database of movies and update our index every time there is a `keyup` event **without** a page refresh. Our html is made up of three files, the index and two partials. 4 | 5 | ### Views 6 | 7 | ##### index.html.erb 8 | On the index.html.erb file we attach a target to the form itself and the search bar. Note that the stimulus controller, `search-movies`, contains all of the targets and the `<%= render "list", movies: @movies %>`. Note we pre-fill the input field with the value of the :query parameter if it is present in the URL query string. Finally, we use the Rails form helper [form_with](https://guides.rubyonrails.org/form_helpers.html). 9 | ```html.erb 10 |
11 | <%= form_with url: movies_path, method: :get, html: {class: "mb-4", data: { search_movies_target: 'form' }} do |f| %> 12 | <%= f.label :query, "Type a movie title" %> 13 | <%= f.text_field :query, 14 | class: "form-control", 15 | value: params[:query], 16 | data: { 17 | search_movies_target: 'input', 18 | action: 'keyup->search-movies#update' 19 | } 20 | %> 21 | <% end %> 22 | 23 | <%= render "list", movies: @movies %> 24 |
25 | ``` 26 | Lets also add a target to the movies list, which we'll update with AJAX as the user searches a movie. Notice it still falls under _view_ of the `search-movies` controller. 27 | ##### _list.html.erb 28 | ```html.erb 29 |
30 |
31 | <% movies.each do |movie| %> 32 | <%= render "movie_infos", movie: movie %> 33 | <% end %> 34 |
35 |
36 | ``` 37 | Finally we have the movie card partial, which we'll add a new stimulus controller to which we'll use to update a movie with AJAX. This will therefore involve a PATCH request. This will be performed via a simple form, which is initially hidden until the Bootstrap class d-none is removed via the displayForm action. We add the target `card` to the movie-card, as this will be what is updated. An action that reveals the edit form. And an action when a user submits the form. 38 | 39 | ##### _movie_infos.html.erb 40 | ```html.erb 41 |
42 | <%= image_tag movie.image_url %> 43 | 44 |
45 |

<%= movie.title

46 | 49 |
50 | 51 | <%= simple_form_for movie, html: {class: "d-none", data: { edit_movie_target: 'form', action: 'submit->edit-movie#update' }} do |f| %> 52 | <%= f.input :title %> 53 | <%= f.input :year %> 54 | <%= f.submit "update movie", class: "btn btn-primary" %> 55 | <% end %> 56 |
57 | ``` 58 | 59 | ## Quick notes! 60 | * you might need to pass the [CSRF](https://developer.mozilla.org/en-US/docs/Glossary/CSRF) token when making a PATCH request (see [here](https://github.com/adrianHards/rails-guide/blob/main/rails/ajax.md) or below) 61 | * A **URI** is a string of characters that identifies a resource, while a **URL** is a type of URI that identifies the location of a resource and specifies the protocol to use to access it.
62 | * use `rails g stimulus search-movies` to generate your Stimulus controller
63 | * check your target values with: 64 | ```js 65 | static targets = ["form", "input", "list"] 66 | 67 | connect() { 68 | console.log(this.formTarget) 69 | console.log(this.inputTarget.value) // whatever the use has typed in to the search bar 70 | console.log(this.listTarget) 71 | } 72 | ``` 73 | * `params` is a hash that contains the parameters submitted in the request. 74 | * the `require` method ensures that the :movie parameter is present, and raises an error if it is not. This is a security measure to prevent unauthorized parameters from being passed in the request. 75 | * The permit method specifies a list of allowed parameters for the Movie model. In this case, only the :title and :year parameters are allowed. This is also a security measure that prevents users from submitting unwanted or malicious parameters. 76 | 77 | ## Search 78 | ### Stimulus controller 79 | * `this.formTarget.action` will tell you what the form is submitting to (i.e. the URI) by default along with any params (e.g. `http://localhost:3000/movies?query=batman`) 80 | 81 | ##### search_movies_controller.js 82 | 83 | Here, we define the update action. We trigger an HTTP request so that our web browser can communicate with our server/database. 84 | 85 | ```js 86 | update() { 87 | const url = `${this.formTarget.action}?query=${this.inputTarget.value}` 88 | fetch(url, { 89 | headers: { "Accept": "text/plain" } 90 | }) // pass header to say what format you want to receive response back in 91 | .then(response => response.text()) // the response is expected to be plain text so we parse with .text() 92 | .then((data) => { 93 | this.listTarget.outerHTML = data // replace whole piece of DOM with data 94 | }) 95 | } 96 | ``` 97 | ### Rails controller 98 | ##### movies_controller.rb 99 | 100 | ```ruby 101 | def index 102 | @movies = Movie.order(year: :desc, title: :asc) 103 | 104 | if params[:query].present? 105 | @movies = @movies.where("title ILIKE ?", "%#{params[:query]}%") 106 | end 107 | 108 | respond_to do |format| 109 | # if HTML provide full index.html.erb page 110 | format.html 111 | # if text, render list partial only, not whole page, where locals are @movies as needed in the partial coming from the above search 112 | format.text { render partial: "movies/list", locals: { movies: @movies }, formats: [:html] } 113 | # without formats: [:html] would be looking for a .txt partial 114 | end 115 | end 116 | ``` 117 | ## Update 118 | ### Stimulus Controller 119 | ##### search_movies_controller.js 120 | ```js 121 | static targets = ["infos", "form", "card"] 122 | 123 | displayForm() { 124 | this.infosTarget.classList.add("d-none") 125 | this.formTarget.classList.remove("d-none") 126 | } 127 | 128 | update(event) { 129 | // stop a page refresh 130 | event.preventDefault(); 131 | console.log(new FormData(this.formTarget)) 132 | const url = this.formTarget.action; 133 | // accept plain text; fetch does get by default, but we need a PATCH; body is what user filled out in form and pass to backend 134 | // new FormData object is built in to js; want to pass the HTML form and values from user, i.e. this.formTarget 135 | fetch(url, { 136 | method: "PATCH", 137 | headers: { "Accept": "text/plain" }, 138 | body: new FormData(this.formTarget) 139 | }) 140 | .then(response => response.text()) 141 | .then((data) => this.cardTarget.outerHTML = data) 142 | } 143 | ``` 144 | ### Rails Controller 145 | ##### movies_controller.rb 146 | ```ruby 147 | def update 148 | @movie = Movie.find(params[:id]) 149 | @movie.update(movie_params) 150 | 151 | respond_to do |format| 152 | format.html { redirect_to movies_path } # if HTML requested, redirect to index 153 | format.text { render partial: "movies/movie_infos", locals: { movie: @movie }, formats: [:html] } 154 | end 155 | end 156 | 157 | private 158 | 159 | # sanitized hash 160 | def movie_params 161 | params.require(:movie).permit(:title, :year) 162 | end 163 | ``` 164 | Finally, look into [errors](https://kitt.lewagon.com/camps/1122/lectures/06-Projects%2F02-i18n#source) if you want edit form to not be hidden on submit if errors are present. 165 | 166 | ## Other 167 | 168 | #### Form 169 | ```html.erb 170 | <%= form_with url: dogs_path, method: :get, class: "d-flex" do %> 171 | <%= date_field_tag :start_date, 172 | value: params[:start_date], 173 | class: "form-control", 174 | placeholder: "Start date" 175 | %> 176 | <%= date_field_tag :end_date, 177 | value: params[:end_date], 178 | class: "form-control", 179 | placeholder: "End date" 180 | %> 181 | <%= submit_tag "Search", class: "btn btn-primary" %> 182 | <% end %> 183 | ``` 184 | 185 | #### SQL, page refresh: 186 | 187 | ```ruby 188 | if params[:start_date].present? 189 | start_date = params[:start_date].to_date.strftime("%Y-%m-%d") 190 | end_date = params[:end_date].to_date.strftime("%Y-%m-%d") 191 | @dogs = Dog.where( 192 | "id NOT IN ( 193 | SELECT dog_id 194 | FROM bookings 195 | WHERE start <= ? AND \"end\" >= ? 196 | )", 197 | end_date, start_date 198 | ) 199 | raise 200 | else 201 | @dogs = Dog.all 202 | end 203 | ``` 204 | Docs: 205 | > [pg_search](https://github.com/Casecommons/pg_search) 206 | 207 | #### pg_search, page refresh: 208 | ```ruby 209 | # model 210 | class Restaurant < ApplicationRecord 211 | include PgSearch::Model 212 | 213 | pg_search_scope :search_available_between, against: [], using: { 214 | tsearch: { any_word: true } 215 | } 216 | 217 | def self.available_between(start_date, end_date) 218 | where.not( 219 | id: Booking.where('start_date <= ? AND end_date >= ?', end_date, start_date).select(:restaurant_id) 220 | ).search_available_between 221 | end 222 | end 223 | 224 | # controller 225 | def index 226 | if params[:query].present? 227 | start_date = params[:start_date] 228 | end_date = params[:end_date] 229 | @restaurants = Restautant.available_between(start_date, end_date) 230 | else 231 | @restaurants = Restaurant.all 232 | end 233 | end 234 | ``` 235 | 236 | 237 | #### AJAX: 238 | 239 | ```html.erb 240 | 241 | 242 | 243 |
244 | <% @restaurants.each do |restaurant| %> 245 |
246 |

<%= restaurant.name %>

247 |

<%= restaurant.description %>

248 |
249 | <% end %> 250 |
251 | ``` 252 | 253 | ```js 254 | // search stimulus controller 255 | static targets = ["restaurants"]; 256 | 257 | async search() { 258 | const query = this.element.value; 259 | 260 | const response = await fetch(`/restaurants?query=${query}.json`); 261 | const results = await response.json(); 262 | 263 | const html = results.map((restaurant) => { 264 | return ` 265 |
266 |

${restaurant.name}

267 |

${restaurant.description}

268 |
269 | `; 270 | }).join(''); 271 | 272 | this.restaurantsTarget.innerHTML = html; 273 | } 274 | ``` 275 | 276 | ```ruby 277 | # restaurant controller 278 | def index 279 | if params[:query].present? 280 | @restaurants = Restaurant.where( 281 | "id NOT IN ( 282 | SELECT restaurant_id 283 | FROM bookings 284 | WHERE start_date <= ? AND end_date >= ? 285 | )", 286 | end_date, start_date 287 | ) 288 | else 289 | @restaurants = Restaurant.all 290 | end 291 | 292 | respond_to do |format| 293 | format.html 294 | format.json { render json: @restaurants } 295 | end 296 | end 297 | ``` 298 | -------------------------------------------------------------------------------- /rails/seed.md: -------------------------------------------------------------------------------- 1 | # Controllers 2 | 3 | #### .new or [.build](https://apidock.com/rails/v5.2.3/ActiveRecord/Associations/CollectionProxy/build) 4 | 5 | In Ruby on Rails, the `.new` method is used to create a new instance of a model, while the `.build` method is used to create a new instance of a model that is part of an existing association. The difference between the two is subtle, but important to understand. 6 | 7 | When you call `.new` with params, it will create a new instance of the model and assign the attributes specified in the params. For example: 8 | 9 | ```ruby 10 | user = User.new(name: 'John', email: 'john@example.com') 11 | ``` 12 | This will create a new instance of the User model, with the name attribute set to 'John' and the email attribute set to 'john@example.com'. 13 | 14 | On the other hand, `.build` is typically used when you have an existing instance of a model and you want to create a new instance of another model that is associated with it. 15 | For example, if you have an existing user object, you can use .build to create a new post object that is associated with that user. 16 | 17 | ```ruby 18 | user = User.new(name: 'John', email: 'john@example.com') 19 | post = user.posts.build(title: 'Hello World', body: 'This is my first post') 20 | ``` 21 | This will create a new instance of the Post model, with the title attribute set to 'Hello World' and the body attribute set to 'This is my first post', and it will also associate the post with the user by adding the user_id to the post's attributes. 22 | 23 | In summary `.new` creates a new object that's not related to any other object, whereas `.build` creates a new object and associate it with another object, usually as part of an association. 24 | -------------------------------------------------------------------------------- /rails/simple.md: -------------------------------------------------------------------------------- 1 | ### [Simple Form](https://github.com/heartcombo/simple_form#collection-check-boxes) 2 | 3 | #### Installation 4 | 1. Add it to your Gemfile:
5 | `gem 'simple_form'`
6 | 2. Install the gem.
7 | `bundle install`
8 | 3. Generate the Simple Form initializer file by running the following command:
9 | `rails generate simple_form:install`
10 | 11 | By default, Simple Form uses the Bootstrap CSS classes for styling. To disable this, add the following line: 12 | ```ruby 13 | # config/initializers/simple_form.rb 14 | config.default_form_class = '' 15 | ``` 16 | 17 | #### Stimulus 18 | `<%= f.association :book_format, label: false, input_html: { data: { controller: 'book-format-select' } } %>` 19 | -------------------------------------------------------------------------------- /rails/style.md: -------------------------------------------------------------------------------- 1 | There are many different ways of writing code that achieve the same goal. However, most programmers agree there are some ways of writing code that are more readable than others. To help write more understandable code, programmers follow style guides. These provide guidelines for how to write code, structure it and avoid common problems. 2 | 3 | * To install 4 | * [Rubocop Installation](#rubocop-installation) 5 | * [ESLint with Standard and Prettier (already added)](#CodeStyleGuide-ESLintwithStandardandPrettier(alreadyadded)) 6 | * [VSCode](#CodeStyleGuide-VSCode) 7 | * [Complete Style Guides](#CodeStyleGuide-CompleteStyleGuides) 8 | * [JavaScript](#CodeStyleGuide-JavaScript) 9 | * [Ruby](#CodeStyleGuide-Ruby) 10 | * [Ruby on Rails](#CodeStyleGuide-RubyonRails) 11 | * [RSpec](#CodeStyleGuide-RSpec) 12 | * [General](#CodeStyleGuide-General) 13 | * [Commented out code](#CodeStyleGuide-Commentedoutcode) 14 | * [Unused variables](#CodeStyleGuide-Unusedvariables) 15 | * [Indentation](#CodeStyleGuide-Indentation) 16 | * [Good scoping of your variables](#CodeStyleGuide-Goodscopingofyourvariables) 17 | * [Naming your variables and functions](#CodeStyleGuide-Namingyourvariablesandfunctions) 18 | * [Avoid long functions](#CodeStyleGuide-Avoidlongfunctions​) 19 | * [Use Git rather than commenting out old code](#CodeStyleGuide-UseGitratherthancommentingoutoldcode) 20 | * [Tailwind](#CodeStyleGuide-Tailwind) 21 | * [Mobile first](#CodeStyleGuide-Mobilefirst) 22 | * [Headwind](#CodeStyleGuide-Headwind) 23 | * [Git](#CodeStyleGuide-Git) 24 | * [Commit early, commit often](#CodeStyleGuide-Commitearly,commitoften) 25 | * [One Pull Request for one feature](#CodeStyleGuide-OnePullRequestforonefeature) 26 | * [JavaScript](#CodeStyleGuide-JavaScript.1) 27 | * [Functions](#CodeStyleGuide-Functions) 28 | * [Naming conventions](#CodeStyleGuide-Namingconventions) 29 | * [Variables](#CodeStyleGuide-Variables) 30 | * [Ruby](#CodeStyleGuide-Ruby.1) 31 | * [Naming conventions](#CodeStyleGuide-Namingconventions.1) 32 | * [Rails](#CodeStyleGuide-Rails) 33 | * [Views](#CodeStyleGuide-Views) 34 | * [Seeds](#CodeStyleGuide-Seeds) 35 | * [RSpec](#CodeStyleGuide-RSpec.1) 36 | * [References](#CodeStyleGuide-References) 37 | 38 | * * * 39 | 40 | For you to install 41 | ------------------ 42 | 43 | ### Rubocop Installation 44 | 45 | [Documentation](https://docs.rubocop.org/rubocop/index.html) 46 | 47 | - [ ] only one person needs to complete the following setup at the start of a project 48 | - [ ] once pushed to GitHub, everyone else just needs to run bundle install 49 | - [ ] install each of the VS Code extensions list below 50 | - [ ] visit your VS Code settings and complete the [following](https://github.com/testdouble/standard/wiki/IDE:-vscode). 51 | 52 | #### Setup 53 | 54 | * From your terminal, `cd` to the project’s root and: 55 | 56 | ```java 57 | group :development, :test do 58 | gem "standard", require: false 59 | gem "rubocop-rails", require: false 60 | gem "rubocop-rspec", require: false 61 | ``` 62 | 63 | This adds [StandardRB](https://github.com/testdouble/standard) as the base (a less aggressive version of Rubocop) for Ruby and Rubocop for Rails and RSpec ([see](https://www.fastruby.io/blog/ruby/code-quality/how-we-use-rubocop-and-standardrb.html)). 64 | 65 | * `bundle install` 66 | 67 | * `standardrb --fix` to automatically fix offences (_use this most of the time_) 68 | 69 | * `rubocop -a` RuboCop will try to [safely](https://docs.rubocop.org/rubocop/usage/auto_correct.html) automatically fix offences 70 | 71 | * `rubocop -A` to run all auto-corrections (safe and unsafe) (generally not recommended) 72 | 73 | 74 | We also have the option of installing the [Annotate gem](https://github.com/ctran/annotate_models), the use of which is discussed [here.](https://satchel.works/@wclittle/full-stack-hello-world-tutorials-part-9) 75 | 76 | ### ESLint with Standard and Prettier (already added) 77 | 78 | - [x] only one person needs to complete the following setup at the start of a project 79 | 80 | #### Prettier 81 | 82 | [Documentation](https://prettier.io/docs/en/index.html) 83 | 84 | * From your terminal `cd`, to the project’s root and run the following to install prettier: 85 | 86 | 87 | ```java 88 | yarn add --dev --exact prettier 89 | echo {}> .prettierrc.json 90 | touch .prettierignore 91 | ``` 92 | 93 | * Open up the “.prettierignore” file in VSCode and add the following to tell prettier to not reformat code there. 94 | 95 | 96 | ```java 97 | tmp/ 98 | public/packs/ 99 | ``` 100 | 101 | * Run prettier with 102 | 103 | 104 | `yarn prettier --write .` 105 | 106 | #### ESLint 107 | 108 | [Documentation](https://eslint.org/docs/latest/) 109 | 110 | ```java 111 | yarn add --dev eslint-config-prettier 112 | yarn add eslint --dev 113 | yarn add babel-eslint --dev 114 | yarn add eslint-plugin-react --dev 115 | touch .eslintrc.json 116 | ``` 117 | 118 | * Open up your .eslintrc.json file and add in: 119 | 120 | 121 | ```java 122 | { 123 | "env": { 124 | "browser": true, 125 | "node": true, 126 | "commonjs": true, 127 | "es2021": true 128 | }, 129 | "extends": [ 130 | "eslint:recommended", 131 | "prettier" 132 | ], 133 | "parserOptions": { 134 | "ecmaVersion": 12 135 | }, 136 | "rules": { 137 | } 138 | } 139 | ``` 140 | 141 | This sets up our ESLint config to let the linter know we are expecting our JavaScript to use modern conventions, and play nice with react and prettier. Save the file. 142 | 143 | * `touch .eslintignore` 144 | 145 | * Open the above file and add: 146 | 147 | 148 | ```java 149 | tmp/ 150 | public/packs/ 151 | ``` 152 | 153 | * Save and run `yarn run eslint` 154 | 155 | 156 | Finally, let’s go ahead and install the Prettier and ESLint extensions in our VSCode so they will run automatically when we write JavaScript and save files. 157 | 158 | ### VSCode 159 | 160 | - [ ] [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) helps you find and fix problems with your JavaScript code 161 | - [ ] [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) a tool that auto-rearranges your code 162 | - [ ] [Headwind](https://marketplace.visualstudio.com/items?itemName=heybourn.headwind) parses your code and reprints class tags to follow a given order 163 | - [ ] [Ruby](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby) 164 | 165 | * * * 166 | 167 | Complete Style Guides 168 | --------------------- 169 | 170 | ### JavaScript 171 | 172 | [AirBnB’s JavaScript Style Guide](https://github.com/airbnb/javascript) 173 | 174 | ### Ruby 175 | 176 | [AirBnB’s Ruby Style Guide](https://github.com/airbnb/ruby) 177 | 178 | [GitHub’s Ruby Style Guide](https://github.com/github/rubocop-github/blob/main/STYLEGUIDE.md) 179 | 180 | ### Ruby on Rails 181 | 182 | [Rubocop’s Rails Style Guide](https://github.com/rubocop/rails-style-guide) 183 | 184 | ### RSpec 185 | 186 | [Better Spec’s RSpec Style Guide](https://www.betterspecs.org/) 187 | 188 | [Rubocop’s RSpec Style Guide](https://github.com/rubocop/rspec-style-guide) 189 | 190 | * * * 191 | 192 | General 193 | ------- 194 | 195 | ### Commented out code 196 | 197 | ```js 198 | // 🛑 Do not do this 199 | function addToShoppingList(item) { 200 | // console.log("Shopping list before", shoppingList); 201 | // console.log("Adding item", item); 202 | shoppingList.add(item); 203 | // console.log("Shopping list after", shoppingList); 204 | } 205 | ``` 206 | 207 | When you (or someone else) is reading your code, you want to see only what’s important. Removing commented out code helps find the relevant code faster and easier. 208 | 209 | ### Unused variables 210 | 211 | ```java 212 | const orderTaxi = (pickUpTime) => { 213 | let driverName = getDriverName(); 214 | let customerName = getCustomerName(); // 🛑 Don't do this! 215 | return `${driverName} will pick you up at ${pickUpTime}`; 216 | } 217 | ``` 218 | 219 | Often when we refactor, some variables become _unused_. Don’t forget to remove them! Someone might come along, look at your code and assume it’s important. 220 | 221 | ### Indentation 222 | 223 | Ensure your default tab size (indicated at the bottom right of your screen in VSCode) is set to **2** 224 | 225 | ### Good scoping of your variables 226 | 227 | Define your variables with the narrowest scope they can have. For example: 228 | 229 | ```js 230 | // 🛑 Do not do this 231 | function findLongestFirstName(fullNames) { 232 | let longest; 233 | let firstName; 234 | for (let i = 0; i < fullNames.length; i++) { 235 | firstName = fullNames[i].split(" ")[0]; 236 | if (!longest || firstName.length > longest.length) { 237 | longest = firstName; 238 | } 239 | } 240 | return longest; 241 | } 242 | ``` 243 | 244 | As your code becomes more complex it will be harder to keep track of what variable is used where. 245 | 246 | ```java 247 | // ✅ Do this instead 248 | const findLongestFirstName = (fullNames) => { 249 | let longest; 250 | for (let i = 0; i < fullNames.length; i++) { 251 | let firstName = fullNames[i].split(" ")[0]; 252 | if (!longest || firstName.length > longest.length) { 253 | longest = firstName; 254 | } 255 | } 256 | return longest; 257 | } 258 | ``` 259 | 260 | ### Naming your variables and functions 261 | 262 | When thinking about whether a variable name is good, try to imagine that you are reading the code again in the future and you have forgotten exactly how it works. Do the variable names help explain what the code is supposed to do? 263 | 264 | #### Avoid short names 265 | 266 | Very short variable names can be difficult to understand since the purpose of the variable can be unclear. They are also difficult to remember, especially if there are many similarly named variables. Try to avoid short names or abbreviations. 267 | 268 | Here are some examples of **bad** names that you should avoid: 269 | 270 | * Single letters like `x` or `y` 271 | 272 | * Abbreviations like `evt` instead of `event` 273 | 274 | * Generic names like `array` or `string` 275 | 276 | 277 | #### Describe what something is/does 278 | 279 | A good variable name quickly explains what it represents to anyone reading the code. 280 | 281 | ```js 282 | // 🛑 Try to avoid this 283 | let song = true; 284 | 285 | // ✅ This is better 286 | let isPlaying = true; 287 | ``` 288 | 289 | The `isPlaying` variable name is better since it tells us whether a song is playing or not. 290 | 291 | ```js 292 | // 🛑 Try to avoid this 293 | const percentage = () => { 294 | // ... 295 | } 296 | 297 | // ✅ This is better 298 | const getPercentage = () => { 299 | // ... 300 | } 301 | ``` 302 | 303 | Here we should use the "get" verb to show that it returns something. 304 | 305 | ```java 306 | // 🛑 Try to avoid this 307 | const isOldEnough = (number) => { 308 | // ... 309 | } 310 | 311 | // ✅ This is better 312 | const isOldEnough = (yearOfBirth) => { 313 | // ... 314 | } 315 | ``` 316 | 317 | Parameters of functions should also have names that properly represent what is going to be received into the function. 318 | 319 | ### Avoid long functions[​](https://syllabus.codeyourfuture.io/guides/code-style-guide#avoid-long-functions) 320 | 321 | Most of the time it is better to split a long function into a few shorter ones. This will: 322 | 323 | 1. Make your code easier to read 324 | 325 | 2. Make your code easier to maintain 326 | 327 | 3. Make your code easier to review by out volunteers 328 | 329 | 330 | Try to keep in mind that you will only ever write the code once, but you will read the code many times. Always aim to write code that is readable. See [The Art of Writing Small and Plain Functions](https://dmitripavlutin.com/the-art-of-writing-small-and-plain-functions/). 331 | 332 | ### Use Git rather than commenting out old code 333 | 334 | And if you want something back, you can always look at the deleted code in the git history. 335 | 336 | * * * 337 | 338 | Tailwind 339 | -------- 340 | 341 | ### Mobile first 342 | 343 | You should only ever be using `md` (for tablets) and `lg` (for desktops); styling without either is for mobile, which is what we should be developing for first. For example: 344 | 345 | ```java 346 | class="text-sm md:text-lg lg:text-xl" 347 | ``` 348 | 349 | This means, for mobiles the text will be ‘small’, on tablets it’ll be ‘large’ and on desktops it’ll be ‘extra large’ 350 | 351 | ### Headwind 352 | 353 | Headwind works globally once installed and will run on save if a `tailwind.config.js` file is present within your working directory. Alternatively, you can trigger Headwind by: 354 | 355 | * Pressing CMD + Shift + T on Mac 356 | 357 | * Pressing CTRL + ALT + T on Windows 358 | 359 | 360 | For customisation options, visit [here](https://marketplace.visualstudio.com/items?itemName=heybourn.headwind). 361 | 362 | * * * 363 | 364 | Git 365 | --- 366 | 367 | ### **Commit early, commit often** 368 | 369 | To ensure more focused commits and a cleaner git history 370 | 371 | ### One Pull Request for one feature 372 | 373 | * * * 374 | 375 | JavaScript 376 | ---------- 377 | 378 | See [AirBnB’s JavaScript style guide](https://github.com/airbnb/javascript) 379 | 380 | ### Functions 381 | 382 | #### Use ES6 Syntax 383 | 384 | Rather than write it all out here, please visit [this](https://www.taniarascia.com/es6-syntax-and-feature-overview/) site for a fantastic overview. 385 | 386 | ### Naming conventions 387 | 388 | * Use `camelCase` for methods and variables. 389 | 390 | 391 | ### Variables 392 | 393 | #### Use `const` over `let` and never `var` 394 | 395 | ```js 396 | var myAge = 21; // 🛑 Do not use 397 | let yourAge = 21; // ✅ Use for values that change 398 | const secondsInMinute = 60; // ✅ Use for values that never change 399 | ``` 400 | 401 | Briefly, `let` can be updated but not re-declared. `const` cannot be updated or re-declared. Of the two, it is better to use `const` to avoid involuntary reassignments. More [info](https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/) and a [discussion](https://stackoverflow.com/questions/41086633/why-most-of-the-time-should-i-use-const-instead-of-let-in-javascript). 402 | 403 | * * * 404 | 405 | Ruby 406 | ---- 407 | 408 | See [GitHub’s](https://github.com/github/rubocop-github/blob/main/STYLEGUIDE.md) or [AirBnB’s](https://github.com/airbnb/ruby) style guide 409 | 410 | ### Naming conventions 411 | 412 | * Use `snake_case` for methods and variables. 413 | 414 | * Use `CamelCase` for classes and modules. 415 | 416 | * Use `SCREAMING_SNAKE_CASE` for other constants. 417 | 418 | 419 | Rails 420 | ----- 421 | 422 | See [Rubocop’s](https://github.com/rubocop/rails-style-guide) style guide 423 | 424 | ### Views 425 | 426 | #### Use [partials](https://guides.rubyonrails.org/v3.2.9/layouts_and_rendering.html) to DRY up your views 427 | 428 | Treat them as a way to move details out of a view so that you can grasp what’s going on more easily by having less code on any one page. For example, you might have a view that looked like this: 429 | 430 | ```ruby 431 | <%= render "shared/ad_banner" %> 432 | 433 |

Products

434 | 435 |

Here are a few of our fine products:

436 | ... 437 | 438 | <%= render "shared/footer" %> 439 | ``` 440 | 441 | Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials contain content that is shared among many pages. 442 | 443 | ### Seeds 444 | 445 | * Use `puts` or `p` with interpolation before and after the creation of a new class. 446 | 447 | * If using .create, add `!` to the end; this will result in a meaningful alert if the class you’re trying to create isn’t valid. For example: 448 | 449 | 450 | ```java 451 | p "Category count is #{Category.count}. Destroying all categories" 452 | Category.destroy_all 453 | p "All categories destroyed. Category count is now #{Category.count}" 454 | 455 | p 'Creating 4 categories' 456 | 457 | Category.create!([ 458 | { name: 'Home', emoji: '🏠' }, 459 | { name: 'Work', emoji: '👨‍💻' }, 460 | { name: 'Social', emoji: '🧉' }, 461 | { name: 'Groceries', emoji: '🍏' }, 462 | ]) 463 | 464 | p "Created #{Category.count} new categories" 465 | ``` 466 | 467 | ### RSpec 468 | 469 | incomplete 470 | 471 | See [Better Specs](https://www.betterspecs.org/) or [Rubocop’s](https://github.com/rubocop/rspec-style-guide) style guide 472 | 473 | #### Continuous Integration 474 | 475 | incomplete 476 | 477 | See [GitHub](https://docs.github.com/en/enterprise-server@3.4/actions/automating-builds-and-tests/building-and-testing-ruby) and [this](https://github.com/ruby/setup-ruby) for more detail 478 | 479 | * * * 480 | 481 | References 482 | ---------- 483 | 484 | * [CodeYourFuture's Style Guide](https://syllabus.codeyourfuture.io/guides/code-style-guide#remember-your-audience) 485 | 486 | * [Setting Up ESLint](https://medium.com/nerd-for-tech/setting-up-eslint-with-standard-and-prettier-be245cb9fc64) 487 | 488 | * [Setting Up Rubocop](https://satchel.works/@wclittle/full-stack-hello-world-tutorials-part-9) 489 | 490 | * [StandardJS](https://standardjs.com/) 491 | -------------------------------------------------------------------------------- /rails/tasks_controller.rb: -------------------------------------------------------------------------------- 1 | class TasksController < ApplicationController 2 | before_action :set_task, only: %i[show edit update destroy] 3 | 4 | # tell Rails once which formats to handle, rather than inside each action 5 | respond_to :html 6 | # set flash messages in respond_with, customised in config/locales/responders.en.yml 7 | responders :flash 8 | 9 | # GET /tasks 10 | def index 11 | @tasks = Task.all 12 | # argument passed to respond_with is used to generate the redirect url for successful html requests 13 | # https://stackoverflow.com/questions/6620864/ruby-on-rails-whats-the-difference-between-respond-to-and-respond-with 14 | # https://www.justinweiss.com/articles/respond-to-without-all-the-pain/ 15 | respond_with(@tasks) 16 | end 17 | 18 | # GET /tasks/1 19 | def show 20 | # before_action 21 | respond_with(@task) 22 | end 23 | 24 | # GET /tasks/new 25 | def new 26 | @task = Task.new 27 | respond_with(@task) 28 | end 29 | 30 | # GET /tasks/1/edit 31 | def edit 32 | # before_action 33 | end 34 | 35 | # POST /tasks 36 | def create 37 | @task = Task.new(task_params) 38 | @task.save 39 | respond_with(@task) 40 | # no need for flash[:notice] = "Task was successfully created." if @task.save as we are using responders 41 | end 42 | 43 | # PATCH/PUT /tasks/1 44 | def update 45 | # before_action 46 | @task.update(task_params) 47 | respond_with(@task) 48 | end 49 | 50 | # DELETE /tasks/1 51 | def destroy 52 | # before_action 53 | @task.destroy 54 | # neither redirect_to tasks_path nor :location => tasks_path needed 55 | respond_with(@task) 56 | end 57 | 58 | # indicates actions are not accessible via any routes 59 | # you can only use private methods with other methods from inside the same class 60 | private 61 | 62 | # Use callbacks to share common setup or constraints between actions 63 | # find instance of task from id of task in params 64 | def set_task 65 | @task = Task.find(params[:id]) 66 | end 67 | 68 | # find the task based on the given id, and load it in the task variable 69 | # only allow a list of trusted parameters through. 70 | def task_params 71 | params.require(:task).permit(:title, :status) 72 | end 73 | # params in a controller looks like a Hash, but it's actually an instance of ActionController::Parameters which 74 | # provides several methods such as require and permit. The require method ensures that a specific parameter is present 75 | # if it's not provided, the require method throws an error. The permit method returns a copy of the parameters object, 76 | # returning only the permitted keys and values 77 | # https://stackoverflow.com/questions/18424671/what-is-params-requireperson-permitname-age-doing-in-rails-4 78 | end 79 | -------------------------------------------------------------------------------- /rails/testing.md: -------------------------------------------------------------------------------- 1 | > [RSpec](#adding-rspec)
2 | > [Workflows](#workflow)
3 | 4 | --- 5 | 6 | #### Adding RSpec 7 | 8 | ##### Remove Minitest 9 | 10 | If necessary, remove the minitest boilerplate (`rm -rf test/`) and (`gem 'minitest'` from your `Gemfile`. Replace with: 11 | 12 | ##### Add RSpec 13 | ```ruby 14 | # Gemfile 15 | group :development, :test do 16 | gem 'rspec-rails' 17 | ``` 18 | 19 | 20 | Run the following to generate the necessary files and directories for RSpec: 21 | ``` 22 | bundle install 23 | rails generate rspec:install 24 | ``` 25 | 26 | Finally, configure generators to generate RSpec tests: 27 | 28 | ```ruby 29 | # config/application.rb 30 | module App 31 | class Application < Rails::Application 32 | config.generators.test_framework = :rspec 33 | ``` 34 | ##### Generate RSpec tests 35 | If you've already created a Rails model and you want to add RSpec tests for it, do the following:
36 | e.g. `rails generate rspec:model User` 37 | 38 | ##### [Run tests](https://manny.codes/7-ways-to-selectively-run-rspec-tests/): 39 | 40 | `bundle exec rspec` to run all the RSpec tests in your project
41 | `bundle exec rspec spec/models/user_spec.rb` to run a specific test file.
42 | `bundle exec rspec spec/models` to run a specific folder.
43 | `bundle exec rspec spec/models/user_spec.rb --only-failures` run only tests that are failing. 44 | 45 | --- 46 | 47 | #### Workflow 48 | A workflow is a configurable automated process made up of one or more jobs. 49 | 1. create the `.github/workflows/` in the root directory of your repository 50 | 2. create a new file called ci.yml and add the following code. 51 | 3. commit these changes and push them to your GitHub repository. 52 | 53 | Your new GitHub Actions workflow file is now installed in your repository and will run automatically each time someone pushes a change to the main branch or when a pull request is opened or updated against the main branch. [See](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#viewing-the-activity-for-a-workflow-run) for the execution history. 54 | 55 | See the following docs for more info on [Service Containers](https://docs.github.com/en/actions/using-containerized-services/about-service-containers) [GitHub Actions](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions) and its [Syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions). 56 | 57 | ```yml 58 | name: github workflow 59 | on: 60 | push: 61 | branches: [main] 62 | pull_request: 63 | branches: [main] 64 | jobs: 65 | # Label of the container job 66 | build-container: 67 | # Containers must run in Linux based operating systems 68 | runs-on: ubuntu-latest 69 | # Service containers to run with `build-container` 70 | services: 71 | # Label used to access the service container 72 | postgres: 73 | # Docker Hub image 74 | image: postgres:14 75 | # map the container's port 5432 to the host's port 5432 76 | ports: 77 | - 5432:5432 78 | # Provide the username and password for postgres 79 | env: 80 | POSTGRES_USER: postgres 81 | POSTGRES_PASSWORD: postgres 82 | # Set health checks to wait until postgres has started 83 | options: >- 84 | --health-cmd pg_isready 85 | --health-interval 10s 86 | --health-timeout 5s 87 | --health-retries 5 88 | steps: 89 | # downloads a copy of the code in your repository before running CI tests 90 | - uses: actions/checkout@v3 91 | # runs 'bundle install' and caches installed gems automatically 92 | - uses: ruby/setup-ruby@v1 93 | with: 94 | bundler-cache: true 95 | - name: Setup Database 96 | run: | 97 | cp config/database.yml.github-actions config/database.yml 98 | bundle exec rake db:create 99 | bundle exec rake db:schema:load 100 | # use environment variables to pass configuration information to your workflow steps 101 | env: 102 | RAILS_ENV: test 103 | POSTGRES_USER: postgres 104 | POSTGRES_PASSWORD: postgres 105 | - name: Run RSpec 106 | run: COVERAGE=true bundle exec rspec --require rails_helper 107 | env: 108 | RAILS_ENV: test 109 | POSTGRES_USER: postgres 110 | POSTGRES_PASSWORD: postgres 111 | 112 | ``` 113 | 114 | Create `config/database.yml.github-actions` and add the following [code](https://www.pibit.nl/github/actions/rails/postgres/rspec/tutorial/example/2019/09/23/github-actions-with-rails-postgres-and-rspec/): 115 | ``` 116 | test: 117 | adapter: postgresql 118 | host: localhost 119 | encoding: unicode 120 | database: github-actions 121 | pool: 20 122 | username: <%= ENV["POSTGRES_USER"] %> 123 | password: <%= ENV["POSTGRES_PASSWORD"] %> 124 | ``` 125 | 126 | -------------------------------------------------------------------------------- /rails/views.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrianHards/learning-rails/aa17cfa7a9e30ffa2758a34864bcbc6c4082e713/rails/views.md -------------------------------------------------------------------------------- /rails/websocket.md: -------------------------------------------------------------------------------- 1 | ## Dictionary 2 | 3 | #### Action Cable 4 | 5 | a framework that provides real-time communication over web sockets. **Web sockets** are a communication protocol that enables real-time, two-way communication between a client (such as a web browser) and a server. Unlike traditional HTTP requests, which are unidirectional and require a new connection to be established for each request, web sockets provide a persistent connection that enables data to be sent and received in real-time. 6 | 7 | ##### Subscription 8 | refers to the process of a client (such as a web browser) requesting to receive updates or data from the server over a web socket connection. Once the subscription is established, the server can push data or updates to the client in real-time, without the client needing to make additional requests. 9 | 10 | ##### Broadcasting 11 | refers to the process of sending data or events to all clients subscribed to a specific channel. 12 | 13 | ### Schema 14 | 15 | Lets assume a simple setup. Three tables, one for messages, one for users and one for chatrooms. 16 | * A chatroom can have many messages, but a message should only belong to one room. That's why a message needs a chatroom_id. 17 | * We need to know who sent a message. A user can send many messages, but a message should only belong to one user. So a message also needs a user_id. 18 | 19 | `yarn add @rails/actioncable` 20 | 21 | steam_from is like creating different channels from our radio tower 22 | need to import rails/actioncable in stimulus, want to connect to channel on page connect 23 | stream_for makes different chatroom for each channel based on its id 24 | -------------------------------------------------------------------------------- /ruby/methods.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrianHards/learning-rails/aa17cfa7a9e30ffa2758a34864bcbc6c4082e713/ruby/methods.md -------------------------------------------------------------------------------- /ruby/oop.md: -------------------------------------------------------------------------------- 1 | ### block 2 | anonymous functions that can be passed into methods code that you put inside the do and end keywords (or { and } for inline blocks). It allows you to group code into a standalone unit that you can use as a method argument. 3 | 4 | - anonymous functions that can be passed into methods 5 | - enclosed in a do-end statement or curly braces {} for inline blocks 6 | - they can have multiple arguments: these are defined between two pipe | characters 7 | 8 | ```ruby 9 | # Form 1: recommended for single line blocks 10 | [1, 2, 3].each { |num| puts num } 11 | ^^^^^ ^^^^^^^^ 12 | block block 13 | arguments body 14 | ``` 15 | 16 | ### yield 17 | calls the code inside the block and runs it 18 | 19 | ```ruby 20 | 21 | def iterate(array) 22 | for element in array 23 | yield(element) 24 | end 25 | end 26 | 27 | numbers = [1, 2, 3] 28 | 29 | iterate(numbers) do |num| 30 | puts num 31 | end 32 | ``` 33 | 34 | **its value**: yield is commonly used to implement reusable code patterns. For example, a method might define the basic structure of an iteration but the specific actions to be performed on each iteration are passed as a block to the method. 35 | -------------------------------------------------------------------------------- /ruby/operators.md: -------------------------------------------------------------------------------- 1 | - [splat](https://thoughtbot.com/blog/ruby-splat-operator) `*`
2 | e.g. `*args` 3 | -------------------------------------------------------------------------------- /setup.md: -------------------------------------------------------------------------------- 1 | - [Rails New](#rails-new) 2 | - [Optional](#optional) 3 | - [Rails Generate](#rails-generate) 4 | - [Command Line](#command-line) 5 | - [Heroku](#heroku) 6 | 7 | --- 8 | You may want to run a few [updates](https://github.com/adrianHards/rails-guide/blob/main/other/terminal.md) before proceeding with a new project.
9 | ### [Rails New](https://guides.rubyonrails.org/getting_started.html) 10 | 11 | Run `rails new -h` to view all the options you can pass to `rails new`. For practice apps I've settled with: 12 |
13 | 14 | ```rails new myapp -c tailwind -j esbuild -d postgresql -T -M``` 15 |
16 |
17 | 18 | * `rails new myapp` creates a new rails application called myapp. If you want use multiple words, the convention is `my_app` 19 | * `-T` skips setting up of tests (so that we can use rspec over the default minitest) 20 | * `-M` skips [Action Mailer](https://guides.rubyonrails.org/action_mailer_basics.html) 21 | * `-j esbuild` sets the JavaScript build tool to [esbuild](https://esbuild.github.io/) 22 | * `-c tailwind` sets the CSS framework to [Tailwind](https://tailwindcss.com/docs/guides/ruby-on-rails) 23 | * `-d postgresql` sets Postgres as the database 24 |
25 | 26 | 1. `cd myapp` and run `git branch -m master main` to set the git branch name to main instead of the default master. 27 | 2. At this point we can start the server with `bin/dev` (instead of `bin/rails s`) and see our app running at [localhost:3000](http://localhost:3000/). 28 | 3. `open http://localhost:3000` 29 | 30 | * `rails` vs `bin/rails`: if you select the former, RubyGems will activate the latest version of the rails executable it can find in PATH. This is fine as long as you use this version of Rails in your project. Using the latter ensures your environment uses the versions specified in your project's Gemfile. 31 | * `bin/dev` runs `foreman start -f Procfile.dev`. foreman runs multiple commands, including `bin/rails`, at the same time (take a look at Procfile.dev to see what commands are being run). 32 | * `gh repo create --public --source=.` will create a public repo on GitHub 33 | 34 |

35 | 36 | --- 37 | 38 | ### Optional 39 | 40 | 1. Add any gems you want to include in your Gemfile. For example, I like to add `gem "responders"` for [respond_with](https://github.com/heartcombo/responders) which allows you to do [this](/tasks_controller.rb). 41 | 2. Run `bundle install` to install any additional gems now included in your Gemfile. For responders, you also need to run: 42 | ``` 43 | bundle install 44 | bin/rails g responders:install 45 | ``` 46 | 3. `touch .rubocop.yml` to the root of your project and [disable](https://docs.rubocop.org/rubocop/configuration.html) checks here. 47 | 48 | 62 |

63 | 64 | --- 65 | 66 | ### Rails Generate 67 | `bin/rails g `

68 | Additional options include: [--no-helper](https://www.rubyguides.com/2020/01/rails-helpers/) and `--no-test-framework` 69 |

70 | 71 | Undo: 72 | * `bin/rails d controller name ` 73 | * `bin/rails d model name` 74 | 75 | #### Resource 76 | `bin/rails g resource movie title:string description:text`
77 | * generates a migration file, a model, a controller, views and the routes for the given class, but does not populate these files with anything else (unlike `scaffold`) 78 | 79 | 80 | #### Migrations 81 | ``` 82 | bin/rails g migration add_name_to_doctors name:string 83 | bin/rails g migration remove_name_from_doctors name:string 84 | bin/rails g migration add_doctor_to_patients doctor:references 85 | ``` 86 | 87 | The first command will create a migration file e.g. `20200928055457_add_name_to_doctors.rb` that contains: 88 | 89 | ``` 90 | class AddNameToDoctors < ActiveRecord::Migration[7.0] 91 | def change 92 | add_column :doctors, :name, :string 93 | end 94 | end 95 | ``` 96 | 97 | * create an empty database for you:
98 | `bin/rails db:create` 99 | * update the schema:
100 | `bin/rails db:migrate` 101 | * drop the database, create it again and re-seed:
102 | `bin/rails db:reset`
103 | `bin/rails db:drop` to drop the db only 104 | * load data from the file: db/seeds.rb into the database
105 | `bin/rails db:seed` 106 | * undoes the last migration, you can then edit the file, and run rails db:migrate again
107 | `bin/rails db:rollback` 108 | * delete a migration file, for example, if you make a typo and haven't migrated yet
109 | `bin/rails d migration SameMigrationNameAsUsedToGenerate` 110 | * if you've truly messed things up (e.g. deleted a migration file by accident), simply delete **schema.db** and re-run `rails db:migrate` 111 | 112 | #### Models 113 | `bin/rails g model Doctor first_name last_name speciality:text patient:has_many`
114 | * a database migration that will add a table and add the columns first_name, last_name, and speciality. 115 | * a model file that will inherit from ApplicationRecord 116 | * if the data type is a string, you don’t need to specify their type. 117 | * patient:has_many will result in models and migration files being generated for both Doctor and Patient, with has_many and belongs_to 118 | 119 | #### Controllers 120 | 121 | `bin/rails g controller Doctors index save` 122 | * provide a controller file and corresponding views folder 123 | * populate controller file with e.g. `def index end` and `index.html.erb` files inside of corresponding views folder 124 | 125 |

126 | 127 | --- 128 | 129 | ### [Command Line](https://guides.rubyonrails.org/command_line.html) 130 | 131 | * `bin/setup` executes setup script placed in the bin directory; add e.g. `bin/rails db:setup` for a consistent setup 132 | * `bin/rails console` start rails console 133 | * `bin/rails routes | grep doctor` only shows us the routes for doctors 134 | 135 |

136 | 137 | --- 138 | 139 | ### [Heroku](https://devcenter.heroku.com/articles/getting-started-with-rails7) 140 | 141 | - Log in to Heroku
142 | `heroku login`
143 | - Open current app in default browser
144 | `heroku open`
145 | - Launch Rails console
146 | `heroku run rails console`
147 | - see live stream of app's logs
148 | `heroku logs --tail`
149 | - delete database
150 | `heroku pg:reset DATABASE --confirm APP-NAME` or
151 | `heroku restart; heroku pg:reset DATABASE --confirm APP-NAME; heroku run rake db:setup`
152 | - recreate database and run migrations
153 | `heroku run rake db:migrate`
154 | - populate database with seed
155 | `heroku run rake db:seed`
156 | - to combine previous two steps:
157 | `heroku run rake db:setup`
158 | - list previous versions of your app
159 | `heroku releases` 160 | - rollback to # version
161 | `heroku rollback [RELEASE #]` 162 | 163 |

Home

164 | --------------------------------------------------------------------------------