From 5a4f314aabb600ef40dc6399aeb72c602d74ece1 Mon Sep 17 00:00:00 2001 From: "Jesse C. Fisher" Date: Tue, 7 Nov 2023 16:50:08 +0000 Subject: [PATCH] Add social links to profiles --- app/assets/javascripts/social_links.coffee | 3 + app/assets/stylesheets/social_links.sass | 0 app/controllers/social_links_controller.rb | 76 ++++++++++ app/helpers/social_links_helper.rb | 2 + app/models/profile.rb | 1 + app/models/social_link.rb | 3 + app/policies/social_link_policy.rb | 55 +++++++ app/views/profiles/show.html.haml | 7 + app/views/social_links/_form.html.haml | 9 ++ .../social_links/_social_link.json.jbuilder | 2 + app/views/social_links/edit.html.haml | 7 + app/views/social_links/index.html.haml | 23 +++ app/views/social_links/index.json.jbuilder | 1 + app/views/social_links/new.html.haml | 5 + app/views/social_links/show.html.haml | 12 ++ app/views/social_links/show.json.jbuilder | 1 + config/routes.rb | 1 + .../20231107161251_create_social_links.rb | 10 ++ db/schema.rb | 11 +- spec/factories/social_links.rb | 6 + spec/helpers/social_links_helper_spec.rb | 15 ++ spec/models/social_link_spec.rb | 5 + spec/requests/social_links_spec.rb | 135 ++++++++++++++++++ spec/routing/social_links_routing_spec.rb | 38 +++++ .../views/social_links/edit.html.haml_spec.rb | 25 ++++ .../social_links/index.html.haml_spec.rb | 23 +++ spec/views/social_links/new.html.haml_spec.rb | 21 +++ .../views/social_links/show.html.haml_spec.rb | 16 +++ 28 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/social_links.coffee create mode 100644 app/assets/stylesheets/social_links.sass create mode 100644 app/controllers/social_links_controller.rb create mode 100644 app/helpers/social_links_helper.rb create mode 100644 app/models/social_link.rb create mode 100644 app/policies/social_link_policy.rb create mode 100644 app/views/social_links/_form.html.haml create mode 100644 app/views/social_links/_social_link.json.jbuilder create mode 100644 app/views/social_links/edit.html.haml create mode 100644 app/views/social_links/index.html.haml create mode 100644 app/views/social_links/index.json.jbuilder create mode 100644 app/views/social_links/new.html.haml create mode 100644 app/views/social_links/show.html.haml create mode 100644 app/views/social_links/show.json.jbuilder create mode 100644 db/migrate/20231107161251_create_social_links.rb create mode 100644 spec/factories/social_links.rb create mode 100644 spec/helpers/social_links_helper_spec.rb create mode 100644 spec/models/social_link_spec.rb create mode 100644 spec/requests/social_links_spec.rb create mode 100644 spec/routing/social_links_routing_spec.rb create mode 100644 spec/views/social_links/edit.html.haml_spec.rb create mode 100644 spec/views/social_links/index.html.haml_spec.rb create mode 100644 spec/views/social_links/new.html.haml_spec.rb create mode 100644 spec/views/social_links/show.html.haml_spec.rb diff --git a/app/assets/javascripts/social_links.coffee b/app/assets/javascripts/social_links.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/social_links.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/social_links.sass b/app/assets/stylesheets/social_links.sass new file mode 100644 index 00000000..e69de29b diff --git a/app/controllers/social_links_controller.rb b/app/controllers/social_links_controller.rb new file mode 100644 index 00000000..fe908052 --- /dev/null +++ b/app/controllers/social_links_controller.rb @@ -0,0 +1,76 @@ +class SocialLinksController < ApplicationController + before_action :set_social_link, only: %i[ show edit update destroy ] + + # GET /social_links or /social_links.json + def index + @social_links = policy_scope SocialLink.all + authorize @social_links + end + + # GET /social_links/1 or /social_links/1.json + def show + authorize @social_link + end + + # GET /social_links/new + def new + @social_link = SocialLink.new + @social_link.profile_id = params[:profile_id] if params[:profile_id] + authorize @social_link + end + + # GET /social_links/1/edit + def edit + authorize @social_link + end + + # POST /social_links or /social_links.json + def create + authorize @social_link = SocialLink.new(social_link_params) + + respond_to do |format| + if @social_link.save + format.html { redirect_to @social_link.profile, notice: "Social link was successfully created." } + format.json { render :show, status: :created, location: @social_link } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @social_link.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /social_links/1 or /social_links/1.json + def update + authorize @social_link + respond_to do |format| + if @social_link.update(social_link_params) + format.html { redirect_to @social_link.profile, notice: "Social link was successfully updated." } + format.json { render :show, status: :ok, location: @social_link } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @social_link.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /social_links/1 or /social_links/1.json + def destroy + @social_link.destroy + + respond_to do |format| + format.html { redirect_to social_links_url, notice: "Social link was successfully destroyed." } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_social_link + @social_link = SocialLink.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def social_link_params + params.require(:social_link).permit(:profile_id, :url) + end +end diff --git a/app/helpers/social_links_helper.rb b/app/helpers/social_links_helper.rb new file mode 100644 index 00000000..229af0be --- /dev/null +++ b/app/helpers/social_links_helper.rb @@ -0,0 +1,2 @@ +module SocialLinksHelper +end diff --git a/app/models/profile.rb b/app/models/profile.rb index 9e534f1e..e23c028f 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -1,3 +1,4 @@ class Profile < ActiveRecord::Base belongs_to :user + has_many :social_links end diff --git a/app/models/social_link.rb b/app/models/social_link.rb new file mode 100644 index 00000000..62a2f562 --- /dev/null +++ b/app/models/social_link.rb @@ -0,0 +1,3 @@ +class SocialLink < ActiveRecord::Base + belongs_to :profile +end diff --git a/app/policies/social_link_policy.rb b/app/policies/social_link_policy.rb new file mode 100644 index 00000000..114c38b5 --- /dev/null +++ b/app/policies/social_link_policy.rb @@ -0,0 +1,55 @@ +class SocialLinkPolicy + attr_reader :user, :model + + def initialize(user, model) + @user = user || User.new + @social_link = model + end + + class Scope + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + if user.admin? + scope.all + else + scope.where(user: user) + end + end + + private + + attr_reader :user, :scope + end + + def index? + @user.admin? || true + end + + def show? + @user.admin? || true + end + + def new? + @user.signed_in? + end + + def create? + @user.signed_in? + end + + def edit? + update? + end + + def update? + @user.admin? || @user == @social_link.user + end + + def destroy? + @user.admin? || @user == @social_link.user + end +end diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 1211c723..4742ec33 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -7,6 +7,13 @@ %p = @profile.summary +-# Social Links +%p + - @profile.social_links.each do |social_link| + = link_to "#{social_link.url}", "https://#{social_link.url}" + - if policy(@profile).edit? + %br= link_to '+ Add Social Link', new_social_link_path(profile_id: @profile.id) + %h2 About #{@profile.name} .about diff --git a/app/views/social_links/_form.html.haml b/app/views/social_links/_form.html.haml new file mode 100644 index 00000000..a01ee8b0 --- /dev/null +++ b/app/views/social_links/_form.html.haml @@ -0,0 +1,9 @@ += simple_form_for(@social_link) do |f| + = f.error_notification + + .form-inputs + = f.hidden_field :profile_id + = f.input :url + + .form-actions + = f.button :submit diff --git a/app/views/social_links/_social_link.json.jbuilder b/app/views/social_links/_social_link.json.jbuilder new file mode 100644 index 00000000..912c4bf9 --- /dev/null +++ b/app/views/social_links/_social_link.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! social_link, :id, :profile_id, :url, :created_at, :updated_at +json.url social_link_url(social_link, format: :json) diff --git a/app/views/social_links/edit.html.haml b/app/views/social_links/edit.html.haml new file mode 100644 index 00000000..186cd599 --- /dev/null +++ b/app/views/social_links/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing social_link + += render 'form' + += link_to 'Show', @social_link +\| += link_to 'Back', social_links_path diff --git a/app/views/social_links/index.html.haml b/app/views/social_links/index.html.haml new file mode 100644 index 00000000..70b08ed6 --- /dev/null +++ b/app/views/social_links/index.html.haml @@ -0,0 +1,23 @@ +%h1 Listing social_links + +%table + %thead + %tr + %th Profile + %th Url + %th + %th + %th + + %tbody + - @social_links.each do |social_link| + %tr + %td= social_link.profile + %td= social_link.url + %td= link_to 'Show', social_link + %td= link_to 'Edit', edit_social_link_path(social_link) + %td= link_to 'Destroy', social_link, method: :delete, data: { confirm: 'Are you sure?' } + +%br + += link_to 'New Social link', new_social_link_path diff --git a/app/views/social_links/index.json.jbuilder b/app/views/social_links/index.json.jbuilder new file mode 100644 index 00000000..185bcdf1 --- /dev/null +++ b/app/views/social_links/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @social_links, partial: "social_links/social_link", as: :social_link diff --git a/app/views/social_links/new.html.haml b/app/views/social_links/new.html.haml new file mode 100644 index 00000000..ac0a1aea --- /dev/null +++ b/app/views/social_links/new.html.haml @@ -0,0 +1,5 @@ +%h1 New social_link + += render 'form' + += link_to 'Back', social_links_path diff --git a/app/views/social_links/show.html.haml b/app/views/social_links/show.html.haml new file mode 100644 index 00000000..3b8ac73d --- /dev/null +++ b/app/views/social_links/show.html.haml @@ -0,0 +1,12 @@ +%p#notice= notice + +%p + %b Profile: + = @social_link.profile +%p + %b Url: + = @social_link.url + += link_to 'Edit', edit_social_link_path(@social_link) +\| += link_to 'Back', social_links_path diff --git a/app/views/social_links/show.json.jbuilder b/app/views/social_links/show.json.jbuilder new file mode 100644 index 00000000..17072671 --- /dev/null +++ b/app/views/social_links/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "social_links/social_link", social_link: @social_link diff --git a/config/routes.rb b/config/routes.rb index bd4b0046..68253b16 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + resources :social_links resources :profiles resources :tokens resources :projects diff --git a/db/migrate/20231107161251_create_social_links.rb b/db/migrate/20231107161251_create_social_links.rb new file mode 100644 index 00000000..a5578f45 --- /dev/null +++ b/db/migrate/20231107161251_create_social_links.rb @@ -0,0 +1,10 @@ +class CreateSocialLinks < ActiveRecord::Migration[6.1] + def change + create_table :social_links do |t| + t.belongs_to :profile, null: false, foreign_key: true + t.string :url + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 57bfaed8..bc1a0cca 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_11_06_161052) do +ActiveRecord::Schema.define(version: 2023_11_07_161251) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -128,6 +128,14 @@ ActiveRecord::Schema.define(version: 2023_11_06_161052) do t.index ["user_id"], name: "index_seats_on_user_id" end + create_table "social_links", force: :cascade do |t| + t.bigint "profile_id", null: false + t.string "url" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["profile_id"], name: "index_social_links_on_profile_id" + end + create_table "soft_token_principals", force: :cascade do |t| t.string "soft_token" t.string "principal" @@ -197,4 +205,5 @@ ActiveRecord::Schema.define(version: 2023_11_06_161052) do add_foreign_key "doodads", "users" add_foreign_key "profiles", "users" add_foreign_key "projects", "users" + add_foreign_key "social_links", "profiles" end diff --git a/spec/factories/social_links.rb b/spec/factories/social_links.rb new file mode 100644 index 00000000..e50b5a2d --- /dev/null +++ b/spec/factories/social_links.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :social_link do + profile nil + url "MyString" + end +end diff --git a/spec/helpers/social_links_helper_spec.rb b/spec/helpers/social_links_helper_spec.rb new file mode 100644 index 00000000..e8f40b34 --- /dev/null +++ b/spec/helpers/social_links_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the SocialLinksHelper. For example: +# +# describe SocialLinksHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe SocialLinksHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/social_link_spec.rb b/spec/models/social_link_spec.rb new file mode 100644 index 00000000..752b143d --- /dev/null +++ b/spec/models/social_link_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe SocialLink, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/social_links_spec.rb b/spec/requests/social_links_spec.rb new file mode 100644 index 00000000..298c3544 --- /dev/null +++ b/spec/requests/social_links_spec.rb @@ -0,0 +1,135 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to test the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. + +RSpec.describe "/social_links", type: :request do + + # This should return the minimal set of attributes required to create a valid + # SocialLink. As you add validations to SocialLink, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + skip("Add a hash of attributes valid for your model") + } + + let(:invalid_attributes) { + skip("Add a hash of attributes invalid for your model") + } + + describe "GET /index" do + it "renders a successful response" do + SocialLink.create! valid_attributes + get social_links_url + expect(response).to be_successful + end + end + + describe "GET /show" do + it "renders a successful response" do + social_link = SocialLink.create! valid_attributes + get social_link_url(social_link) + expect(response).to be_successful + end + end + + describe "GET /new" do + it "renders a successful response" do + get new_social_link_url + expect(response).to be_successful + end + end + + describe "GET /edit" do + it "renders a successful response" do + social_link = SocialLink.create! valid_attributes + get edit_social_link_url(social_link) + expect(response).to be_successful + end + end + + describe "POST /create" do + context "with valid parameters" do + it "creates a new SocialLink" do + expect { + post social_links_url, params: { social_link: valid_attributes } + }.to change(SocialLink, :count).by(1) + end + + it "redirects to the created social_link" do + post social_links_url, params: { social_link: valid_attributes } + expect(response).to redirect_to(social_link_url(SocialLink.last)) + end + end + + context "with invalid parameters" do + it "does not create a new SocialLink" do + expect { + post social_links_url, params: { social_link: invalid_attributes } + }.to change(SocialLink, :count).by(0) + end + + + it "renders a successful response (i.e. to display the 'new' template)" do + post social_links_url, params: { social_link: invalid_attributes } + expect(response).to be_successful + end + + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:new_attributes) { + skip("Add a hash of attributes valid for your model") + } + + it "updates the requested social_link" do + social_link = SocialLink.create! valid_attributes + patch social_link_url(social_link), params: { social_link: new_attributes } + social_link.reload + skip("Add assertions for updated state") + end + + it "redirects to the social_link" do + social_link = SocialLink.create! valid_attributes + patch social_link_url(social_link), params: { social_link: new_attributes } + social_link.reload + expect(response).to redirect_to(social_link_url(social_link)) + end + end + + context "with invalid parameters" do + + it "renders a successful response (i.e. to display the 'edit' template)" do + social_link = SocialLink.create! valid_attributes + patch social_link_url(social_link), params: { social_link: invalid_attributes } + expect(response).to be_successful + end + + end + end + + describe "DELETE /destroy" do + it "destroys the requested social_link" do + social_link = SocialLink.create! valid_attributes + expect { + delete social_link_url(social_link) + }.to change(SocialLink, :count).by(-1) + end + + it "redirects to the social_links list" do + social_link = SocialLink.create! valid_attributes + delete social_link_url(social_link) + expect(response).to redirect_to(social_links_url) + end + end +end diff --git a/spec/routing/social_links_routing_spec.rb b/spec/routing/social_links_routing_spec.rb new file mode 100644 index 00000000..c1853b2a --- /dev/null +++ b/spec/routing/social_links_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe SocialLinksController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/social_links").to route_to("social_links#index") + end + + it "routes to #new" do + expect(get: "/social_links/new").to route_to("social_links#new") + end + + it "routes to #show" do + expect(get: "/social_links/1").to route_to("social_links#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/social_links/1/edit").to route_to("social_links#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/social_links").to route_to("social_links#create") + end + + it "routes to #update via PUT" do + expect(put: "/social_links/1").to route_to("social_links#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/social_links/1").to route_to("social_links#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/social_links/1").to route_to("social_links#destroy", id: "1") + end + end +end diff --git a/spec/views/social_links/edit.html.haml_spec.rb b/spec/views/social_links/edit.html.haml_spec.rb new file mode 100644 index 00000000..daa90be0 --- /dev/null +++ b/spec/views/social_links/edit.html.haml_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +RSpec.describe "social_links/edit", type: :view do + let(:social_link) { + SocialLink.create!( + profile: nil, + url: "MyString" + ) + } + + before(:each) do + assign(:social_link, social_link) + end + + it "renders the edit social_link form" do + render + + assert_select "form[action=?][method=?]", social_link_path(social_link), "post" do + + assert_select "input[name=?]", "social_link[profile_id]" + + assert_select "input[name=?]", "social_link[url]" + end + end +end diff --git a/spec/views/social_links/index.html.haml_spec.rb b/spec/views/social_links/index.html.haml_spec.rb new file mode 100644 index 00000000..9a9e8ba9 --- /dev/null +++ b/spec/views/social_links/index.html.haml_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +RSpec.describe "social_links/index", type: :view do + before(:each) do + assign(:social_links, [ + SocialLink.create!( + profile: nil, + url: "Url" + ), + SocialLink.create!( + profile: nil, + url: "Url" + ) + ]) + end + + it "renders a list of social_links" do + render + cell_selector = Rails::VERSION::STRING >= '7' ? 'div>p' : 'tr>td' + assert_select cell_selector, text: Regexp.new(nil.to_s), count: 2 + assert_select cell_selector, text: Regexp.new("Url".to_s), count: 2 + end +end diff --git a/spec/views/social_links/new.html.haml_spec.rb b/spec/views/social_links/new.html.haml_spec.rb new file mode 100644 index 00000000..2c7f48a0 --- /dev/null +++ b/spec/views/social_links/new.html.haml_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe "social_links/new", type: :view do + before(:each) do + assign(:social_link, SocialLink.new( + profile: nil, + url: "MyString" + )) + end + + it "renders new social_link form" do + render + + assert_select "form[action=?][method=?]", social_links_path, "post" do + + assert_select "input[name=?]", "social_link[profile_id]" + + assert_select "input[name=?]", "social_link[url]" + end + end +end diff --git a/spec/views/social_links/show.html.haml_spec.rb b/spec/views/social_links/show.html.haml_spec.rb new file mode 100644 index 00000000..8da571a3 --- /dev/null +++ b/spec/views/social_links/show.html.haml_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe "social_links/show", type: :view do + before(:each) do + assign(:social_link, SocialLink.create!( + profile: nil, + url: "Url" + )) + end + + it "renders attributes in

" do + render + expect(rendered).to match(//) + expect(rendered).to match(/Url/) + end +end