{"id":15725868,"url":"https://github.com/ndrean/simple-nest-form","last_synced_at":"2026-01-07T18:46:24.498Z","repository":{"id":41642251,"uuid":"264230055","full_name":"ndrean/simple-nest-form","owner":"ndrean","description":"Fetch GET response.text() \u0026 Nested \u0026 customs routes \u0026 forms","archived":false,"fork":false,"pushed_at":"2023-01-19T18:56:52.000Z","size":1427,"stargazers_count":0,"open_issues_count":30,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-06T06:46:14.677Z","etag":null,"topics":["ajax","rails"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ndrean.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-15T15:29:07.000Z","updated_at":"2020-12-13T00:38:07.000Z","dependencies_parsed_at":"2023-02-11T17:00:42.630Z","dependency_job_id":null,"html_url":"https://github.com/ndrean/simple-nest-form","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fsimple-nest-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fsimple-nest-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fsimple-nest-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fsimple-nest-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ndrean","download_url":"https://codeload.github.com/ndrean/simple-nest-form/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246402583,"owners_count":20771341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","rails"],"created_at":"2024-10-03T22:24:41.488Z","updated_at":"2026-01-07T18:46:24.458Z","avatar_url":"https://github.com/ndrean.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# README\n\nSimple one-to-may association _post / comment_ where the comments resources are namespaced by posts: `posts/:post_id/comments/:id`.\n\n- [Import JS in js.erb](#import-js-methods-in-js.erb)\n- [Rendering a collection of comments](#rendering-a-collection-of-comments)\n\n- [ Render a nested form](#render-a-nested-form) with `@post, @comment`\n\n- [Fetch GET for Post.all with 2 Ajax method](#fetch-GET-AJAX)\n- [Other examples of custom routes/methods](#other-examples-of-custom-routes/methods)\n\n- [ Reminder installation Bootstrap \u0026 Simple Form ](#bootstrap-simple-form-setup)\n\n## Import JS methods in _js.erb_\n\nTo add Erb support in your JS templates, run:\n\n```bash\nbundle exec rails webpacker:install:erb\n```\n\non a Rails app already setup with Webpacker.\n\nWith this setting, we then can create a _.js.erb_ file in the folder _/javascript/packs/_. Then we can use ERB (Ruby parses the file first) and import external libraries with `import { myFunction } from '../components/myJsFile.js`.\n\nIn other words, we can import _.js_ libraries into _.js.erb_ files.\n\n\u003e This can save on _data-attributes_ (the `data-something=\"\u003c%= Post.first.id%\u003e\"` in the HTML file with it's searching `document.querySelector('[data-something]')`can be replaced simply by eg `const id = \u003c%= Post.first.id%\u003e in the _.js.erb_ file)\n\n\u003e Note 1: A 'standard' view rendering file _.js.erb_ located in the views does \u003cstrong\u003enot\u003c/strong\u003e have access to `import`, only those located in the folder _/javascript/packs/_ do (after running `webpacker:install:erb`).\n\n\u003e Note 2: To use a JS library inside a view _.html.erb_ we need to:\n\n- import the library in a _someFile.js.erb_ file in the folder _/javascript/packs/_\n\n- import the _someFile.js.erb_ file in the view with `\u003ct%= javascript_pack_tag 'someFile' %\u003e`\n\n\u003e Note 3: we need to have Turbolinks loaded to access to the DOM, so all the code in the _someFile.js.erb_ file is wrapped as a callback: `document.addEventListener(\"turbolinks:load\", myFunction})`, and declare `const myFunction = ()=\u003e {[...]}` after.\n\n## Routes:\n\n```ruby\n#routes\nRails.application.routes.draw do\n\n  root to: 'posts#index'\n\n  resources :posts do\n    post 'save/:id', to: 'comments#save', as: 'save'\n\n    post 'comments/save'\n    #delete 'comments/:id', to: 'comments#erase'\n    resources :comments, only: [:index, :new, :create]\n  end\n\n  get 'articles', to: 'posts#display_articles'\n\n  resources :comments, only: [:destroy, :show, :edit, :update]\n\n  delete 'erase/:id', to: \"comments#erase\", as: 'erase'\n\nend\n```\n\n## Render a collection of comments\n\nExample in the view _/views/posts_ that renders the method _posts#index_. We can iterate with a table and use `\u003c% @posts.each do |post| %\u003e \u003c%= post.title %\u003e ...`. We can also use `\u003c%= render @posts %\u003e` or `\u003c%= render partial: '/posts/post', collection: @posts %\u003e` to render the list of post defined in a partial _views/posts/\\_post.html.erb_ (as an _article_).\n\nAnother example with `\u003c%= render @post.comments %\u003e` to render all comments for a given post, with the partial _/views/comments/\\_comment.html.erb_.\n\n## Render a nested form\n\nTwo examples.\n\n1. On the view 'show' for _/posts/1_, we have a form to create a nested _comment_ (=\u003e comments#create). The form is rendered with the partial `\u003c%= render 'comments/_form %\u003e`. We use the formbuilder `form_with` and define:\n\n```ruby\n# /views/post/1 posts/show\n\u003c% render comments/forms %\n# views/comments/_forms.html.erb\n \u003c%= form_with model: [@post, @comment] do |form| %\n```\n\n(where the form object `f.object` is a _comment_). The _posts#show_ furnishes a new comment object:\n\n```ruby\ndef show\n    @post = Post.find(params[:id])\n    @comment = Comment.new(post: @post)\nend\n```\n\nand on submit, the form calls by default the method _comments#create_ (the url would have been `post_comments_path(@post)`. It automatically calls the method _comments#create_ from the other controller:\n\n```ruby\n#comments_controller.rb\ndef create\n    @post = Post.find(params[:post_id]) # \u003c=\u003e before_action :set_post\n    @comment = @post.comments.build(comment_params)\n    # we decided to inject the new comment by JAvascript in the 'posts/:post_id#show' view =\u003e respond_to js\n    respond_to do |format|\n      if @comment.save\n        format.js\n        format.html { redirect_to post_comments_path(@post), notice: 'Comment was successfully created.' }\n      else\n        format.html { render :new }\n      end\n    end\n  end\n```\n\n2. In the index view _/posts/1/comments/new_, we have defined a custom method 'save' method (the standard _comments#create_ works with `respond_to js` since the formbuilder `form_with` has the attribute `remote: true` by default; here, we don't need AJAX rendering as we have a redirection in the view, so we could have used the html rendering and declare `local: true`). For this, we define a new route:\n\n```ruby\nresources: posts do\n    post 'comments/save'\n```\n\n```ruby\n#comments#save\ndef save\n    respond_to do |format|\n      @comment = @post.comments.create(comment_params)\n      format.html { redirect_to post_comments_path(@post), notice: 'Comment was successfully created' }\n    end\n  end\n```\n\nso we have a view _/posts/1/comments/new_ that contains a form with `form_with ..., local: true` (non-Ajax):\n\n```ruby\n# views/posts/:post_id/comments/new\n\u003c%= form_with model: [@post, @comment], url: {controller:\"comments\", action: \"save\"}, local: true  do |form| %\u003e\n\n```\n\nThe command _rails routes_ shows that:\n\n\u003e url: post_comments_save_path(@post) \u003c=\u003e url: {controller:\"comments\", action: \"save\"}\n\n## Fetch GET AJAX\n\nWe defined a button to display all the posts. On page load, the list is empty\nWe defined a JS `fetch() GET` function that triggers on this button click,\nand points to the URL `/posts?f=\"\"`. This URL is served by the method 'posts#index'.\nWe put a dummy query string `f=\"\"` such that we can differenciate the presence of params so\nthat the method `index` can react differently: an empty array `[]` on page load, and\n`Post.all` when 'index' sees some params. Then we render a partial. Note that it is\nimportant to use `locals: {posts: @posts}` for this to work (usually we don't need `locals`).\nThe JS `fetch()` asks for 'text/html' and parses the response from 'index' into 'text' with `response.text()`.\nThen we just pass this content in the (unsual) form 'text/html' (parsed by Ruby, see in the console/network call) to render in\nthe view.\u003c/p\u003e\n\n\u003cp\u003eNote: we need Turbolinks to be loaded to use the JS methods (see 'application.js')\u003c/p\u003e\n\n```ruby\n# post_controller.rb\ndef index\n  if params[:f].present?\n    @posts = Post.all\n    render partial: 'posts/posts', locals: {posts: @posts}, layout: false\n  else\n  #   # on page load, show nothing\n    @posts = []\n  end\nend\n```\n\n```js\n#/packs/components/fetchPosts.js\nconst fetchPosts = (tag) =\u003e {\n  document.querySelector(tag).addEventListener(\"click\", (e) =\u003e {\n    e.preventDefault();\n    try {\n      const query = await fetch('/posts?f=\"\"', {\n        method: \"GET\",\n        headers: {\n          \"Content-Type\": \"text/html\",\n          Accept: \"text/html\",\n        },\n        credentials: \"same-origin\", // default value\n      });\n      if (query.ok) {\n        const content = await query.text();\n        return (document.querySelector(\"#posts_list\").innerHTML = content);\n      }\n    } catch (error) {\n      throw error;\n    }\n  });\n};\n\nexport { fetchPosts };\n```\n\n\u003e We can use `innerHTML` to render this text.\n\nWe can compare this to a traditionnal method using a `link_to, remote: true` to another method that renders a `js.erb` file:\n\n```ruby\n#posts#display_articles\ndef display_articles\n      @posts = Post.all\n      respond_to :js\n  end\n```\n\n```js\ndocument.querySelector(\"#articles_list\").innerHTML = \"\";\ndocument\n  .querySelector(\"#articles_list\")\n  .insertAdjacentHTML(\"afterbegin\", `\u003c%= j render @posts %\u003e`);\n```\n\n\u003e We need `insertAdjacentHTML` and not `innerHTML` to work.\n\n## Other examples of custom routes/methods\n\nWe also defined two destroy methods in the controller _comments_ . Both are Ajax but the rendering is different. Note that we could have written a `\u003c% if condition %\u003e { do something} \u003c% else %\u003e { do something else}` in a _destroy.js.erb_ file.\n\nA _destroy_ method defined in the resources list is called - for a _post_ object by a link (made Ajax here with `remote: true`)\n\n```ruby\n    \u003c%= link_to 'Destroy', post, method: :delete, remote: true, data: { confirm: 'Are you sure?' } %\u003e\n\n```\n\nFor a _destroy_ link for the nested _comments_ controller, the link would have been `post_comment_path(comment.post, comment), method: :delete,`.\n\nHere, we have two _delete_ in the controller _comments_ so we need to specify the route:\n\n```ruby\nresources :posts do\n    delete 'comments/:id', to: 'comments#erase'\n```\n\nand the call in the view _posts/:post_id/comments_ by the link:\n\n```ruby\n\u003c%= link_to 'Erase', [@post, comment], url: \"erase/#{comment.id}\", method: :delete, remote: true,\n```\n\nand in the show _views/posts/:post_id_ calling the partial _views/comments/\\_comment.html.erb_, we use:\n\n```ruby\n\u003c%= link_to 'Destroy', [@post, comment], url: \"destroy/#{comment.id}\", method: :delete, remote: true, data: { confirm: 'Are you sure to Erase?' } %\u003e\n```\n\n## Bootstrap Simple Form setup\n\n```bash\nyarn add bootstrap\n```\n\n- Modifiy _#assp/Assets/Stylesheets/application.css_ to _.scss_ and add: `@import \"bootstrap/scss/bootstrap\";`\n\n- in _/layout/application.html.erb_ do\n\n```html\n\u003chead\u003e\n  ...\n  \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" /\u003e\n  \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e\n\u003c/head\u003e\n```\n\n```ruby\ngem 'simple_form'\n```\n\n```bash\nbundle\nrails g simple_form:install --bootstrap\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndrean%2Fsimple-nest-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fndrean%2Fsimple-nest-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndrean%2Fsimple-nest-form/lists"}