noun-project-api 0.2.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +5 -5
  2. data/Rakefile +9 -7
  3. data/lib/noun_project_api.rb +38 -0
  4. data/lib/noun_project_api/base_item.rb +30 -0
  5. data/lib/noun_project_api/collection.rb +36 -0
  6. data/lib/noun_project_api/collection_retriever.rb +14 -0
  7. data/lib/{noun-project-api → noun_project_api}/connection.rb +3 -1
  8. data/lib/noun_project_api/errors.rb +14 -0
  9. data/lib/noun_project_api/icon.rb +41 -0
  10. data/lib/noun_project_api/icon_retriever.rb +14 -0
  11. data/lib/noun_project_api/icons_retriever.rb +66 -0
  12. data/lib/noun_project_api/reporter.rb +22 -0
  13. data/lib/noun_project_api/retriever.rb +18 -0
  14. data/spec/lib/noun_project_api/base_item_spec.rb +13 -0
  15. data/spec/lib/noun_project_api/collection_retriever_spec.rb +87 -0
  16. data/spec/lib/noun_project_api/collection_spec.rb +66 -0
  17. data/spec/lib/{noun-project-api → noun_project_api}/icon_retriever_spec.rb +20 -18
  18. data/spec/lib/{noun-project-api → noun_project_api}/icon_spec.rb +21 -19
  19. data/spec/lib/{noun-project-api → noun_project_api}/icons_retriever_spec.rb +27 -25
  20. data/spec/lib/{noun-project-api → noun_project_api}/reporter_spec.rb +24 -24
  21. data/spec/lib/noun_project_api/retriever_spec.rb +22 -0
  22. data/spec/lib/{nount_project_api_spec.rb → noun_project_api_spec.rb} +5 -3
  23. data/spec/spec_helper.rb +3 -4
  24. data/spec/support/fakes.rb +93 -10
  25. metadata +90 -45
  26. data/lib/noun-project-api.rb +0 -30
  27. data/lib/noun-project-api/icon.rb +0 -52
  28. data/lib/noun-project-api/icon_retriever.rb +0 -20
  29. data/lib/noun-project-api/icons_retriever.rb +0 -51
  30. data/lib/noun-project-api/reporter.rb +0 -20
  31. data/lib/noun-project-api/retriever.rb +0 -6
  32. data/spec/lib/noun-project-api/retriever_spec.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 11f21fc8fa8332fb270247aba04d4ac778c5cff9
4
- data.tar.gz: 9cf63e9099b5f9b1b777aa0191cf80fd516b3b90
2
+ SHA256:
3
+ metadata.gz: 7599a88ab282cc57200c3907a08b057d466d91af307c992c42fa59ca15bdbe55
4
+ data.tar.gz: cee01ba5d89c09fb74b62c7ef2b5d630437632ce6c43406b5d65b2fd417c544b
5
5
  SHA512:
6
- metadata.gz: 1d1e4725b6dd873d4f4f66cc9d0636123196cbd02a4b090d3fdf548df54d2ecc40b16eecac4050c2e6bbb6e9b190f84f4706cfbf4d04c5391b85bab302d715de
7
- data.tar.gz: 5ade878aa4c75eb4cd0e0875be509fd381381ce090789784307f8e5f6e74631a23b9097f05a837300200b4547aeeabef5ce9148e7f43952c6e2dd6098c9e0ea0
6
+ metadata.gz: bf81da78826aee4244949577e0b103b1dc16435e813115576b81701d72a31a4569e78cc85ec5cd7e921666e12392f18aff9fd02dd9f3ec1d2a006e21cb7c341b
7
+ data.tar.gz: de4ab0a45285f4979c697beff40d1f02ff497a9d4037487172000cede3ca46f20a47c2a51b8d635ddc9a478d3b0a6cdba07dba8b3417311b6cbc75df73491140
data/Rakefile CHANGED
@@ -1,14 +1,16 @@
1
- require 'bundler'
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
2
4
  Bundler::GemHelper.install_tasks
3
5
 
4
- $:.unshift 'lib'
6
+ $LOAD_PATH.unshift "lib"
5
7
 
6
- desc 'Default: run unit tests.'
7
- task :default => [:spec]
8
+ desc "Default: run unit tests."
9
+ task default: [:spec]
8
10
 
9
- require 'rspec/core/rake_task'
11
+ require "rspec/core/rake_task"
10
12
 
11
- desc 'Run specs'
13
+ desc "Run specs"
12
14
  RSpec::Core::RakeTask.new do |t|
13
- t.pattern = './spec/**/*_spec.rb'
15
+ t.pattern = "./spec/**/*_spec.rb"
14
16
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "oauth"
4
+ require "active_support"
5
+ require "json"
6
+ require "noun_project_api/errors"
7
+ require "noun_project_api/connection"
8
+ require "noun_project_api/icon_retriever"
9
+ require "noun_project_api/reporter"
10
+ require "noun_project_api/icons_retriever"
11
+ require "noun_project_api/icon"
12
+ require "noun_project_api/collection_retriever"
13
+ require "noun_project_api/collection"
14
+
15
+ # Top level name space for the entire Gem.
16
+ module NounProjectApi
17
+ API_BASE = "http://api.thenounproject.com"
18
+
19
+ def self.configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def self.configure
24
+ self.configuration ||= Configuration.new
25
+ yield(configuration) if block_given?
26
+ end
27
+
28
+ # Main configuration class.
29
+ class Configuration
30
+ attr_accessor :public_domain, :cache, :cache_ttl
31
+
32
+ def initialize
33
+ @public_domain = false
34
+ @cache = ActiveSupport::Cache::NullStore.new
35
+ @cache_ttl = 604800 # Week in seconds
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ # A basis to Items returned by the noun project.
5
+ class BaseItem
6
+ attr_accessor :original_hash
7
+ ITEM_NAME = nil
8
+
9
+ def initialize(origin)
10
+ raise NotImplementedError, "Must use a subclass" if self.class::ITEM_NAME.nil?
11
+
12
+ origin = JSON.parse(origin, symbolize_names: true) if origin.is_a? String
13
+ if origin.key? self.class::ITEM_NAME
14
+ origin = origin.delete(
15
+ self.class::ITEM_NAME
16
+ )
17
+ end
18
+
19
+ @original_hash = origin
20
+ end
21
+
22
+ def id
23
+ original_hash[:id].to_i
24
+ end
25
+
26
+ def to_json(*_args)
27
+ JSON.dump(to_hash)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/base_item"
4
+
5
+ module NounProjectApi
6
+ # A single Collection as an abstracted ruby object.
7
+ class Collection < BaseItem
8
+ ITEM_NAME = :collection
9
+
10
+ def author_id
11
+ original_hash[:author_id].to_i
12
+ end
13
+
14
+ def author_name
15
+ original_hash[:author][:name]
16
+ end
17
+
18
+ def icon_count
19
+ original_hash[:icon_count].to_i
20
+ end
21
+
22
+ def published?
23
+ original_hash[:is_published].to_i == 1
24
+ end
25
+
26
+ def to_hash
27
+ {
28
+ id: id,
29
+ author_id: author_id,
30
+ author_name: author_name,
31
+ icon_count: icon_count,
32
+ published: published?
33
+ }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/collection"
4
+ require "noun_project_api/retriever"
5
+
6
+ module NounProjectApi
7
+ # Retrieve a collection.
8
+ class CollectionRetriever < Retriever
9
+ API_PATH = "/collection/"
10
+ ITEM_CLASS = Collection
11
+
12
+ alias find_by_slug find
13
+ end
14
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NounProjectApi
2
4
  # Basic connection methods and setup.
3
5
  module Connection
@@ -6,7 +8,7 @@ module NounProjectApi
6
8
  def initialize(token, secret)
7
9
  @token = token
8
10
  @secret = secret
9
- fail(ArgumentError, 'Missing token or secret') unless @token && @secret
11
+ raise ArgumentError, "Missing token or secret" unless @token && @secret
10
12
 
11
13
  @access_token = OAuth::AccessToken.new(OAuth::Consumer.new(token, secret))
12
14
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ class ServiceError < StandardError
5
+ attr_reader :status, :body
6
+
7
+ def initialize(status, body)
8
+ @status = status
9
+ @body = body
10
+
11
+ super("Noun Project API Error")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/base_item"
4
+
5
+ module NounProjectApi
6
+ # A single Icon as an abstracted ruby object.
7
+ class Icon < BaseItem
8
+ PREVIEW_SIZE_200 = 200
9
+ PREVIEW_SIZE_42 = 42
10
+ PREVIEW_SIZE_84 = 84
11
+
12
+ PUBLIC_DOMAIN_LICENSE = "public-domain"
13
+
14
+ ITEM_NAME = :icon
15
+
16
+ def public_domain?
17
+ original_hash[:license_description] == PUBLIC_DOMAIN_LICENSE
18
+ end
19
+
20
+ def svg_url
21
+ original_hash[:icon_url]
22
+ end
23
+
24
+ def preview_url(size = PREVIEW_SIZE_200)
25
+ if size == PREVIEW_SIZE_200
26
+ original_hash[:preview_url]
27
+ else
28
+ original_hash[:"preview_url_#{size}"]
29
+ end
30
+ end
31
+
32
+ def to_hash
33
+ {
34
+ id: id,
35
+ preview_url_200: preview_url(PREVIEW_SIZE_200),
36
+ preview_url_84: preview_url(PREVIEW_SIZE_84),
37
+ preview_url_42: preview_url(PREVIEW_SIZE_42)
38
+ }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/icon"
4
+ require "noun_project_api/retriever"
5
+
6
+ module NounProjectApi
7
+ # Retrieve an icon.
8
+ class IconRetriever < Retriever
9
+ API_PATH = "/icon/"
10
+ ITEM_CLASS = Icon
11
+
12
+ alias find_by_slug find
13
+ end
14
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/retriever"
4
+
5
+ module NounProjectApi
6
+ # Retrieve icons.
7
+ class IconsRetriever < Retriever
8
+ API_PATH = "/icons/"
9
+
10
+ # Finds multiple icons based on the term
11
+ # * term - search term
12
+ # * limit - limit the amount of results
13
+ # * offset - offset the results
14
+ # * page - page number
15
+ def find(term, limit = nil, offset = nil, page = nil)
16
+ cache_key = Digest::MD5.hexdigest("#{term}+#{limit}+#{offset}+#{page}")
17
+ cache_ttl = NounProjectApi.configuration.cache_ttl
18
+
19
+ NounProjectApi.configuration.cache.fetch(cache_key, expires_in: cache_ttl) do
20
+ raise ArgumentError, "Missing search term" unless term
21
+
22
+ search = OAuth::Helper.escape(term)
23
+ search += "?limit_to_public_domain=#{NounProjectApi.configuration.public_domain ? 1 : 0}"
24
+
25
+ args = {
26
+ "limit" => limit,
27
+ "offset" => offset,
28
+ "page" => page
29
+ }.reject { |_, v| v.nil? }
30
+ args.each { |k, v| search += "&#{k}=#{v}" } unless args.empty?
31
+
32
+ result = access_token.get("#{API_BASE}#{API_PATH}#{search}")
33
+ raise ServiceError.new(result.code, result.body) unless ["200", "404"].include? result.code
34
+
35
+ if result.code == "200"
36
+ JSON.parse(result.body, symbolize_names: true)[:icons].map { |icon| Icon.new(icon) }
37
+ else
38
+ []
39
+ end
40
+ end
41
+ end
42
+
43
+ # List recent uploads
44
+ # * limit - limit the amount of results
45
+ # * offset - offset the results
46
+ # * page - page number
47
+ def recent_uploads(limit = nil, offset = nil, page = nil)
48
+ args = {
49
+ "limit" => limit,
50
+ "offset" => offset,
51
+ "page" => page
52
+ }.reject { |_, v| v.nil? }
53
+ if !args.empty?
54
+ search = "?"
55
+ args.each { |k, v| search += "#{k}=#{v}&" }
56
+ else
57
+ search = ""
58
+ end
59
+
60
+ result = access_token.get("#{API_BASE}#{API_PATH}recent_uploads#{search}")
61
+ raise ServiceError.new(result.code, result.body) unless result.code == "200"
62
+
63
+ JSON.parse(result.body, symbolize_names: true)[:recent_uploads].map { |icon| Icon.new(icon) }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ # Main class to hold reporting actions back to the Noun Project.
5
+ class Reporter
6
+ include Connection
7
+
8
+ API_PATH = "/notify/publish"
9
+
10
+ def report_used(ids)
11
+ ids = [ids] if ids.is_a?(String) || ids.is_a?(Integer)
12
+ raise ArgumentError, "Missing ids" if ids.nil? || ids.empty?
13
+
14
+ result = access_token.post(
15
+ "#{API_BASE}#{API_PATH}",
16
+ { icons: ids.join(",") }.to_json,
17
+ "Accept" => "application/json", "Content-Type" => "application/json"
18
+ )
19
+ result.code == "200"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ # A base class for different retriever classes.
5
+ class Retriever
6
+ include Connection
7
+
8
+ # Find an item based on it's id.
9
+ def find(id)
10
+ raise ArgumentError, "Missing id/slug" unless id
11
+
12
+ result = access_token.get("#{API_BASE}#{self.class::API_PATH}#{id}")
13
+ raise ServiceError.new(result.code, result.body) unless result.code == "200"
14
+
15
+ self.class::ITEM_CLASS.new(result.body)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "ostruct"
5
+
6
+ RSpec.describe NounProjectApi::BaseItem do
7
+ it "raises an error on direct initialization" do
8
+ data = JSON.parse(Fakes::Results::ICON_VALID)
9
+ expect {
10
+ NounProjectApi::BaseItem.new(JSON.dump(data["icon"]))
11
+ }.to raise_error(NotImplementedError)
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "ostruct"
5
+
6
+ RSpec.describe NounProjectApi::CollectionRetriever do
7
+ before :each do
8
+ @collection = NounProjectApi::CollectionRetriever.new(Faker::Internet.password(min_length: 16), Faker::Internet.password(min_length: 16))
9
+ @valid_hash = JSON.parse(Fakes::Results::COLLECTION_VALID, symbolize_names: true)
10
+ @valid_response = OpenStruct.new(
11
+ body: Fakes::Results::COLLECTION_VALID,
12
+ code: "200"
13
+ )
14
+
15
+ @missing_response = OpenStruct.new(
16
+ code: "404"
17
+ )
18
+ end
19
+
20
+ context "id" do
21
+ it "raises an error when no id is provided" do
22
+ expect { @collection.find(nil) }.to raise_error(ArgumentError)
23
+ end
24
+
25
+ it "returns a proper result with a correct id" do
26
+ id = 1
27
+ expect(@collection.access_token).to receive(
28
+ :get
29
+ ).with(
30
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{id}"
31
+ ).and_return(
32
+ @valid_response
33
+ )
34
+
35
+ result = @collection.find(id)
36
+ expect(result).to be_a(NounProjectApi::Collection)
37
+ expect(result.original_hash).to eq(@valid_hash[:collection])
38
+ end
39
+
40
+ it "raises an error with a missing id" do
41
+ id = 1
42
+ expect(@collection.access_token).to receive(
43
+ :get
44
+ ).with(
45
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{id}"
46
+ ).and_return(
47
+ @missing_response
48
+ )
49
+
50
+ expect { @collection.find(id) }.to raise_error(NounProjectApi::ServiceError)
51
+ end
52
+ end
53
+
54
+ context "slug" do
55
+ it "raises an error when no slug is provided" do
56
+ expect { @collection.find_by_slug(nil) }.to raise_error(ArgumentError)
57
+ end
58
+
59
+ it "returns a proper result with a correct slug" do
60
+ slug = "existing_slug"
61
+ expect(@collection.access_token).to receive(
62
+ :get
63
+ ).with(
64
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{slug}"
65
+ ).and_return(
66
+ @valid_response
67
+ )
68
+
69
+ result = @collection.find(slug)
70
+ expect(result).to be_a(NounProjectApi::Collection)
71
+ expect(result.original_hash).to eq(@valid_hash[:collection])
72
+ end
73
+
74
+ it "raises an error with a missing slug" do
75
+ slug = "missing_slug"
76
+ expect(@collection.access_token).to receive(
77
+ :get
78
+ ).with(
79
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{slug}"
80
+ ).and_return(
81
+ @missing_response
82
+ )
83
+
84
+ expect { @collection.find_by_slug(slug) }.to raise_error(NounProjectApi::ServiceError)
85
+ end
86
+ end
87
+ end