contentful_model 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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