contentful_model 0.2.0 → 1.0.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 (84) hide show
  1. checksums.yaml +5 -5
  2. data/lib/contentful_model.rb +17 -13
  3. data/lib/contentful_model/asset.rb +30 -0
  4. data/lib/contentful_model/asset_dimension_query.rb +70 -0
  5. data/lib/contentful_model/asset_dimensions.rb +54 -0
  6. data/lib/contentful_model/associations/associations.rb +10 -4
  7. data/lib/contentful_model/associations/belongs_to.rb +9 -4
  8. data/lib/contentful_model/associations/belongs_to_many.rb +22 -45
  9. data/lib/contentful_model/associations/has_many.rb +15 -13
  10. data/lib/contentful_model/associations/has_many_nested.rb +39 -37
  11. data/lib/contentful_model/associations/has_one.rb +22 -14
  12. data/lib/contentful_model/base.rb +115 -74
  13. data/lib/contentful_model/client.rb +14 -3
  14. data/lib/contentful_model/errors.rb +0 -1
  15. data/lib/contentful_model/manageable.rb +49 -21
  16. data/lib/contentful_model/management.rb +1 -0
  17. data/lib/contentful_model/migrations/content_type.rb +24 -24
  18. data/lib/contentful_model/migrations/content_type_factory.rb +9 -6
  19. data/lib/contentful_model/migrations/migration.rb +9 -4
  20. data/lib/contentful_model/queries.rb +60 -9
  21. data/lib/contentful_model/query.rb +148 -5
  22. data/lib/contentful_model/validations/lambda_validation.rb +17 -0
  23. data/lib/contentful_model/validations/validates_presence_of.rb +4 -1
  24. data/lib/contentful_model/validations/validations.rb +56 -8
  25. data/lib/contentful_model/version.rb +1 -1
  26. data/spec/asset_spec.rb +141 -0
  27. data/spec/associations/belongs_to_many_spec.rb +38 -0
  28. data/spec/associations/belongs_to_spec.rb +22 -0
  29. data/spec/associations/has_many_nested_spec.rb +321 -0
  30. data/spec/associations/has_many_spec.rb +33 -0
  31. data/spec/associations/has_one_spec.rb +32 -0
  32. data/spec/base_spec.rb +199 -0
  33. data/spec/chainable_queries_spec.rb +199 -0
  34. data/spec/client_spec.rb +11 -0
  35. data/spec/contentful_model_spec.rb +25 -0
  36. data/spec/fixtures/vcr_cassettes/asset/all.yml +161 -0
  37. data/spec/fixtures/vcr_cassettes/asset/find.yml +118 -0
  38. data/spec/fixtures/vcr_cassettes/association/belongs_to_many.yml +439 -0
  39. data/spec/fixtures/vcr_cassettes/association/has_many.yml +161 -0
  40. data/spec/fixtures/vcr_cassettes/association/has_one.yml +161 -0
  41. data/spec/fixtures/vcr_cassettes/association/nested_with_root_root.yml +240 -0
  42. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_higher_include.yml +123 -0
  43. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_parent.yml +609 -0
  44. data/spec/fixtures/vcr_cassettes/association/nested_without_root_childless.yml +688 -0
  45. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle.yml +319 -0
  46. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_higher_include.yml +161 -0
  47. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_parent.yml +489 -0
  48. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless.yml +331 -0
  49. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless_higher_include.yml +82 -0
  50. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle.yml +161 -0
  51. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle_higher_include.yml +82 -0
  52. data/spec/fixtures/vcr_cassettes/base/content_type.yml +185 -0
  53. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty.yml +202 -0
  54. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty_with_value.yml +124 -0
  55. data/spec/fixtures/vcr_cassettes/client.yml +98 -0
  56. data/spec/fixtures/vcr_cassettes/dog.yml +161 -0
  57. data/spec/fixtures/vcr_cassettes/human.yml +199 -0
  58. data/spec/fixtures/vcr_cassettes/management/client.yml +1362 -0
  59. data/spec/fixtures/vcr_cassettes/management/nyancat.yml +1449 -0
  60. data/spec/fixtures/vcr_cassettes/management/nyancat_2.yml +1449 -0
  61. data/spec/fixtures/vcr_cassettes/management/nyancat_publish.yml +481 -0
  62. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_fail.yml +558 -0
  63. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_save.yml +620 -0
  64. data/spec/fixtures/vcr_cassettes/management/nyancat_save.yml +479 -0
  65. data/spec/fixtures/vcr_cassettes/management/nyancat_save_2.yml +479 -0
  66. data/spec/fixtures/vcr_cassettes/nyancat.yml +196 -0
  67. data/spec/fixtures/vcr_cassettes/playground/nyancat.yml +177 -0
  68. data/spec/fixtures/vcr_cassettes/query/each_entry.yml +319 -0
  69. data/spec/fixtures/vcr_cassettes/query/each_page.yml +319 -0
  70. data/spec/fixtures/vcr_cassettes/query/empty.yml +180 -0
  71. data/spec/fixtures/vcr_cassettes/query/load.yml +197 -0
  72. data/spec/fixtures/vcr_cassettes/query/manual_pagination.yml +161 -0
  73. data/spec/fixtures/vcr_cassettes/query/nyancat_invalid_elements.yml +247 -0
  74. data/spec/manageable_spec.rb +186 -0
  75. data/spec/management_spec.rb +17 -0
  76. data/spec/migrations/content_type_factory_spec.rb +41 -0
  77. data/spec/migrations/content_type_spec.rb +176 -0
  78. data/spec/migrations/migration_spec.rb +75 -0
  79. data/spec/queries_spec.rb +85 -0
  80. data/spec/query_spec.rb +126 -0
  81. data/spec/spec_helper.rb +58 -0
  82. data/spec/validations/validations_spec.rb +182 -0
  83. metadata +213 -19
  84. data/lib/contentful_model/chainable_queries.rb +0 -104
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: cb158dcb23427ac8201185b1201a237ba7dfa0a5
4
- data.tar.gz: 7f4e3ac0d68637daf9bb60662c85476ea6e8916a
2
+ SHA256:
3
+ metadata.gz: 7627c9489ee307874ad678255845eda746e36b0ff2b23cbfb3a352959a06d2da
4
+ data.tar.gz: 46cfc93b06df11246596c6629191ab66bcad7023de9b56b1a55ba60ab06c5a9d
5
5
  SHA512:
6
- metadata.gz: 6a69c4da7a8cb02d67af7e4685bcff46f363d4fe63137d30a6a554f7d6c464314ae5980ea3bd2955d61145ee1120b5235675059e5e25c11b2ea986648b983017
7
- data.tar.gz: a6b37e8649735c82a5ee6f7439291ceb45b21597e3fc8c0dfe0edf8b483b963af10c07f38a5f2272c876bf9cd4ec2ef1e8419c544379964ccaa2f847077a9ed4
6
+ metadata.gz: c9761a4b040e5cb433c636218b733f07d4efbcdec9227acc629594eeaf887824d0e8306dc384eaa0e1a9af27f1dd5ea32f55795b1c55bdb4293c66fa43f59167
7
+ data.tar.gz: 111d113b97a8eb7c8d3e345196029e3cd62c5e6a3872c13568be7fcb9f5a69870d686e89b7f644ad8917fcc4bb3c7cb9e1ec408e624c5b399ce25ae8a912e0eb
@@ -1,28 +1,32 @@
1
- require 'require_all'
1
+ require 'active_support/all'
2
2
  require 'contentful/management'
3
3
  require 'contentful'
4
- require_rel '.'
5
4
 
6
- require "active_support/all"
5
+ require_relative 'contentful_model/base'
6
+ require_relative 'contentful_model/migrations/migration'
7
+ require_relative 'contentful_model/version'
7
8
 
9
+ # ContentfulModel is an ActiveModel-like interface for the Contentful SDK.
8
10
  module ContentfulModel
9
11
  class << self
10
- #accessor to set the preview API for use instead of the production one
12
+ # accessor to set the preview API for use instead of the production one
11
13
  attr_accessor :use_preview_api
12
14
 
13
- #access the configuration class as ContentfulModel.configuration
15
+ # access the configuration class as ContentfulModel.configuration
14
16
  attr_accessor :configuration
15
17
 
16
- #block for configuration.
18
+ # block for configuration.
17
19
  def configure
18
20
  self.configuration ||= Configuration.new
19
21
  yield(configuration)
20
22
  end
21
23
  end
22
24
 
25
+ # Configuration store for ContentfulModel
23
26
  class Configuration
24
27
  attr_accessor :access_token,
25
28
  :preview_access_token,
29
+ :environment,
26
30
  :space,
27
31
  :entry_mapping,
28
32
  :management_token,
@@ -30,22 +34,22 @@ module ContentfulModel
30
34
 
31
35
  def initialize
32
36
  @entry_mapping ||= {}
37
+ @environment = 'master'
33
38
  end
34
39
 
35
- #Rather than listing out all the possible attributes as setters, we have a catchall
36
- #called 'options' which takes a hash and generates instance vars
37
- #@param options [Hash]
40
+ # Rather than listing out all the possible attributes as setters, we have a catchall
41
+ # called 'options' which takes a hash and generates instance vars
42
+ # @param options [Hash]
38
43
  def options=(options)
39
- options.each do |k,v|
40
- instance_variable_set(:"@#{k}",v)
44
+ options.each do |k, v|
45
+ instance_variable_set(:"@#{k}", v)
41
46
  end
42
47
  end
43
48
 
44
-
45
49
  # Return the Configuration object as a hash, with symbols as keys.
46
50
  # @return [Hash]
47
51
  def to_hash
48
- Hash[instance_variables.map { |name| [name.to_s.gsub("@","").to_sym, instance_variable_get(name)] } ]
52
+ Hash[instance_variables.map { |name| [name.to_s.delete('@').to_sym, instance_variable_get(name)] }]
49
53
  end
50
54
  end
51
55
  end
@@ -0,0 +1,30 @@
1
+ require_relative 'asset_dimensions'
2
+
3
+ module ContentfulModel
4
+ # Module for providing querying capabilities to Asset
5
+ class Asset < Contentful::Asset
6
+ include ContentfulModel::AssetDimensions
7
+
8
+ class << self
9
+ def all(query = {})
10
+ client.assets(query)
11
+ end
12
+
13
+ def find(id)
14
+ client.asset(id)
15
+ end
16
+
17
+ def client
18
+ if ContentfulModel.use_preview_api
19
+ @preview_client ||= ContentfulModel::Client.new(
20
+ ContentfulModel.configuration.to_hash
21
+ )
22
+ else
23
+ @client ||= ContentfulModel::Client.new(
24
+ ContentfulModel.configuration.to_hash
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,70 @@
1
+ module ContentfulModel
2
+ # Module for extending Asset with Image API capabilities
3
+ class AssetDimensionQuery
4
+ attr_reader :asset, :query
5
+
6
+ def initialize(asset)
7
+ @asset = asset
8
+ @query = {}
9
+ end
10
+
11
+ def resize(width = nil, height = nil)
12
+ self.width(width) unless width.nil?
13
+ self.height(height) unless height.nil?
14
+ self
15
+ end
16
+
17
+ def width(w)
18
+ query[:w] = w
19
+ self
20
+ end
21
+
22
+ def height(h)
23
+ query[:h] = h
24
+ self
25
+ end
26
+
27
+ def format(fm)
28
+ query[:fm] = fm
29
+ self
30
+ end
31
+
32
+ def jpeg_quality(q)
33
+ query[:fm] = 'jpg'
34
+ query[:q] = q
35
+ self
36
+ end
37
+
38
+ def png_8bit
39
+ query[:fm] = 'png'
40
+ query[:fl] = 'png8'
41
+ self
42
+ end
43
+
44
+ def resize_behavior(fit)
45
+ query[:fit] = fit
46
+ self
47
+ end
48
+
49
+ def thumbnail_focused_on(f)
50
+ query[:fit] = 'thumb'
51
+ query[:f] = f
52
+ self
53
+ end
54
+
55
+ def rounded_corners(r)
56
+ query[:r] = r
57
+ self
58
+ end
59
+
60
+ def padded_background_color(bg)
61
+ query[:fit] = 'pad'
62
+ query[:bg] = bg
63
+ self
64
+ end
65
+
66
+ def load
67
+ asset.url(query)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,54 @@
1
+ require_relative 'asset_dimension_query'
2
+
3
+ module ContentfulModel
4
+ # Module to extend Asset with Image API capabilities
5
+ module AssetDimensions
6
+ def query
7
+ ContentfulModel::AssetDimensionQuery.new(self)
8
+ end
9
+
10
+ def resize(width = nil, height = nil)
11
+ query.resize(width, height)
12
+ end
13
+
14
+ def width(w)
15
+ query.width(w)
16
+ end
17
+
18
+ def height(h)
19
+ query.height(h)
20
+ end
21
+
22
+ def format(fm)
23
+ query.format(fm)
24
+ end
25
+
26
+ def jpeg_quality(q)
27
+ query.jpeg_quality(q)
28
+ end
29
+
30
+ def png_8bit
31
+ query.png_8bit
32
+ end
33
+
34
+ def resize_behavior(fit)
35
+ query.resize_behavior(fit)
36
+ end
37
+
38
+ def thumbnail_focused_on(f)
39
+ query.thumbnail_focused_on(f)
40
+ end
41
+
42
+ def rounded_corners(r)
43
+ query.rounded_corners(r)
44
+ end
45
+
46
+ def padded_background_color(bg)
47
+ query.padded_background_color(bg)
48
+ end
49
+
50
+ def load
51
+ query.load
52
+ end
53
+ end
54
+ end
@@ -1,7 +1,13 @@
1
- # A module to map relationships, a little like ActiveRecord::Relation
2
- # This is necessary because Contentful::Link classes are not 2-way, so you can't
3
- # get the parent from a child.
1
+ require_relative 'belongs_to'
2
+ require_relative 'belongs_to_many'
3
+ require_relative 'has_many'
4
+ require_relative 'has_one'
5
+ require_relative 'has_many_nested'
6
+
4
7
  module ContentfulModel
8
+ # A module to map relationships, a little like ActiveRecord::Relation
9
+ # This is necessary because Contentful::Link classes are not 2-way, so you can't
10
+ # get the parent from a child.
5
11
  module Associations
6
12
  def self.included(base)
7
13
  base.include HasMany
@@ -11,4 +17,4 @@ module ContentfulModel
11
17
  base.include HasManyNested
12
18
  end
13
19
  end
14
- end
20
+ end
@@ -1,17 +1,22 @@
1
1
  module ContentfulModel
2
2
  module Associations
3
+ # Defines Belongs To association
3
4
  module BelongsTo
4
5
  def self.included(base)
5
6
  base.extend ClassMethods
6
7
  end
7
8
 
9
+ # Class method
8
10
  module ClassMethods
9
11
  # belongs_to is called on the child, and creates methods for mapping to the parent
10
- # @param association_name [Symbol] the singular name of the parent
11
- def belongs_to(association_name, opts = {})
12
- raise NotImplementedError, "Contentful doesn't have a singular belongs_to relationship. Use belongs_to_many instead."
12
+ # @param _association_name [Symbol] the singular name of the parent
13
+ def belongs_to(_association_name, _opts = {})
14
+ fail(
15
+ NotImplementedError,
16
+ "Contentful doesn't have a singular belongs_to relationship. Use belongs_to_many instead."
17
+ )
13
18
  end
14
19
  end
15
20
  end
16
21
  end
17
- end
22
+ end
@@ -1,9 +1,12 @@
1
1
  module ContentfulModel
2
2
  module Associations
3
+ # Defines Belongs To Many association
3
4
  module BelongsToMany
4
5
  def self.included(base)
5
6
  base.extend ClassMethods
6
7
  end
8
+
9
+ # Class method
7
10
  module ClassMethods
8
11
  # belongs_to_many implements a has_many association from the opposite end, and allows a call to the association name
9
12
  # to return all instances for which this object is a child.
@@ -12,17 +15,17 @@ module ContentfulModel
12
15
  # is in the children. It requires one API call.
13
16
 
14
17
  # class Bar
15
- # belongs_to_many :foos, class_name: Foo, inverse_of: :special_bars
18
+ # belongs_to_many :foos, class_name: Foo
16
19
  # end
17
20
 
18
- # In this example, children on the parent are accessed through an association called special_bars.
21
+ # In this example, children on the parent are accessed through an association called special_bars.
19
22
 
20
23
  # @param association_names [Symbol] plural name of the class we need to search through, to find this class
21
- # @param options [true, Hash] options
24
+ # @param opts [true, Hash] options
22
25
  def belongs_to_many(association_names, opts = {})
23
26
  default_options = {
24
27
  class_name: association_names.to_s.singularize.classify,
25
- inverse_of: self.to_s.underscore.to_sym
28
+ page_size: 100
26
29
  }
27
30
  options = default_options.merge(opts)
28
31
 
@@ -39,55 +42,29 @@ module ContentfulModel
39
42
  end
40
43
 
41
44
  define_method "#{association_names.to_s.singularize}=" do |parent|
42
- instance_variable_set(:"@#{association_names.to_s.singularize}",parent)
43
- instance_variable_set(:"@loaded_with_parent", true)
44
- return self
45
+ instance_variable_set(:"@#{association_names.to_s.singularize}", parent)
46
+ instance_variable_set(:@loaded_with_parent, true)
47
+ self
45
48
  end
46
49
 
47
- define_method "loaded_with_parent?" do
48
- instance_variable_get(:"@loaded_with_parent") ? true : false
50
+ define_method :loaded_with_parent? do
51
+ instance_variable_get(:@loaded_with_parent) ? true : false
49
52
  end
50
53
 
51
- # Set up the association name (plural)
52
- if self.respond_to?(association_names)
53
- self.send(association_names)
54
- else
55
- define_method "#{association_names}" do
56
- parents = instance_variable_get(:"@#{association_names}")
57
- if parents.nil?
58
- #get the parent class objects as an array
59
- parent_objects = options[:class_name].constantize.send(:all).send(:load)
60
- #iterate through parent objects and see if any of the children include the same ID as the method
61
- parents = parent_objects.select do |parent_object|
62
- #check to see if the parent object responds to the plural or singular.
63
- if parent_object.respond_to?(:"#{options[:inverse_of].to_s.pluralize}")
64
- collection_of_children_on_parent = parent_object.send(:"#{options[:inverse_of].to_s.pluralize}")
65
- #get the collection of children from the parent. This *might* be nil if the parent doesn't have
66
- # any children, in which case, just skip over this parent item and move on to the next.
67
- if collection_of_children_on_parent.nil?
68
- next
69
- else
70
- collection_of_children_on_parent.collect(&:id).include?(id)
71
- end
72
- else
73
- #if it doesn't respond to the plural, assume singular
74
- child_on_parent = parent_object.send(:"#{options[:inverse_of]}")
75
- # Do the same skipping routine on nil.
76
- if child_on_parent.nil?
77
- next
78
- else
79
- child_on_parent.send(:id) == id
80
- end
81
-
82
- end
83
- end
84
- instance_variable_set(:"@#{association_names}",parents)
54
+ define_method association_names do
55
+ parents = instance_variable_get(:"@#{association_names}")
56
+ if parents.nil?
57
+ parents = []
58
+ options[:class_name].constantize.send(:each_entry, options[:page_size], 'sys.updatedAt', links_to_entry: id) do |parent|
59
+ parents << parent
85
60
  end
86
- parents
61
+
62
+ instance_variable_set(:"@#{association_names}", parents)
87
63
  end
64
+ parents
88
65
  end
89
66
  end
90
67
  end
91
68
  end
92
69
  end
93
- end
70
+ end
@@ -1,10 +1,12 @@
1
1
  module ContentfulModel
2
2
  module Associations
3
+ # Defines Has Many association
3
4
  module HasMany
4
5
  def self.included(base)
5
6
  base.extend ClassMethods
6
7
  end
7
8
 
9
+ # Class method
8
10
  module ClassMethods
9
11
  # has_many is called on the parent model
10
12
  #
@@ -16,34 +18,34 @@ module ContentfulModel
16
18
  # from the name of the model we're calling. If you specify a class_name, the method called on the parent will
17
19
  # be that. e.g. .somethings in this example
18
20
  # @param association_names [Symbol] the name of the child model, as a plural symbol
21
+ # rubocop:disable Style/PredicateName
19
22
  def has_many(association_names, options = {})
20
23
  default_options = {
21
24
  class_name: association_names.to_s.singularize.classify,
22
- inverse_of: self.to_s.underscore.to_s
25
+ inverse_of: to_s.underscore.to_s
23
26
  }
24
27
  options = default_options.merge(options)
28
+
29
+ include_discovered(options[:class_name])
30
+
25
31
  define_method association_names do
26
32
  begin
27
33
  # Start by calling the association name as a method on the superclass.
28
34
  # this will end up in ContentfulModel::Base#method_missing and return the value from Contentful.
29
35
  # We set the singular of the association name on each object in the collection to allow easy
30
36
  # reverse recursion without another API call (i.e. finding the Foo which called .bars())
31
- super().collect do |child|
32
- child.send(:"#{options[:inverse_of]}=",self)
33
- end
34
- rescue ContentfulModel::AttributeNotFoundError
35
- # If AttributeNotFoundError is raised, that means that the association name isn't available on the object.
36
- # We try to call the class name (pluralize) instead, or give up and return an empty collection
37
- if options[:class_name].pluralize.underscore.to_sym != association_names
38
- self.send(options[:class_name].pluralize.underscore.to_sym)
39
- else
40
- #return an empty collection if the class name was the same as the association name and there's no attribute on the object.
41
- []
37
+ super().each do |child|
38
+ child.send(:"#{options[:inverse_of]}=", self) if child.respond_to?(:"#{options[:inverse_of]}=")
42
39
  end
40
+ rescue NoMethodError
41
+ possible_field_name = options[:class_name].pluralize.underscore.to_sym
42
+ return send(possible_field_name) if possible_field_name != association_names && respond_to?(possible_field_name)
43
+ []
43
44
  end
44
45
  end
45
46
  end
47
+ # rubocop:enable Style/PredicateName
46
48
  end
47
49
  end
48
50
  end
49
- end
51
+ end