From 8bfce7c350b62ceaba5a4417ee1eb1fa721424cf Mon Sep 17 00:00:00 2001 From: Daniel Moreira Date: Sat, 5 Aug 2023 20:32:47 -0300 Subject: [PATCH 01/21] Update spooky.gemspec --- spooky.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spooky.gemspec b/spooky.gemspec index e613fb5..e050e84 100644 --- a/spooky.gemspec +++ b/spooky.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_runtime_dependency "activesupport", "~> 6.0" + spec.add_runtime_dependency "activesupport", ">= 6.0" spec.add_runtime_dependency "http", "~> 4.0" spec.add_development_dependency "bundler", "~> 2.0" From 41cb4e8a252556a11eb02722d2c9f5299b83b095 Mon Sep 17 00:00:00 2001 From: Daniel Moreira Date: Sat, 5 Aug 2023 20:33:32 -0300 Subject: [PATCH 02/21] Update spooky.gemspec --- spooky.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spooky.gemspec b/spooky.gemspec index e050e84..d8be484 100644 --- a/spooky.gemspec +++ b/spooky.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_runtime_dependency "activesupport", ">= 6.0" - spec.add_runtime_dependency "http", "~> 4.0" + spec.add_runtime_dependency "http", ">= 4.0" spec.add_development_dependency "bundler", "~> 2.0" spec.add_development_dependency "dotenv", "~> 2.7" From dec329a0be94b06113022683d8e20321a0ce5199 Mon Sep 17 00:00:00 2001 From: Daniel Moreira Date: Mon, 7 Aug 2023 11:51:18 -0300 Subject: [PATCH 03/21] Update tag.rb --- lib/spooky/tag.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index 14b7242..7e27329 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -1,6 +1,7 @@ module Spooky class Tag ATTRIBUTES = [ + "accent_color", "description", "feature_image", "id", From 01a365d95163d124afe695d4ae3157bceb7fc700 Mon Sep 17 00:00:00 2001 From: Daniel Moreira Date: Mon, 7 Aug 2023 17:01:41 -0300 Subject: [PATCH 04/21] Update client.rb --- lib/spooky/client.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index 281d286..89e263f 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -37,13 +37,9 @@ def fetch(resource, options = {}) response.present? && [response.map { |attrs| resource_class.send(:new, attrs) }, pagination] end - def posts(tags: false, authors: false, filter: false, page: false, limit: false) - inc = [] - inc << "tags" if tags - inc << "authors" if authors - + def posts(include: [], filter: false, page: false, limit: false) options = {} - options[:include] = inc if inc.present? + options[:include] = include options = apply_filter(options, filter) options = apply_pagination(options, { page: page, limit: limit }) From c8c89d52339b257c59942557c66a763b0d61ea67 Mon Sep 17 00:00:00 2001 From: Daniel Moreira Date: Tue, 8 Aug 2023 10:54:27 -0300 Subject: [PATCH 05/21] Update client.rb --- lib/spooky/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index 89e263f..ae1936f 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -9,7 +9,7 @@ class Client def initialize(attrs = {}) @api_url = ENV["GHOST_API_URL"] || attrs[:api_url] @api_key = ENV["GHOST_CONTENT_API_KEY"] || attrs[:api_key] - @endpoint = "#{@api_url}/ghost/api/v3/content" + @endpoint = "#{@api_url}/ghost/api/content" end def fetch_json(resource, options = {}) From bf415f18e621012f4276c0d2f68b8283e884e29a Mon Sep 17 00:00:00 2001 From: Daniel Moreira Date: Tue, 8 Aug 2023 11:36:39 -0300 Subject: [PATCH 06/21] Add Page Support --- lib/spooky.rb | 1 + lib/spooky/client.rb | 24 ++++++++++++++++++++++++ lib/spooky/page.rb | 40 ++++++++++++++++++++++++++++++++++++++++ lib/spooky/version.rb | 2 +- 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 lib/spooky/page.rb diff --git a/lib/spooky.rb b/lib/spooky.rb index 63a13ca..e3434bc 100644 --- a/lib/spooky.rb +++ b/lib/spooky.rb @@ -2,6 +2,7 @@ require "spooky/is_resource" require "spooky/client" +require "spooky/page" require "spooky/post" require "spooky/author" require "spooky/tag" diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index ae1936f..8369c33 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -37,6 +37,30 @@ def fetch(resource, options = {}) response.present? && [response.map { |attrs| resource_class.send(:new, attrs) }, pagination] end + def pages(include: [], filter: false, page: false, limit: false) + options = {} + options[:include] = include + + options = apply_filter(options, filter) + options = apply_pagination(options, { page: page, limit: limit }) + + fetch("pages", options) + end + + def page_by(id: nil, slug: nil) + options = {} + + if id.present? + response, _ = fetch("pages/#{id}", options) + response.present? && response.first + elsif slug.present? + response, _ = fetch("pages/slug/#{slug}", options) + response.present? && response.first + else + false + end + end + def posts(include: [], filter: false, page: false, limit: false) options = {} options[:include] = include diff --git a/lib/spooky/page.rb b/lib/spooky/page.rb new file mode 100644 index 0000000..a57d000 --- /dev/null +++ b/lib/spooky/page.rb @@ -0,0 +1,40 @@ +module Spooky + class Page + ATTRIBUTES = [ + "canonical_url", + "codeinjection_foot", + "codeinjection_head", + "comment_id", + "created_at", + "custom_excerpt", + "custom_template", + "excerpt", + "feature_image", + "feature_image_alt", + "feature_image_caption", + "featured", + "frontmatter", + "html", + "id", + "meta_description", + "meta_title", + "og_description", + "og_image", + "og_title", + "published_at", + "reading_time", + "show_title_and_feature_image", + "slug", + "title", + "twitter_description", + "twitter_image", + "twitter_title", + "updated_at", + "url", + "uuid", + "visibility" + ].freeze + + include IsResource + end +end diff --git a/lib/spooky/version.rb b/lib/spooky/version.rb index 2ebebdb..52a312f 100644 --- a/lib/spooky/version.rb +++ b/lib/spooky/version.rb @@ -1,3 +1,3 @@ module Spooky - VERSION = "1.1.0".freeze + VERSION = "2.0.0".freeze end From 116058b8575edd72fe90858a44e5bf361f66ff90 Mon Sep 17 00:00:00 2001 From: Daniel Moreira Date: Tue, 8 Aug 2023 20:35:35 -0300 Subject: [PATCH 07/21] Update client.rb --- lib/spooky/client.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index 8369c33..0b550ad 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -71,13 +71,9 @@ def posts(include: [], filter: false, page: false, limit: false) fetch("posts", options) end - def post_by(id: nil, slug: nil, tags: false, authors: false) - inc = [] - inc << "tags" if tags - inc << "authors" if authors - + def post_by(id: nil, slug: nil, include: []) options = {} - options[:include] = inc if inc.present? + options[:include] = include if id.present? response, _ = fetch("posts/#{id}", options) From a73dd0e4364828ac52b741bd24332017cf911509 Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Thu, 3 Apr 2025 13:32:38 -0400 Subject: [PATCH 08/21] rubocop -a We have rubocop, so let's fix what it recommends. --- lib/spooky/author.rb | 28 ++++++++-------- lib/spooky/client.rb | 18 +++++----- lib/spooky/is_resource.rb | 2 +- lib/spooky/page.rb | 66 ++++++++++++++++++------------------ lib/spooky/post.rb | 70 +++++++++++++++++++-------------------- lib/spooky/tag.rb | 22 ++++++------ spec/client_spec.rb | 18 ++++++---- spec/fixtures.rb | 2 +- spec/posts_spec.rb | 6 ++-- spec/spec_helper.rb | 2 +- spooky.gemspec | 5 +-- 11 files changed, 122 insertions(+), 117 deletions(-) diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index e4c99b5..87051b0 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -1,19 +1,19 @@ module Spooky class Author - ATTRIBUTES = [ - "bio", - "cover_image", - "facebook", - "id", - "location", - "meta_description", - "meta_title", - "name", - "profile_image", - "slug", - "twitter", - "url", - "website" + ATTRIBUTES = %w[ + bio + cover_image + facebook + id + location + meta_description + meta_title + name + profile_image + slug + twitter + url + website ].freeze include IsResource diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index 0b550ad..816036a 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -51,10 +51,10 @@ def page_by(id: nil, slug: nil) options = {} if id.present? - response, _ = fetch("pages/#{id}", options) + response, = fetch("pages/#{id}", options) response.present? && response.first elsif slug.present? - response, _ = fetch("pages/slug/#{slug}", options) + response, = fetch("pages/slug/#{slug}", options) response.present? && response.first else false @@ -76,10 +76,10 @@ def post_by(id: nil, slug: nil, include: []) options[:include] = include if id.present? - response, _ = fetch("posts/#{id}", options) + response, = fetch("posts/#{id}", options) response.present? && response.first elsif slug.present? - response, _ = fetch("posts/slug/#{slug}", options) + response, = fetch("posts/slug/#{slug}", options) response.present? && response.first else false @@ -90,11 +90,11 @@ def post_by(id: nil, slug: nil, include: []) def apply_filter(options, filter) if filter.present? - if filter.is_a?(Hash) - options[:filter] = filter.map { |k, v| "#{k}:#{v}" }.join("+") - else - options[:filter] = filter - end + options[:filter] = if filter.is_a?(Hash) + filter.map { |k, v| "#{k}:#{v}" }.join("+") + else + filter + end end options diff --git a/lib/spooky/is_resource.rb b/lib/spooky/is_resource.rb index 159f039..ac9fff2 100644 --- a/lib/spooky/is_resource.rb +++ b/lib/spooky/is_resource.rb @@ -13,7 +13,7 @@ def initialize(attrs = {}) end def parse_datetimes(attrs) - ["created_at", "updated_at", "published_at"].each do |date_attr| + %w[created_at updated_at published_at].each do |date_attr| instance_variable_set("@#{date_attr}", DateTime.iso8601(attrs[date_attr])) if attrs[date_attr].present? end end diff --git a/lib/spooky/page.rb b/lib/spooky/page.rb index a57d000..032aaac 100644 --- a/lib/spooky/page.rb +++ b/lib/spooky/page.rb @@ -1,38 +1,38 @@ module Spooky class Page - ATTRIBUTES = [ - "canonical_url", - "codeinjection_foot", - "codeinjection_head", - "comment_id", - "created_at", - "custom_excerpt", - "custom_template", - "excerpt", - "feature_image", - "feature_image_alt", - "feature_image_caption", - "featured", - "frontmatter", - "html", - "id", - "meta_description", - "meta_title", - "og_description", - "og_image", - "og_title", - "published_at", - "reading_time", - "show_title_and_feature_image", - "slug", - "title", - "twitter_description", - "twitter_image", - "twitter_title", - "updated_at", - "url", - "uuid", - "visibility" + ATTRIBUTES = %w[ + canonical_url + codeinjection_foot + codeinjection_head + comment_id + created_at + custom_excerpt + custom_template + excerpt + feature_image + feature_image_alt + feature_image_caption + featured + frontmatter + html + id + meta_description + meta_title + og_description + og_image + og_title + published_at + reading_time + show_title_and_feature_image + slug + title + twitter_description + twitter_image + twitter_title + updated_at + url + uuid + visibility ].freeze include IsResource diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index b389f85..1f5d2d1 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -1,40 +1,40 @@ module Spooky class Post - ATTRIBUTES = [ - "authors", - "canonical_url", - "codeinjection_foot", - "codeinjection_head", - "comment_id", - "created_at", - "custom_excerpt", - "custom_template", - "email_subject", - "excerpt", - "feature_image", - "featured", - "html", - "id", - "meta_description", - "meta_title", - "og_description", - "og_image", - "og_title", - "primary_author", - "primary_tag", - "published_at", - "reading_time", - "send_email_when_published", - "slug", - "tags", - "title", - "twitter_description", - "twitter_image", - "twitter_title", - "updated_at", - "url", - "uuid", - "visibility" + ATTRIBUTES = %w[ + authors + canonical_url + codeinjection_foot + codeinjection_head + comment_id + created_at + custom_excerpt + custom_template + email_subject + excerpt + feature_image + featured + html + id + meta_description + meta_title + og_description + og_image + og_title + primary_author + primary_tag + published_at + reading_time + send_email_when_published + slug + tags + title + twitter_description + twitter_image + twitter_title + updated_at + url + uuid + visibility ].freeze include IsResource diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index 7e27329..d12c6e6 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -1,16 +1,16 @@ module Spooky class Tag - ATTRIBUTES = [ - "accent_color", - "description", - "feature_image", - "id", - "meta_description", - "meta_title", - "name", - "slug", - "url", - "visibility" + ATTRIBUTES = %w[ + accent_color + description + feature_image + id + meta_description + meta_title + name + slug + url + visibility ].freeze include IsResource diff --git a/spec/client_spec.rb b/spec/client_spec.rb index fcac03a..9c4d6e6 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -17,28 +17,32 @@ end it "gets all posts with tags and authors" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", { params: { key: "abc123", include: ["tags", "authors"] } }) + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", + { params: { key: "abc123", include: %w[tags authors] } }) client.posts(tags: true, authors: true) end it "gets a post by id" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/99/", { params: { key: "abc123" } }) + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/99/", + { params: { key: "abc123" } }) client.post_by(id: 99) end it "gets a post by slug" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/slug/this-is-a-slug/", { params: { key: "abc123" } }) - client.post_by(slug: 'this-is-a-slug') + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/slug/this-is-a-slug/", + { params: { key: "abc123" } }) + client.post_by(slug: "this-is-a-slug") end it "gets featured posts with hash filter" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", { params: { key: "abc123", filter: "featured:true" } }) + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", + { params: { key: "abc123", filter: "featured:true" } }) client.posts(filter: { featured: true }) end it "applies a string filter to the request" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", { params: { key: "abc123", filter: "title:Welcome" } }) + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", + { params: { key: "abc123", filter: "title:Welcome" } }) client.posts(filter: "title:Welcome") end - end diff --git a/spec/fixtures.rb b/spec/fixtures.rb index 78b7237..af84cbd 100644 --- a/spec/fixtures.rb +++ b/spec/fixtures.rb @@ -254,7 +254,7 @@ POST ), posts_with_authors: JSON.parse( - <<~'POST' + <<~POST { "posts": [ { diff --git a/spec/posts_spec.rb b/spec/posts_spec.rb index e02e221..5c44274 100644 --- a/spec/posts_spec.rb +++ b/spec/posts_spec.rb @@ -8,7 +8,7 @@ response = double("A parsed response", parse: FIXTURES[:simple_posts]) allow(HTTP).to receive(:get).and_return(response) - posts, _ = client.posts + posts, = client.posts expect(posts.all? { |p| p.is_a?(Spooky::Post) }).to be(true) end @@ -27,7 +27,7 @@ response = double("A parsed response", parse: FIXTURES[:posts_with_tags]) allow(HTTP).to receive(:get).and_return(response) - posts, _ = client.posts(tags: true) + posts, = client.posts(tags: true) post = posts.first expect(post.primary_tag.is_a?(Spooky::Tag)).to be(true) @@ -38,7 +38,7 @@ response = double("A parsed response", parse: FIXTURES[:posts_with_authors]) allow(HTTP).to receive(:get).and_return(response) - posts, _ = client.posts(authors: true) + posts, = client.posts(authors: true) post = posts.first expect(post.primary_author.is_a?(Spooky::Author)).to be(true) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index abcfbb1..1935a38 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,5 @@ ENV["GHOST_API_URL"] = "https://spec.test" ENV["GHOST_CONTENT_API_KEY"] = "abc123" -$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "spooky" diff --git a/spooky.gemspec b/spooky.gemspec index d8be484..0994a58 100644 --- a/spooky.gemspec +++ b/spooky.gemspec @@ -19,12 +19,13 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_runtime_dependency "activesupport", ">= 6.0" - spec.add_runtime_dependency "http", ">= 4.0" + spec.add_dependency "activesupport", ">= 6.0" + spec.add_dependency "http", ">= 4.0" spec.add_development_dependency "bundler", "~> 2.0" spec.add_development_dependency "dotenv", "~> 2.7" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rspec", "~> 3.9" spec.add_development_dependency "rubocop", "~> 0.89.0" + spec.metadata["rubygems_mfa_required"] = "true" end From 7f6ca58df24e23c62c0965c218d882fa8b108b2e Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 5 Apr 2025 10:22:04 -0400 Subject: [PATCH 09/21] Update specs and readme to match newer APIs --- README.md | 6 +++--- lib/spooky/client.rb | 6 +++--- spec/client_spec.rb | 14 +++++++------- spec/posts_spec.rb | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 89f60a9..9aea9c6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A simple Ruby wrapper for the [Ghost](https://ghost.org) Content API. -The Ghost Content API documentation can be found [here](https://ghost.org/docs/api/v3/content/#endpoints). +The Ghost Content API documentation can be found [here](https://ghost.org/docs/content-api/). ## Installation @@ -77,7 +77,7 @@ Both return a `Spooky::Post`. #### Get posts with a filter applied -Filtering accepts simple hashes as conditions or [NQL strings for more complex filters](https://ghost.org/docs/api/v3/content/#syntax-reference). +Filtering accepts simple hashes as conditions or [NQL strings for more complex filters](https://ghost.org/docs/api/content/#syntax-reference). ```ruby featured_posts, pagination = client.posts(filter: { featured: true }) @@ -125,4 +125,4 @@ The gem is available as open source under the terms of the [MIT License](http:// ## Credits -This gem was originally developed by [infinityrobot](https://github.com/infinityrobot). \ No newline at end of file +This gem was originally developed by [infinityrobot](https://github.com/infinityrobot). diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index 816036a..ce2bc88 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -39,7 +39,7 @@ def fetch(resource, options = {}) def pages(include: [], filter: false, page: false, limit: false) options = {} - options[:include] = include + options[:include] = include unless include.empty? options = apply_filter(options, filter) options = apply_pagination(options, { page: page, limit: limit }) @@ -63,7 +63,7 @@ def page_by(id: nil, slug: nil) def posts(include: [], filter: false, page: false, limit: false) options = {} - options[:include] = include + options[:include] = include unless include.empty? options = apply_filter(options, filter) options = apply_pagination(options, { page: page, limit: limit }) @@ -73,7 +73,7 @@ def posts(include: [], filter: false, page: false, limit: false) def post_by(id: nil, slug: nil, include: []) options = {} - options[:include] = include + options[:include] = include unless include.empty? if id.present? response, = fetch("posts/#{id}", options) diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 9c4d6e6..288e99c 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -12,36 +12,36 @@ end it "gets all posts" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", { params: { key: "abc123" } }) + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", { params: { key: "abc123" } }) client.posts end it "gets all posts with tags and authors" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", { params: { key: "abc123", include: %w[tags authors] } }) - client.posts(tags: true, authors: true) + client.posts(include: %w[tags authors]) end it "gets a post by id" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/99/", + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/99/", { params: { key: "abc123" } }) client.post_by(id: 99) end it "gets a post by slug" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/slug/this-is-a-slug/", + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/slug/this-is-a-slug/", { params: { key: "abc123" } }) client.post_by(slug: "this-is-a-slug") end it "gets featured posts with hash filter" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", { params: { key: "abc123", filter: "featured:true" } }) client.posts(filter: { featured: true }) end it "applies a string filter to the request" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/v3/content/posts/", + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", { params: { key: "abc123", filter: "title:Welcome" } }) client.posts(filter: "title:Welcome") end diff --git a/spec/posts_spec.rb b/spec/posts_spec.rb index 5c44274..16367f7 100644 --- a/spec/posts_spec.rb +++ b/spec/posts_spec.rb @@ -27,7 +27,7 @@ response = double("A parsed response", parse: FIXTURES[:posts_with_tags]) allow(HTTP).to receive(:get).and_return(response) - posts, = client.posts(tags: true) + posts, = client.posts(include: %w[tags]) post = posts.first expect(post.primary_tag.is_a?(Spooky::Tag)).to be(true) @@ -38,7 +38,7 @@ response = double("A parsed response", parse: FIXTURES[:posts_with_authors]) allow(HTTP).to receive(:get).and_return(response) - posts, = client.posts(authors: true) + posts, = client.posts(include: %w[authors]) post = posts.first expect(post.primary_author.is_a?(Spooky::Author)).to be(true) From bda095a1dc80f3fd22e6cb594f7928e1c2e48db0 Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 5 Apr 2025 10:23:25 -0400 Subject: [PATCH 10/21] rubocop -A Adds string literal comments --- Gemfile | 2 ++ Rakefile | 2 ++ bin/console | 1 + lib/spooky.rb | 2 ++ lib/spooky/author.rb | 2 ++ lib/spooky/client.rb | 2 ++ lib/spooky/is_resource.rb | 2 ++ lib/spooky/page.rb | 2 ++ lib/spooky/post.rb | 2 ++ lib/spooky/tag.rb | 2 ++ lib/spooky/version.rb | 4 +++- spec/client_spec.rb | 2 ++ spec/fixtures.rb | 2 ++ spec/posts_spec.rb | 2 ++ spec/spec_helper.rb | 2 ++ spec/spooky_spec.rb | 2 ++ spooky.gemspec | 2 ++ 17 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 96d6f2e..11968ad 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source "https://rubygems.org" # Specify your gem's dependencies in spooky.gemspec diff --git a/Rakefile b/Rakefile index c29cca0..41b8864 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "bundler/gem_tasks" require "rubocop/rake_task" require "rspec/core/rake_task" diff --git a/bin/console b/bin/console index 364599e..9d8ef9b 100755 --- a/bin/console +++ b/bin/console @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require "bundler/setup" require "spooky" diff --git a/lib/spooky.rb b/lib/spooky.rb index e3434bc..fed54e5 100644 --- a/lib/spooky.rb +++ b/lib/spooky.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spooky/version" require "spooky/is_resource" diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index 87051b0..eade1ba 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spooky class Author ATTRIBUTES = %w[ diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index ce2bc88..5242b0b 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "http" require "active_support/core_ext/object/blank" require "active_support/core_ext/string/inflections" diff --git a/lib/spooky/is_resource.rb b/lib/spooky/is_resource.rb index ac9fff2..01534cf 100644 --- a/lib/spooky/is_resource.rb +++ b/lib/spooky/is_resource.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module IsResource def self.included(base) base.class_eval do diff --git a/lib/spooky/page.rb b/lib/spooky/page.rb index 032aaac..f02705a 100644 --- a/lib/spooky/page.rb +++ b/lib/spooky/page.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spooky class Page ATTRIBUTES = %w[ diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index 1f5d2d1..b90b6b1 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spooky class Post ATTRIBUTES = %w[ diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index d12c6e6..5a12a8b 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spooky class Tag ATTRIBUTES = %w[ diff --git a/lib/spooky/version.rb b/lib/spooky/version.rb index 52a312f..1615384 100644 --- a/lib/spooky/version.rb +++ b/lib/spooky/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spooky - VERSION = "2.0.0".freeze + VERSION = "2.0.0" end diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 288e99c..63446cc 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" ENV["GHOST_API_URL"] = "https://spec.test" diff --git a/spec/fixtures.rb b/spec/fixtures.rb index af84cbd..e2cd88c 100644 --- a/spec/fixtures.rb +++ b/spec/fixtures.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + FIXTURES = { simple_posts: JSON.parse( <<~'POST' diff --git a/spec/posts_spec.rb b/spec/posts_spec.rb index 16367f7..44bfd5a 100644 --- a/spec/posts_spec.rb +++ b/spec/posts_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" require "fixtures" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1935a38..746e16a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ENV["GHOST_API_URL"] = "https://spec.test" ENV["GHOST_CONTENT_API_KEY"] = "abc123" diff --git a/spec/spooky_spec.rb b/spec/spooky_spec.rb index 868915a..0142531 100644 --- a/spec/spooky_spec.rb +++ b/spec/spooky_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Spooky do diff --git a/spooky.gemspec b/spooky.gemspec index 0994a58..e758b3c 100644 --- a/spooky.gemspec +++ b/spooky.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "spooky/version" From d0436e94c011dedf277d9c592b965636b18f90c3 Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Fri, 18 Apr 2025 23:18:18 -0400 Subject: [PATCH 11/21] Switch to a base class instead of IsResource module --- lib/spooky.rb | 2 +- lib/spooky/author.rb | 4 ++-- lib/spooky/base.rb | 25 +++++++++++++++++++++++++ lib/spooky/is_resource.rb | 28 ---------------------------- lib/spooky/page.rb | 4 ++-- lib/spooky/post.rb | 4 ++-- lib/spooky/tag.rb | 4 ++-- 7 files changed, 34 insertions(+), 37 deletions(-) create mode 100644 lib/spooky/base.rb delete mode 100644 lib/spooky/is_resource.rb diff --git a/lib/spooky.rb b/lib/spooky.rb index fed54e5..175287c 100644 --- a/lib/spooky.rb +++ b/lib/spooky.rb @@ -2,7 +2,7 @@ require "spooky/version" -require "spooky/is_resource" +require "spooky/base" require "spooky/client" require "spooky/page" require "spooky/post" diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index eade1ba..ac1023a 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Spooky - class Author + class Author < Base ATTRIBUTES = %w[ bio cover_image @@ -18,6 +18,6 @@ class Author website ].freeze - include IsResource + attr_reader(*const_get("ATTRIBUTES")) end end diff --git a/lib/spooky/base.rb b/lib/spooky/base.rb new file mode 100644 index 0000000..427412e --- /dev/null +++ b/lib/spooky/base.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Spooky + class Base + def initialize(attrs = {}) + self.class.const_get("ATTRIBUTES").each do |attribute| + instance_variable_set("@#{attribute}", attrs[attribute]) + end + + parse_datetimes(attrs) + parse_attributes(attrs) + end + + def parse_datetimes(attrs) + %w[created_at updated_at published_at].each do |date_attr| + instance_variable_set("@#{date_attr}", DateTime.iso8601(attrs[date_attr])) if attrs[date_attr].present? + end + end + + def parse_attributes(attrs) + # Abstract method, should be overridden in child if needed. + end + + end +end diff --git a/lib/spooky/is_resource.rb b/lib/spooky/is_resource.rb deleted file mode 100644 index 01534cf..0000000 --- a/lib/spooky/is_resource.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module IsResource - def self.included(base) - base.class_eval do - attr_reader(*const_get("ATTRIBUTES")) - - def initialize(attrs = {}) - self.class.const_get("ATTRIBUTES").each do |attribute| - instance_variable_set("@#{attribute}", attrs[attribute]) - end - - parse_datetimes(attrs) - parse_attributes(attrs) - end - - def parse_datetimes(attrs) - %w[created_at updated_at published_at].each do |date_attr| - instance_variable_set("@#{date_attr}", DateTime.iso8601(attrs[date_attr])) if attrs[date_attr].present? - end - end - - def parse_attributes(attrs) - # Abstract method, should be overridden in child if needed. - end - end - end -end diff --git a/lib/spooky/page.rb b/lib/spooky/page.rb index f02705a..5e4fe46 100644 --- a/lib/spooky/page.rb +++ b/lib/spooky/page.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Spooky - class Page + class Page < Base ATTRIBUTES = %w[ canonical_url codeinjection_foot @@ -37,6 +37,6 @@ class Page visibility ].freeze - include IsResource + attr_reader(*const_get("ATTRIBUTES")) end end diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index b90b6b1..0b9ea44 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Spooky - class Post + class Post < Base ATTRIBUTES = %w[ authors canonical_url @@ -39,7 +39,7 @@ class Post visibility ].freeze - include IsResource + attr_reader(*const_get("ATTRIBUTES")) def parse_attributes(attrs) author = attrs["primary_author"] diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index 5a12a8b..d56f1e1 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Spooky - class Tag + class Tag < Base ATTRIBUTES = %w[ accent_color description @@ -15,6 +15,6 @@ class Tag visibility ].freeze - include IsResource + attr_reader(*const_get("ATTRIBUTES")) end end From 351278986bf81f30dc7cb2a8e4a7f3487d1150dd Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 00:04:02 -0400 Subject: [PATCH 12/21] Leverage ActiveModel --- lib/spooky.rb | 6 +-- lib/spooky/author.rb | 33 ++++++------ lib/spooky/base.rb | 25 --------- lib/spooky/page.rb | 39 +------------- lib/spooky/post.rb | 119 ++++++++++++++++++++++++------------------- lib/spooky/tag.rb | 38 ++++++++------ spooky.gemspec | 1 + 7 files changed, 113 insertions(+), 148 deletions(-) delete mode 100644 lib/spooky/base.rb diff --git a/lib/spooky.rb b/lib/spooky.rb index 175287c..7ee1857 100644 --- a/lib/spooky.rb +++ b/lib/spooky.rb @@ -2,9 +2,9 @@ require "spooky/version" -require "spooky/base" +require "active_model" require "spooky/client" -require "spooky/page" -require "spooky/post" require "spooky/author" require "spooky/tag" +require "spooky/post" +require "spooky/page" diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index ac1023a..71e5034 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -1,23 +1,22 @@ # frozen_string_literal: true module Spooky - class Author < Base - ATTRIBUTES = %w[ - bio - cover_image - facebook - id - location - meta_description - meta_title - name - profile_image - slug - twitter - url - website - ].freeze + class Author + include ActiveModel::Model + include ActiveModel::Attributes - attr_reader(*const_get("ATTRIBUTES")) + attribute :slug, :string + attribute :id, :string + attribute :name, :string + attribute :profile_image, :string + attribute :cover_image, :string + attribute :bio, :string + attribute :website, :string + attribute :location, :string + attribute :facebook, :string + attribute :twitter, :string + attribute :meta_title, :string + attribute :meta_description, :string + attribute :url, :string end end diff --git a/lib/spooky/base.rb b/lib/spooky/base.rb deleted file mode 100644 index 427412e..0000000 --- a/lib/spooky/base.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Spooky - class Base - def initialize(attrs = {}) - self.class.const_get("ATTRIBUTES").each do |attribute| - instance_variable_set("@#{attribute}", attrs[attribute]) - end - - parse_datetimes(attrs) - parse_attributes(attrs) - end - - def parse_datetimes(attrs) - %w[created_at updated_at published_at].each do |date_attr| - instance_variable_set("@#{date_attr}", DateTime.iso8601(attrs[date_attr])) if attrs[date_attr].present? - end - end - - def parse_attributes(attrs) - # Abstract method, should be overridden in child if needed. - end - - end -end diff --git a/lib/spooky/page.rb b/lib/spooky/page.rb index 5e4fe46..83e9f19 100644 --- a/lib/spooky/page.rb +++ b/lib/spooky/page.rb @@ -1,42 +1,7 @@ # frozen_string_literal: true module Spooky - class Page < Base - ATTRIBUTES = %w[ - canonical_url - codeinjection_foot - codeinjection_head - comment_id - created_at - custom_excerpt - custom_template - excerpt - feature_image - feature_image_alt - feature_image_caption - featured - frontmatter - html - id - meta_description - meta_title - og_description - og_image - og_title - published_at - reading_time - show_title_and_feature_image - slug - title - twitter_description - twitter_image - twitter_title - updated_at - url - uuid - visibility - ].freeze - - attr_reader(*const_get("ATTRIBUTES")) + class Page < Post + # Per docs, "Pages are structured identically to posts."" end end diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index 0b9ea44..3d330aa 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -1,60 +1,77 @@ # frozen_string_literal: true module Spooky - class Post < Base - ATTRIBUTES = %w[ - authors - canonical_url - codeinjection_foot - codeinjection_head - comment_id - created_at - custom_excerpt - custom_template - email_subject - excerpt - feature_image - featured - html - id - meta_description - meta_title - og_description - og_image - og_title - primary_author - primary_tag - published_at - reading_time - send_email_when_published - slug - tags - title - twitter_description - twitter_image - twitter_title - updated_at - url - uuid - visibility - ].freeze - - attr_reader(*const_get("ATTRIBUTES")) - - def parse_attributes(attrs) - author = attrs["primary_author"] - @primary_author = author.present? && Spooky::Author.new(author) - - @authors = (attrs["authors"] || []).map do |author| - Spooky::Author.new(author) - end + class Post + include ActiveModel::Model + include ActiveModel::Attributes + + attribute :slug, :string + attribute :id, :immutable_string + attribute :uuid, :immutable_string + attribute :title, :string + attribute :html, :string + attribute :comment_id, :string + attribute :feature_image, :string + attribute :feature_image_alt, :string + attribute :feature_image_caption, :string + attribute :featured, :boolean + attribute :visibility, :string + attribute :created_at, :datetime + attribute :updated_at, :datetime + attribute :published_at, :datetime + attribute :custom_excerpt, :string + attribute :codeinjection_head, :string + attribute :codeinjection_foot, :string + attribute :custom_template, :string + attribute :canonical_url, :string + attribute :url, :string + attribute :excerpt, :string + attribute :reading_time, :integer + attribute :access, :boolean + attribute :og_image, :string + attribute :og_title, :string + attribute :og_description, :string + attribute :twitter_image, :string + attribute :twitter_title, :string + attribute :twitter_description, :string + attribute :meta_title, :string + attribute :meta_description, :string + attribute :email_subject, :string + attribute :send_email_when_published, :boolean + attribute :frontmatter, :string + attribute :show_title_and_feature_image, :boolean + attribute :visibility, :string + attribute :meta_description, :string + attribute :meta_title, :string + + attr_reader :primary_author, :primary_tag - tag = attrs["primary_tag"] - @primary_tag = tag.present? && Spooky::Tag.new(tag) + def authors + @authors ||= [] + end + + def authors=(attributes) + @authors = attributes.map do |i, author_attrs| + Author.new(author_attrs) + end + end - @tags = (attrs["tags"] || []).map do |tag| - Spooky::Tag.new(tag) + def tags + @tags ||= [] + end + + def tags=(attributes) + @tags = attributes.map do |tag_attrs| + Tag.new(tag_attrs) end end + + def primary_author=(attributes) + @primary_author = Author.new(attributes) + end + + def primary_tag=(attributes) + @primary_tag = Tag.new(attributes) + end end end diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index d56f1e1..314c259 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -1,20 +1,28 @@ # frozen_string_literal: true module Spooky - class Tag < Base - ATTRIBUTES = %w[ - accent_color - description - feature_image - id - meta_description - meta_title - name - slug - url - visibility - ].freeze - - attr_reader(*const_get("ATTRIBUTES")) + class Tag + include ActiveModel::Model + include ActiveModel::Attributes + + attribute :slug, :string + attribute :id, :immutable_string + attribute :name, :string + attribute :description, :string + attribute :feature_image, :string + attribute :visibility, :string + attribute :meta_title, :string + attribute :meta_description, :string + attribute :og_image, :string + attribute :og_title, :string + attribute :og_description, :string + attribute :twitter_image, :string + attribute :twitter_title, :string + attribute :twitter_description, :string + attribute :codeinjection_head, :string + attribute :codeinjection_foot, :string + attribute :canonical_url, :string + attribute :accent_color, :string + attribute :url, :string end end diff --git a/spooky.gemspec b/spooky.gemspec index e758b3c..c33e414 100644 --- a/spooky.gemspec +++ b/spooky.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "activesupport", ">= 6.0" + spec.add_dependency "activemodel", ">= 6.0" spec.add_dependency "http", ">= 4.0" spec.add_development_dependency "bundler", "~> 2.0" From 760a1c2396dbef5eabd7fbcc4c14045bbc0d005d Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 10:19:38 -0400 Subject: [PATCH 13/21] Add a base class to implement attribute_writer_missing By default it was erroring on new fields. That's a bit aggressive, so just warn instead. --- lib/spooky.rb | 1 + lib/spooky/author.rb | 10 +++++++++- lib/spooky/base.rb | 14 ++++++++++++++ lib/spooky/post.rb | 7 +++---- lib/spooky/tag.rb | 5 +---- 5 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 lib/spooky/base.rb diff --git a/lib/spooky.rb b/lib/spooky.rb index 7ee1857..1fce108 100644 --- a/lib/spooky.rb +++ b/lib/spooky.rb @@ -4,6 +4,7 @@ require "active_model" require "spooky/client" +require "spooky/base" require "spooky/author" require "spooky/tag" require "spooky/post" diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index 71e5034..158171a 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Spooky - class Author + class Author < Base include ActiveModel::Model include ActiveModel::Attributes @@ -18,5 +18,13 @@ class Author attribute :meta_title, :string attribute :meta_description, :string attribute :url, :string + attribute :threads, :string + attribute :bluesky, :string + attribute :mastodon, :string + attribute :tiktok, :string + attribute :instagram, :string + attribute :linkedin, :string + attribute :youtube, :string + attribute :comments, :boolean end end diff --git a/lib/spooky/base.rb b/lib/spooky/base.rb new file mode 100644 index 0000000..12404f7 --- /dev/null +++ b/lib/spooky/base.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Spooky + class Base + include ActiveModel::Model + include ActiveModel::Attributes + + def attribute_writer_missing(name, value) + # Typically means they added something to the API. + # Most likely a new social network. + puts "Unexpected attribute for #{self.class.name}: #{name}" + end + end +end diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index 3d330aa..f9cd0e9 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -1,10 +1,7 @@ # frozen_string_literal: true module Spooky - class Post - include ActiveModel::Model - include ActiveModel::Attributes - + class Post < Base attribute :slug, :string attribute :id, :immutable_string attribute :uuid, :immutable_string @@ -43,6 +40,8 @@ class Post attribute :visibility, :string attribute :meta_description, :string attribute :meta_title, :string + attribute :comments, :boolean + attr_reader :primary_author, :primary_tag diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index 314c259..c5e141b 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -1,10 +1,7 @@ # frozen_string_literal: true module Spooky - class Tag - include ActiveModel::Model - include ActiveModel::Attributes - + class Tag < Base attribute :slug, :string attribute :id, :immutable_string attribute :name, :string From 3f3d6f13d334f8260c64b0b829c79d859b0cbc55 Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 13:56:59 -0400 Subject: [PATCH 14/21] Add a persisted? method Otherwise it always defaults to false. Not really needed, but adding for correctness --- lib/spooky/base.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/spooky/base.rb b/lib/spooky/base.rb index 12404f7..d4084a8 100644 --- a/lib/spooky/base.rb +++ b/lib/spooky/base.rb @@ -10,5 +10,9 @@ def attribute_writer_missing(name, value) # Most likely a new social network. puts "Unexpected attribute for #{self.class.name}: #{name}" end + + def persisted? + self.id.present? + end end end From 6f874e32f338e7499cf0cd6f2e026441359dd4bf Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 13:57:16 -0400 Subject: [PATCH 15/21] Add to_s and to_param methods --- lib/spooky/author.rb | 8 ++++++++ lib/spooky/post.rb | 10 +++++++++- lib/spooky/tag.rb | 8 ++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index 158171a..a9488d4 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -26,5 +26,13 @@ class Author < Base attribute :linkedin, :string attribute :youtube, :string attribute :comments, :boolean + + def to_s + name.to_s + end + + def to_param + slug.to_s + end end end diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index f9cd0e9..de21e83 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -50,7 +50,7 @@ def authors end def authors=(attributes) - @authors = attributes.map do |i, author_attrs| + @authors = attributes.map do |author_attrs| Author.new(author_attrs) end end @@ -72,5 +72,13 @@ def primary_author=(attributes) def primary_tag=(attributes) @primary_tag = Tag.new(attributes) end + + def to_s + title.to_s + end + + def to_param + slug.to_s + end end end diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index c5e141b..9755b4e 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -22,4 +22,12 @@ class Tag < Base attribute :accent_color, :string attribute :url, :string end + + def to_s + name.to_s + end + + def to_param + slug.to_s + end end From ac35b748fb44806096d939c8d69e9607902c3bff Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 14:17:26 -0400 Subject: [PATCH 16/21] Add tag and author fetching --- lib/spooky/client.rb | 50 ++++++++++++++++++++++++ lib/spooky/post.rb | 1 - spec/authors_spec.rb | 40 +++++++++++++++++++ spec/client_spec.rb | 91 ++++++++++++++++++++++++++++++++------------ spec/fixtures.rb | 81 +++++++++++++++++++++++++++++++++++++++ spec/tags_spec.rb | 40 +++++++++++++++++++ 6 files changed, 277 insertions(+), 26 deletions(-) create mode 100644 spec/authors_spec.rb create mode 100644 spec/tags_spec.rb diff --git a/lib/spooky/client.rb b/lib/spooky/client.rb index 5242b0b..1ef5adc 100644 --- a/lib/spooky/client.rb +++ b/lib/spooky/client.rb @@ -88,6 +88,56 @@ def post_by(id: nil, slug: nil, include: []) end end + def authors(include: [], filter: false, page: false, limit: false) + options = {} + options[:include] = include unless include.empty? + + options = apply_filter(options, filter) + options = apply_pagination(options, { page: page, limit: limit }) + + fetch("authors", options) + end + + def author_by(id: nil, slug: nil, include: []) + options = {} + options[:include] = include unless include.empty? + + if id.present? + response, = fetch("authors/#{id}", options) + response.present? && response.first + elsif slug.present? + response, = fetch("authors/slug/#{slug}", options) + response.present? && response.first + else + false + end + end + + def tags(include: [], filter: false, page: false, limit: false) + options = {} + options[:include] = include unless include.empty? + + options = apply_filter(options, filter) + options = apply_pagination(options, { page: page, limit: limit }) + + fetch("tags", options) + end + + def tag_by(id: nil, slug: nil, include: []) + options = {} + options[:include] = include unless include.empty? + + if id.present? + response, = fetch("tags/#{id}", options) + response.present? && response.first + elsif slug.present? + response, = fetch("tags/slug/#{slug}", options) + response.present? && response.first + else + false + end + end + private def apply_filter(options, filter) diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index de21e83..c9711fb 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -42,7 +42,6 @@ class Post < Base attribute :meta_title, :string attribute :comments, :boolean - attr_reader :primary_author, :primary_tag def authors diff --git a/spec/authors_spec.rb b/spec/authors_spec.rb new file mode 100644 index 0000000..2316a76 --- /dev/null +++ b/spec/authors_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "spec_helper" +require "fixtures" + +describe Spooky::Author do + let(:client) { Spooky::Client.new } + + it "returns an array of Authors" do + response = double("A parsed response", parse: FIXTURES[:authors]) + allow(HTTP).to receive(:get).and_return(response) + + authors, = client.authors + + expect(authors.all? { |p| p.is_a?(Spooky::Author) }).to be(true) + end + + it "returns the pagination information with a browse request" do + response = double("A parsed response", parse: FIXTURES[:authors]) + allow(HTTP).to receive(:get).and_return(response) + + _, pagination = client.authors + + expect(pagination).to be_present + expect(pagination.keys).to include("page", "limit", "pages", "total") + end + + + it "returns a requested author by ID" do + response = double("A parsed response", parse: FIXTURES[:authors]) + allow(HTTP).to receive(:get).and_return(response) + + # This doesn't actually query by the ID. We are testing attribute creation here. + author = client.author_by(id: "1") + + expect(author.id).to eq("1") + expect(author.name).to eq("Georges Gabereau") + expect(author.slug).to eq("georges") + end +end diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 63446cc..b5a0658 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -13,38 +13,79 @@ allow(HTTP).to receive(:get).and_return(response) end - it "gets all posts" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", { params: { key: "abc123" } }) - client.posts - end + describe "posts" do + it "gets all posts" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", { params: { key: "abc123" } }) + client.posts + end - it "gets all posts with tags and authors" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", - { params: { key: "abc123", include: %w[tags authors] } }) - client.posts(include: %w[tags authors]) - end + it "gets all posts with tags and authors" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", + { params: { key: "abc123", include: %w[tags authors] } }) + client.posts(include: %w[tags authors]) + end + + it "gets a post by id" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/99/", + { params: { key: "abc123" } }) + client.post_by(id: 99) + end + + it "gets all posts with tags and authors" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", + { params: { key: "abc123", include: %w[tags authors] } }) + client.posts(include: %w[tags authors]) + end - it "gets a post by id" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/99/", - { params: { key: "abc123" } }) - client.post_by(id: 99) + it "gets featured posts with hash filter" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", + { params: { key: "abc123", filter: "featured:true" } }) + client.posts(filter: { featured: true }) + end + + it "applies a string filter to the request" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", + { params: { key: "abc123", filter: "title:Welcome" } }) + client.posts(filter: "title:Welcome") + end end - it "gets a post by slug" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/slug/this-is-a-slug/", - { params: { key: "abc123" } }) - client.post_by(slug: "this-is-a-slug") + describe "pages" do + it "gets all pages" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/pages/", { params: { key: "abc123" } }) + client.pages + end + + it "gets a page by id" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/pages/1/", + { params: { key: "abc123" } }) + client.page_by(id: 1) + end end - it "gets featured posts with hash filter" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", - { params: { key: "abc123", filter: "featured:true" } }) - client.posts(filter: { featured: true }) + describe "authors" do + it "gets all authors" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/authors/", { params: { key: "abc123" } }) + client.authors + end + + it "gets a author by id" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/authors/1/", + { params: { key: "abc123" } }) + client.author_by(id: 1) + end end - it "applies a string filter to the request" do - expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", - { params: { key: "abc123", filter: "title:Welcome" } }) - client.posts(filter: "title:Welcome") + describe "tags" do + it "gets all tags" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/tags/", { params: { key: "abc123" } }) + client.tags + end + + it "gets a tag by id" do + expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/tags/5f397fe0a185384852d1f144/", + { params: { key: "abc123" } }) + client.tag_by(id: "5f397fe0a185384852d1f144") + end end end diff --git a/spec/fixtures.rb b/spec/fixtures.rb index e2cd88c..7b36b46 100644 --- a/spec/fixtures.rb +++ b/spec/fixtures.rb @@ -337,5 +337,86 @@ } } POST + ), + + authors: JSON.parse( + <<~AUTHORS + { + "authors": [ + { + "id": "1", + "name": "Georges Gabereau", + "slug": "georges", + "profile_image": null, + "cover_image": null, + "bio": null, + "website": null, + "location": null, + "facebook": null, + "twitter": null, + "meta_title": null, + "meta_description": null, + "threads": null, + "bluesky": null, + "mastodon": null, + "tiktok": null, + "youtube": null, + "instagram": null, + "linkedin": null, + "url": "https://blog.spec.test/author/georges/" + } + ], + "meta": { + "pagination": { + "page": 1, + "limit": 15, + "pages": 1, + "total": 5, + "next": null, + "prev": null + } + } + } + AUTHORS + ), + + tags: JSON.parse( + <<~TAGS + { + "tags": [ + { + "id": "5f397fe0a185384852d1f144", + "name": "Getting Started", + "slug": "getting-started", + "description": null, + "feature_image": null, + "visibility": "public", + "og_image": null, + "og_title": null, + "og_description": null, + "twitter_image": null, + "twitter_title": null, + "twitter_description": null, + "meta_title": null, + "meta_description": null, + "codeinjection_head": null, + "codeinjection_foot": null, + "canonical_url": null, + "accent_color": null, + "url": "https://blog.spec.test.ghost.io/tag/getting-started/" + } + ], + "meta": { + "pagination": { + "page": 1, + "limit": 15, + "pages": 1, + "total": 1, + "next": null, + "prev": null + } + } + } + TAGS ) }.freeze diff --git a/spec/tags_spec.rb b/spec/tags_spec.rb new file mode 100644 index 0000000..5a532e0 --- /dev/null +++ b/spec/tags_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "spec_helper" +require "fixtures" + +describe Spooky::Tag do + let(:client) { Spooky::Client.new } + + it "returns an array of Tags" do + response = double("A parsed response", parse: FIXTURES[:tags]) + allow(HTTP).to receive(:get).and_return(response) + + tags, = client.tags + + expect(tags.all? { |p| p.is_a?(Spooky::Tag) }).to be(true) + end + + it "returns the pagination information with a browse request" do + response = double("A parsed response", parse: FIXTURES[:tags]) + allow(HTTP).to receive(:get).and_return(response) + + _, pagination = client.tags + + expect(pagination).to be_present + expect(pagination.keys).to include("page", "limit", "pages", "total") + end + + + it "returns a requested tag by ID" do + response = double("A parsed response", parse: FIXTURES[:tags]) + allow(HTTP).to receive(:get).and_return(response) + + # This doesn't actually query by the ID. We are testing attribute creation here. + tag = client.tag_by(id: "5f397fe0a185384852d1f144") + + expect(tag.id).to eq("5f397fe0a185384852d1f144") + expect(tag.name).to eq("Getting Started") + expect(tag.slug).to eq("getting-started") + end +end From b6bf8170ceea526a3925c0e5c3399d257bd630aa Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 14:59:28 -0400 Subject: [PATCH 17/21] Add plaintext attribute to post/page --- lib/spooky/post.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index c9711fb..e40b858 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -7,6 +7,7 @@ class Post < Base attribute :uuid, :immutable_string attribute :title, :string attribute :html, :string + attribute :plaintext, :string attribute :comment_id, :string attribute :feature_image, :string attribute :feature_image_alt, :string From e4783ca20e04c5a1a9c82cf524e548bb5a04b96d Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 14:59:55 -0400 Subject: [PATCH 18/21] Add post counts and fix tag to_s and to_param --- lib/spooky.rb | 1 + lib/spooky/author.rb | 6 +++ lib/spooky/count.rb | 7 ++++ lib/spooky/tag.rb | 18 ++++++--- spec/authors_spec.rb | 13 ++++++- spec/fixtures.rb | 87 ++++++++++++++++++++++++++++++++++++++++++++ spec/tags_spec.rb | 13 ++++++- 7 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 lib/spooky/count.rb diff --git a/lib/spooky.rb b/lib/spooky.rb index 1fce108..2deba6c 100644 --- a/lib/spooky.rb +++ b/lib/spooky.rb @@ -5,6 +5,7 @@ require "active_model" require "spooky/client" require "spooky/base" +require "spooky/count" require "spooky/author" require "spooky/tag" require "spooky/post" diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index a9488d4..685d8e4 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -27,6 +27,12 @@ class Author < Base attribute :youtube, :string attribute :comments, :boolean + attr_reader :count + + def count=(attributes) + @count = Count.new(attributes) + end + def to_s name.to_s end diff --git a/lib/spooky/count.rb b/lib/spooky/count.rb new file mode 100644 index 0000000..a0c6aeb --- /dev/null +++ b/lib/spooky/count.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Spooky + class Count < Base + attribute :posts, :integer + end +end diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index 9755b4e..fbc51b7 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -21,13 +21,19 @@ class Tag < Base attribute :canonical_url, :string attribute :accent_color, :string attribute :url, :string - end - def to_s - name.to_s - end + attr_reader :count - def to_param - slug.to_s + def count=(attributes) + @count = Count.new(attributes) + end + + def to_s + name.to_s + end + + def to_param + slug.to_s + end end end diff --git a/spec/authors_spec.rb b/spec/authors_spec.rb index 2316a76..6f427a4 100644 --- a/spec/authors_spec.rb +++ b/spec/authors_spec.rb @@ -25,7 +25,6 @@ expect(pagination.keys).to include("page", "limit", "pages", "total") end - it "returns a requested author by ID" do response = double("A parsed response", parse: FIXTURES[:authors]) allow(HTTP).to receive(:get).and_return(response) @@ -37,4 +36,16 @@ expect(author.name).to eq("Georges Gabereau") expect(author.slug).to eq("georges") end + + it "returns an array of Authors with post count" do + response = double("A parsed response", parse: FIXTURES[:authors_with_count]) + allow(HTTP).to receive(:get).and_return(response) + + authors, = client.authors(include: "count.posts") + + expect(authors.first.id).to eq("1") + expect(authors.first.name).to eq("Georges Gabereau") + expect(authors.first.slug).to eq("georges") + expect(authors.first.count.posts).to eq(5) + end end diff --git a/spec/fixtures.rb b/spec/fixtures.rb index 7b36b46..d22dfc6 100644 --- a/spec/fixtures.rb +++ b/spec/fixtures.rb @@ -380,6 +380,50 @@ AUTHORS ), + authors_with_count: JSON.parse( + <<~AUTHORS + { + "authors": [ + { + "id": "1", + "name": "Georges Gabereau", + "slug": "georges", + "profile_image": null, + "cover_image": null, + "bio": null, + "website": null, + "location": null, + "facebook": null, + "twitter": null, + "meta_title": null, + "meta_description": null, + "threads": null, + "bluesky": null, + "mastodon": null, + "tiktok": null, + "youtube": null, + "instagram": null, + "linkedin": null, + "count": { + "posts": 5 + }, + "url": "https://blog.spec.test/author/georges/" + } + ], + "meta": { + "pagination": { + "page": 1, + "limit": 15, + "pages": 1, + "total": 5, + "next": null, + "prev": null + } + } + } + AUTHORS + ), + tags: JSON.parse( <<~TAGS { @@ -418,5 +462,48 @@ } } TAGS + ), + + tags_with_count: JSON.parse( + <<~TAGS + { + "tags": [ + { + "id": "5f397fe0a185384852d1f144", + "name": "Getting Started", + "slug": "getting-started", + "description": null, + "feature_image": null, + "visibility": "public", + "og_image": null, + "og_title": null, + "og_description": null, + "twitter_image": null, + "twitter_title": null, + "twitter_description": null, + "meta_title": null, + "meta_description": null, + "codeinjection_head": null, + "codeinjection_foot": null, + "canonical_url": null, + "accent_color": null, + "count": { + "posts": 5 + }, + "url": "https://blog.spec.test.ghost.io/tag/getting-started/" + } + ], + "meta": { + "pagination": { + "page": 1, + "limit": 15, + "pages": 1, + "total": 1, + "next": null, + "prev": null + } + } + } + TAGS ) }.freeze diff --git a/spec/tags_spec.rb b/spec/tags_spec.rb index 5a532e0..ac2d50f 100644 --- a/spec/tags_spec.rb +++ b/spec/tags_spec.rb @@ -25,7 +25,6 @@ expect(pagination.keys).to include("page", "limit", "pages", "total") end - it "returns a requested tag by ID" do response = double("A parsed response", parse: FIXTURES[:tags]) allow(HTTP).to receive(:get).and_return(response) @@ -37,4 +36,16 @@ expect(tag.name).to eq("Getting Started") expect(tag.slug).to eq("getting-started") end + + it "returns an array of Tags with post count" do + response = double("A parsed response", parse: FIXTURES[:tags_with_count]) + allow(HTTP).to receive(:get).and_return(response) + + tags, = client.tags(include: "count.posts") + + expect(tags.first.id).to eq("5f397fe0a185384852d1f144") + expect(tags.first.name).to eq("Getting Started") + expect(tags.first.slug).to eq("getting-started") + expect(tags.first.count.posts).to eq(5) + end end From 39255a48a0b2f3c82891c31499923cb53a4596dd Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sat, 19 Apr 2025 15:05:22 -0400 Subject: [PATCH 19/21] Remove duplicate lines --- lib/spooky/author.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/spooky/author.rb b/lib/spooky/author.rb index 685d8e4..b7a33f0 100644 --- a/lib/spooky/author.rb +++ b/lib/spooky/author.rb @@ -2,9 +2,6 @@ module Spooky class Author < Base - include ActiveModel::Model - include ActiveModel::Attributes - attribute :slug, :string attribute :id, :string attribute :name, :string From 9ce0d1368c1b52ca1bcf5b9d06ac9231f989ac18 Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sun, 20 Apr 2025 09:26:43 -0400 Subject: [PATCH 20/21] rubocop suggestions --- lib/spooky/base.rb | 4 ++-- lib/spooky/post.rb | 2 +- lib/spooky/tag.rb | 4 ++-- spec/client_spec.rb | 16 ++++++++-------- spooky.gemspec | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/spooky/base.rb b/lib/spooky/base.rb index d4084a8..2da71d8 100644 --- a/lib/spooky/base.rb +++ b/lib/spooky/base.rb @@ -8,11 +8,11 @@ class Base def attribute_writer_missing(name, value) # Typically means they added something to the API. # Most likely a new social network. - puts "Unexpected attribute for #{self.class.name}: #{name}" + puts "Unexpected attribute for #{self.class.name}: \"#{name}\": \"#{value}\"" end def persisted? - self.id.present? + id.present? end end end diff --git a/lib/spooky/post.rb b/lib/spooky/post.rb index e40b858..1a61171 100644 --- a/lib/spooky/post.rb +++ b/lib/spooky/post.rb @@ -58,7 +58,7 @@ def authors=(attributes) def tags @tags ||= [] end - + def tags=(attributes) @tags = attributes.map do |tag_attrs| Tag.new(tag_attrs) diff --git a/lib/spooky/tag.rb b/lib/spooky/tag.rb index fbc51b7..69fe202 100644 --- a/lib/spooky/tag.rb +++ b/lib/spooky/tag.rb @@ -27,11 +27,11 @@ class Tag < Base def count=(attributes) @count = Count.new(attributes) end - + def to_s name.to_s end - + def to_param slug.to_s end diff --git a/spec/client_spec.rb b/spec/client_spec.rb index b5a0658..b476f47 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -21,31 +21,31 @@ it "gets all posts with tags and authors" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", - { params: { key: "abc123", include: %w[tags authors] } }) + { params: { key: "abc123", include: %w[tags authors] } }) client.posts(include: %w[tags authors]) end it "gets a post by id" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/99/", - { params: { key: "abc123" } }) + { params: { key: "abc123" } }) client.post_by(id: 99) end it "gets all posts with tags and authors" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", - { params: { key: "abc123", include: %w[tags authors] } }) + { params: { key: "abc123", include: %w[tags authors] } }) client.posts(include: %w[tags authors]) end it "gets featured posts with hash filter" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", - { params: { key: "abc123", filter: "featured:true" } }) + { params: { key: "abc123", filter: "featured:true" } }) client.posts(filter: { featured: true }) end it "applies a string filter to the request" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/posts/", - { params: { key: "abc123", filter: "title:Welcome" } }) + { params: { key: "abc123", filter: "title:Welcome" } }) client.posts(filter: "title:Welcome") end end @@ -58,7 +58,7 @@ it "gets a page by id" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/pages/1/", - { params: { key: "abc123" } }) + { params: { key: "abc123" } }) client.page_by(id: 1) end end @@ -71,7 +71,7 @@ it "gets a author by id" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/authors/1/", - { params: { key: "abc123" } }) + { params: { key: "abc123" } }) client.author_by(id: 1) end end @@ -84,7 +84,7 @@ it "gets a tag by id" do expect(HTTP).to receive(:get).with("https://spec.test/ghost/api/content/tags/5f397fe0a185384852d1f144/", - { params: { key: "abc123" } }) + { params: { key: "abc123" } }) client.tag_by(id: "5f397fe0a185384852d1f144") end end diff --git a/spooky.gemspec b/spooky.gemspec index c33e414..b1949cc 100644 --- a/spooky.gemspec +++ b/spooky.gemspec @@ -21,8 +21,8 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "activesupport", ">= 6.0" spec.add_dependency "activemodel", ">= 6.0" + spec.add_dependency "activesupport", ">= 6.0" spec.add_dependency "http", ">= 4.0" spec.add_development_dependency "bundler", "~> 2.0" From fe9d0344db1440a552acbaf8bfbaaa02f5145647 Mon Sep 17 00:00:00 2001 From: Daniel Morrison Date: Sun, 20 Apr 2025 09:36:09 -0400 Subject: [PATCH 21/21] Add pages spec --- spec/fixtures.rb | 186 +++++++++++++++++++++++++++++++++++++++++++++ spec/pages_spec.rb | 61 +++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 spec/pages_spec.rb diff --git a/spec/fixtures.rb b/spec/fixtures.rb index d22dfc6..7b7675a 100644 --- a/spec/fixtures.rb +++ b/spec/fixtures.rb @@ -339,6 +339,192 @@ POST ), + pages: JSON.parse( + <<~PAGES + { + "pages": [ + { + "id": "67ed823ddf3cfc00077dbd50", + "uuid": "b84cd61f-2b0d-4851-9bd7-463d55205ba6", + "title": "About this site", + "slug": "about", + "html": "

About this site…

", + "comment_id": "67ed823ddf3cfc00077dbd50", + "feature_image": null, + "featured": false, + "visibility": "public", + "created_at": "2025-04-02T14:30:21.000-04:00", + "updated_at": "2025-04-03T10:16:44.000-04:00", + "published_at": "2025-04-02T14:30:21.000-04:00", + "custom_excerpt": null, + "codeinjection_head": null, + "codeinjection_foot": null, + "custom_template": null, + "canonical_url": null, + "show_title_and_feature_image": true, + "url": "https://blog.spec.test/about/", + "excerpt": "This is our about page", + "reading_time": 1, + "access": true, + "comments": false, + "og_image": null, + "og_title": null, + "og_description": null, + "twitter_image": null, + "twitter_title": null, + "twitter_description": null, + "meta_title": null, + "meta_description": null, + "frontmatter": null, + "feature_image_alt": null, + "feature_image_caption": null + } + ], + "meta": { + "pagination": { + "page": 1, + "limit": 15, + "pages": 1, + "total": 1, + "next": null, + "prev": null + } + } + } + PAGES + ), + + pages_with_authors_and_tags: JSON.parse( + <<~PAGES + { + "pages": [ + { + "id": "67ed823ddf3cfc00077dbd50", + "uuid": "b84cd61f-2b0d-4851-9bd7-463d55205ba6", + "title": "About this site", + "slug": "about", + "html": "

About this site…

", + "comment_id": "67ed823ddf3cfc00077dbd50", + "feature_image": null, + "featured": false, + "visibility": "public", + "created_at": "2025-04-02T14:30:21.000-04:00", + "updated_at": "2025-04-03T10:16:44.000-04:00", + "published_at": "2025-04-02T14:30:21.000-04:00", + "custom_excerpt": null, + "codeinjection_head": null, + "codeinjection_foot": null, + "custom_template": null, + "canonical_url": null, + "show_title_and_feature_image": true, + "authors": [ + { + "id": "1", + "name": "Georges Gabereau", + "slug": "georges", + "profile_image": null, + "cover_image": null, + "bio": null, + "website": null, + "location": null, + "facebook": null, + "twitter": null, + "meta_title": null, + "meta_description": null, + "url": "https://blog.spec.test/author/georges/" + } + ], + "primary_author": { + "id": "1", + "name": "Georges Gabereau", + "slug": "georges", + "profile_image": null, + "cover_image": null, + "bio": null, + "website": null, + "location": null, + "facebook": null, + "twitter": null, + "meta_title": null, + "meta_description": null, + "url": "https://blog.spec.test/author/georges/" + }, + "tags": [ + { + "id": "5f397fe0a185384852d1f144", + "name": "Getting Started", + "slug": "getting-started", + "description": null, + "feature_image": null, + "visibility": "public", + "og_image": null, + "og_title": null, + "og_description": null, + "twitter_image": null, + "twitter_title": null, + "twitter_description": null, + "meta_title": null, + "meta_description": null, + "codeinjection_head": null, + "codeinjection_foot": null, + "canonical_url": null, + "accent_color": null, + "url": "https://blog.spec.test/tag/getting-started/" + } + ], + "primary_tag": { + "id": "5f397fe0a185384852d1f144", + "name": "Getting Started", + "slug": "getting-started", + "description": null, + "feature_image": null, + "visibility": "public", + "og_image": null, + "og_title": null, + "og_description": null, + "twitter_image": null, + "twitter_title": null, + "twitter_description": null, + "meta_title": null, + "meta_description": null, + "codeinjection_head": null, + "codeinjection_foot": null, + "canonical_url": null, + "accent_color": null, + "url": "https://blog.spec.test/tag/getting-started/" + }, + "url": "https://blog.spec.test/about/", + "excerpt": "This is our about page", + "reading_time": 1, + "access": true, + "comments": false, + "og_image": null, + "og_title": null, + "og_description": null, + "twitter_image": null, + "twitter_title": null, + "twitter_description": null, + "meta_title": null, + "meta_description": null, + "frontmatter": null, + "feature_image_alt": null, + "feature_image_caption": null + } + ], + "meta": { + "pagination": { + "page": 1, + "limit": 15, + "pages": 1, + "total": 1, + "next": null, + "prev": null + } + } + } + PAGES + ), + authors: JSON.parse( <<~AUTHORS { diff --git a/spec/pages_spec.rb b/spec/pages_spec.rb new file mode 100644 index 0000000..b6fc697 --- /dev/null +++ b/spec/pages_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "spec_helper" +require "fixtures" + +describe Spooky::Page do + let(:client) { Spooky::Client.new } + + it "returns an array of Pages" do + response = double("A parsed response", parse: FIXTURES[:pages]) + allow(HTTP).to receive(:get).and_return(response) + + pages, = client.pages + + expect(pages.all? { |p| p.is_a?(Spooky::Page) }).to be(true) + end + + it "returns the pagination information with a browse request" do + response = double("A parsed response", parse: FIXTURES[:pages]) + allow(HTTP).to receive(:get).and_return(response) + + _, pagination = client.pages + + expect(pagination).to be_present + expect(pagination.keys).to include("page", "limit", "pages", "total") + end + + it "converts nested tags to Spooky::Tag" do + response = double("A parsed response", parse: FIXTURES[:pages_with_authors_and_tags]) + allow(HTTP).to receive(:get).and_return(response) + + pages, = client.pages(include: %w[tags]) + page = pages.first + + expect(page.primary_tag.is_a?(Spooky::Tag)).to be(true) + expect(page.tags.all? { |p| p.is_a?(Spooky::Tag) }).to be(true) + end + + it "converts nested authors to Spooky::Author" do + response = double("A parsed response", parse: FIXTURES[:pages_with_authors_and_tags]) + allow(HTTP).to receive(:get).and_return(response) + + pages, = client.pages(include: %w[authors]) + page = pages.first + + expect(page.primary_author.is_a?(Spooky::Author)).to be(true) + expect(page.authors.all? { |p| p.is_a?(Spooky::Author) }).to be(true) + end + + it "returns a requested page by ID" do + response = double("A parsed response", parse: FIXTURES[:pages]) + allow(HTTP).to receive(:get).and_return(response) + + # This doesn't actually query by the ID. We are testing attribute creation here. + page = client.page_by(id: "5f397fe1a185384852d1f1a5") + + expect(page.id).to eq("67ed823ddf3cfc00077dbd50") + expect(page.title).to eq("About this site") + expect(page.slug).to eq("about") + end +end