Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

[Feature] ActiveResource - Associations through reflections #230

Closed
wants to merge 4 commits into from
@SweeD

Matthias Folz and I've added associations (has_many and has_one) via reflection classes to ActiveResources.

Here is the link to the googlegroups discussion:
http://groups.google.com/group/rubyonrails-core/browse_thread/thread/dd8d83955a7cf9e8/1f95a896887cd9a9#1f95a896887cd9a9

And the lighthouse ticket:
https://rails.lighthouseapp.com/projects/8994/tickets/6473-activeresource-adding-associations-through-reflections#ticket-6473-1

I hope, it's not impolite to open a pull request without an answer on the lighthouse ticket form a core member.
If it's so: I'm sorry... ;)

Greetz
Markus Schwed

@SweeD

This ticket would be the begin of extending active resource with more restful features.
(like association link resolving as seen in Atomic-feeds)

@miloops

What about belongs_to? If we add has_many and has_one is intuitive to have belongs_to associations, right?

@SweeD

Hi miloops,

in the first step, we've added just has_one and has_many associations to configurate the :class_name (more options will follow), which should be used for the initialization of associated objects.
(more detailed description in the linked Lighthouse ticket)

So, before it get merged, we should add a belongs_to association, that adds an accessor for the parent, which it belongs to?

Should we also add some autoresolving for a belongs_to association, if the request response (for i.e. Comment.find(2) ) only includes the 'post_id' instead of all attributes (nested) for the 'post' object. ?

@SweeD SweeD closed this
@SweeD SweeD reopened this
@SweeD

Sorry... i closed the pull request by mistake... :)

@smartinez87

Please remember to include the guides updated with this change, to make it a complete request.

@SweeD

I just rebased to the latest rails master, so the least added commits aren't new commits.

activeresource/lib/active_resource/associations.rb
((16 lines not shown))
+ # be used for resolving the association class.
+ #
+ # ==== Example for [:class_name] - option
+ # GET /posts/123.xml delivers following response body:
+ # <post>
+ # <title>ActiveResource now have associations</title>
+ # <content> ... </content>
+ # <comments>
+ # <comment> ... </comment>
+ # <comment> ... </comment>
+ # </comments>
+ # </post>
+ # ====
+ #
+ # <tt>has_many :comments, :class_name => 'myblog/comment'</tt>
+ # Would resolve this comments into the <tt>Myblog::Comment</tt> class.
@gogolok
gogolok added a note

this -> those

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activeresource/lib/active_resource/reflection.rb
@@ -0,0 +1,68 @@
+require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/module/deprecation'
+
+module ActiveResource
+ # = Active Resource reflection
+ #
+ # Associations in ActiveResource would be used to resolve nested attributes
+ # in a response with correct classes.
+ # Now they could be specify over Associations with the options :class_name
@gogolok
gogolok added a note

specify -> specified

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@guilleiguaran

@SweeD: Do you finish belongs_to?

@core members: is there any chance you can review this for inclusion in Rails 3.2?

@SweeD

Sorry, I was on vacation.

We will finish it within the next two weeks.

Greetz
SweeD

@sikachu
Collaborator

You have two more days to complete your patch :P (haha, just j/k)

I'll come back and check again after you've finished it. If you can finish belongs_to, and squash some of the tiny commits that would be great. :)

@sikachu sikachu was assigned
@guilleiguaran

Just a friendly reminder, I keep an eye on this too, hahaha :D

@SweeD

@sikachu @guilleiguaran
Woohoo... haven't forgot.... We'll finish it.
Little bit stressful these days... :D

Please don't stone me... xD

@guilleiguaran

Looks like this is finished, I see belongs_to added on the code, I will start to test this now :)

@SweeD: great work!!!

@SweeD

Sorry, it's not finished, already.
But almost... ;)

Have to write the resolving of the parent for a belongs_to association and squash the tiny commits... :)
Will finish these days!

...b/active_resource/associations/builder/association.rb
@@ -0,0 +1,32 @@
+module ActiveResource::Associations::Builder
+ class Association #:nodoc:
+
+ # providing a Class-Variable, which will have a differend store of subclasses
@dasch
dasch added a note

"differend" -> "different"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dasch dasch commented on the diff
activeresource/lib/active_resource/associations.rb
@@ -0,0 +1,104 @@
+module ActiveResource::Associations
+
+ module Builder
+ autoload :Association, 'active_resource/associations/builder/association'
+ autoload :HasMany, 'active_resource/associations/builder/has_many'
+ autoload :HasOne, 'active_resource/associations/builder/has_one'
+ autoload :BelongsTo, 'active_resource/associations/builder/belongs_to'
+ end
@dasch
dasch added a note

Try to use autoload_at here, i.e.

module Builder
  autoload_at 'active_resource/associations/builder' do
    autoload :Association
    autoload :HasMany
    autoload :HasOne
    autoload :BelongsTo
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jeremy
Owner

Good idea and nice implementation. Targeting Rails 3.2.

@RobertLowe

+1 this is awesome.

@sikachu
Collaborator

@SweeD what's the progress of this patch? I want to make sure this can be merged into Rails 3.2 ;)

@guilleiguaran

+1, this should be in master soon :)

@SweeD

Oha, haven't got time for a while.

Will finish it this month, sorry about that. ;)

@SweeD

@sikachu we "just" have to finish the belongs_to association and then, we're done.

@jeremy
Owner
Have to write the resolving of the parent for a belongs_to association and squash the tiny commits...

Rails 3.2 release candidate is coming within days, so this is going to miss the boat without a final push!

@sikachu
Collaborator

true!

@SweeD

We will work on it today and hopefully finish it tomorrow.

@johnmetta

Noting that the last comment is from Dec 15th with a hopeful finish by Dec 16th. Just wanted to bump this to see if the status has changed.

@SweeD

Sorry
..but sadly, the status haven't changed since 15.12.

But we will finish it 100% this weekend.

@SweeD

@sikachu ....and finally: Done! :D

Really, really sorry for the delay.
Looking forward to feedback... :)

@jeremy
Owner

@SweeD great!

@SweeD

@jeremy Thanks!

Something to change/cleanup or so?

@SweeD

Closed for new pull request in new rails/activeresource repo.

rails/activeresource#1

Oh... it's the first one... xD

@SweeD SweeD closed this
@SweeD SweeD referenced this pull request in rails/activeresource
Merged

[Feature] ActiveResource - Associations through reflections #1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 11, 2012
  1. @SweeD

    Added ActiveResource - Association-Builder and Reflections

    SweeD authored SweeD committed
  2. @mafolz @SweeD
  3. @mafolz @SweeD

    ActiveResource::Reflection now recognized by find_or_create_resource_…

    mafolz authored SweeD committed
    …for_collection
Commits on Feb 12, 2012
  1. @SweeD

    Added belongs_to association to active_resource

    SweeD authored SweeD committed
This page is out of date. Refresh to see the latest.
View
112 activeresource/lib/active_resource/associations.rb
@@ -0,0 +1,112 @@
+module ActiveResource::Associations
+
+ module Builder
+ autoload :Association, 'active_resource/associations/builder/association'
+ autoload :HasMany, 'active_resource/associations/builder/has_many'
+ autoload :HasOne, 'active_resource/associations/builder/has_one'
+ autoload :BelongsTo, 'active_resource/associations/builder/belongs_to'
+ end
@dasch
dasch added a note

Try to use autoload_at here, i.e.

module Builder
  autoload_at 'active_resource/associations/builder' do
    autoload :Association
    autoload :HasMany
    autoload :HasOne
    autoload :BelongsTo
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+
+
+ # Specifies a one-to-many association.
+ #
+ # === Options
+ # [:class_name]
+ # Specify the class name of the association. This class name would
+ # be used for resolving the association class.
+ #
+ # ==== Example for [:class_name] - option
+ # GET /posts/123.xml delivers following response body:
+ # <post>
+ # <title>ActiveResource now have associations</title>
+ # <content> ... </content>
+ # <comments>
+ # <comment> ... </comment>
+ # <comment> ... </comment>
+ # </comments>
+ # </post>
+ # ====
+ #
+ # <tt>has_many :comments, :class_name => 'myblog/comment'</tt>
+ # Would resolve those comments into the <tt>Myblog::Comment</tt> class.
+ def has_many(name, options = {})
+ Builder::HasMany.build(self, name, options)
+ end
+
+ # Specifies a one-to-one association.
+ #
+ # === Options
+ # [:class_name]
+ # Specify the class name of the association. This class name would
+ # be used for resolving the association class.
+ #
+ # ==== Example for [:class_name] - option
+ # GET /posts/123.xml delivers following response body:
+ # <post>
+ # <title>ActiveResource now have associations</title>
+ # <content> ... </content>
+ # <author>
+ # <name>caffeinatedBoys</name>
+ # </author>
+ # </post>
+ # ====
+ #
+ # <tt>has_one :author, :class_name => 'myblog/author'</tt>
+ # Would resolve this author into the <tt>Myblog::Author</tt> class.
+ def has_one(name, options = {})
+ Builder::HasOne.build(self, name, options)
+ end
+
+ # Specifies a one-to-one association with another class. This class should only be used
+ # if this class contains the foreign key.
+ #
+ # Methods will be added for retrieval and query for a single associated object, for which
+ # this object holds an id:
+ #
+ # [association(force_reload = false)]
+ # Returns the associated object. +nil+ is returned if the foreign key is +nil+.
+ # Throws a ActiveResource::ResourceNotFound exception if the foreign key is not +nil+
+ # and the resource is not found.
+ #
+ # (+association+ is replaced with the symbol passed as the first argument, so
+ # <tt>belongs_to :post</tt> would add among others <tt>post.nil?</tt>.
+ #
+ # === Example
+ #
+ # A Comment class declaress <tt>belongs_to :post</tt>, which will add:
+ # * <tt>Comment#post</tt> (similar to <tt>Post.find(post_id)</tt>)
+ # The declaration can also include an options hash to specialize the behavior of the association.
+ #
+ # === Options
+ # [:class_name]
+ # Specify the class name for the association. Use it only if that name canÄt be inferred from association name.
+ # So <tt>belongs_to :post</tt> will by default be linked to the Post class, but if the real class name is Article,
+ # you'll have to specify it with whis option.
+ # [:foreign_key]
+ # Specify the foreign key used for the association. By default this is guessed to be the name
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :post</tt>
+ # association will use "post_id" as the default <tt>:foreign_key</tt>. Similarly,
+ # <tt>belongs_to :article, :class_name => "Post"</tt> will use a foreign key
+ # of "article_id".
+ #
+ # Option examples:
+ # <tt>belongs_to :customer, :class_name => 'User'</tt>
+ # Creates a belongs_to association called customer which is represented through the <tt>User</tt> class.
+ #
+ # <tt>belongs_to :customer, :foreign_key => 'user_id'</tt>
+ # Creates a belongs_to association called customer which would be resolved by the foreign_key <tt>user_id</tt> instead of <tt>customer_id</tt>
+ #
+ def belongs_to(name, options={})
+ Builder::BelongsTo.build(self, name, options)
+ end
+
+ # Defines the belongs_to association finder method
+ def defines_belongs_to_finder_method(method_name, association_model, finder_key)
+ define_method(method_name) do
+ ivar_name = :"@#{method_name}"
+ instance_variable_defined?(ivar_name) ? instance_variable_get(ivar_name) : instance_variable_set(ivar_name, association_model.find(send(finder_key)))
+ end
+ end
+
+end
View
32 activeresource/lib/active_resource/associations/builder/association.rb
@@ -0,0 +1,32 @@
+module ActiveResource::Associations::Builder
+ class Association #:nodoc:
+
+ # providing a Class-Variable, which will have a different store of subclasses
+ class_attribute :valid_options
+ self.valid_options = [:class_name]
+
+ # would identify subclasses of association
+ class_attribute :macro
+
+ attr_reader :model, :name, :options, :klass
+
+ def self.build(model, name, options)
+ new(model, name, options).build
+ end
+
+ def initialize(model, name, options)
+ @model, @name, @options = model, name, options
+ end
+
+ def build
+ validate_options
+ reflection = model.create_reflection(self.class.macro, name, options)
+ end
+
+ private
+
+ def validate_options
+ options.assert_valid_keys(self.class.valid_options)
+ end
+ end
+end
View
14 activeresource/lib/active_resource/associations/builder/belongs_to.rb
@@ -0,0 +1,14 @@
+module ActiveResource::Associations::Builder
+ class BelongsTo < Association
+ self.valid_options += [:foreign_key]
+
+ self.macro = :belongs_to
+
+ def build
+ validate_options
+ reflection = model.create_reflection(self.class.macro, name, options)
+ model.defines_belongs_to_finder_method(reflection.name, reflection.klass, reflection.foreign_key)
+ return reflection
+ end
+ end
+end
View
5 activeresource/lib/active_resource/associations/builder/has_many.rb
@@ -0,0 +1,5 @@
+module ActiveResource::Associations::Builder
+ class HasMany < Association
+ self.macro = :has_many
+ end
+end
View
5 activeresource/lib/active_resource/associations/builder/has_one.rb
@@ -0,0 +1,5 @@
+module ActiveResource::Associations::Builder
+ class HasOne < Association
+ self.macro = :has_one
+ end
+end
View
8 activeresource/lib/active_resource/base.rb
@@ -17,6 +17,8 @@
require 'active_resource/formats'
require 'active_resource/schema'
require 'active_resource/log_subscriber'
+require 'active_resource/associations'
+require 'active_resource/reflection'
module ActiveResource
# ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
@@ -1403,6 +1405,7 @@ def response_code_allows_body?(c)
# Tries to find a resource for a given collection name; if it fails, then the resource is created
def find_or_create_resource_for_collection(name)
+ return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s))
end
@@ -1423,6 +1426,8 @@ def find_or_create_resource_in_modules(resource_name, module_names)
# Tries to find a resource for a given name; if it fails, then the resource is created
def find_or_create_resource_for(name)
+ return reflections[name.to_sym].klass if reflections.key?(name.to_sym)
+
resource_name = name.to_s.camelize
const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
@@ -1475,9 +1480,12 @@ def method_missing(method_symbol, *arguments) #:nodoc:
class Base
extend ActiveModel::Naming
+ extend ActiveResource::Associations
+
include CustomMethods, Observing, Validations
include ActiveModel::Conversion
include ActiveModel::Serializers::JSON
include ActiveModel::Serializers::Xml
+ include ActiveResource::Reflection
end
end
View
77 activeresource/lib/active_resource/reflection.rb
@@ -0,0 +1,77 @@
+require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/module/deprecation'
+
+module ActiveResource
+ # = Active Resource reflection
+ #
+ # Associations in ActiveResource would be used to resolve nested attributes
+ # in a response with correct classes.
+ # Now they could be specified over Associations with the options :class_name
+ module Reflection # :nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :reflections
+ self.reflections = {}
+ end
+
+ module ClassMethods
+ def create_reflection(macro, name, options)
+ reflection = AssociationReflection.new(macro, name, options)
+ self.reflections = self.reflections.merge(name => reflection)
+ reflection
+ end
+ end
+
+
+ class AssociationReflection
+
+ def initialize(macro, name, options)
+ @macro, @name, @options = macro, name, options
+ end
+
+ # Returns the name of the macro.
+ #
+ # <tt>has_many :clients</tt> returns <tt>:clients</tt>
+ attr_reader :name
+
+ # Returns the macro type.
+ #
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
+ attr_reader :macro
+
+ # Returns the hash of options used for the macro.
+ #
+ # <tt>has_many :clients</tt> returns +{}+
+ attr_reader :options
+
+ # Returns the class for the macro.
+ #
+ # <tt>has_many :clients</tt> returns the Client class
+ def klass
+ @klass ||= class_name.constantize
+ end
+
+ # Returns the class name for the macro.
+ #
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
+ def class_name
+ @class_name ||= derive_class_name
+ end
+
+ # Returns the foreign_key for the macro.
+ def foreign_key
+ @foreign_key ||= self.options[:foreign_key] || "#{self.name.to_s.downcase}_id"
+ end
+
+ private
+ def derive_class_name
+ return (options[:class_name] ? options[:class_name].to_s : name.to_s).classify
+ end
+
+ def derive_foreign_key
+ return options[:foreign_key] ? options[:foreign_key].to_s : "#{name.to_s.downcase}_id"
+ end
+ end
+ end
+end
View
4 activeresource/test/abstract_unit.rb
@@ -74,7 +74,9 @@ def setup_response
:children => []
}
]
- }]
+ }],
+ :enemies => [{:name => 'Joker'}],
+ :mother => {:name => 'Ingeborg'}
}
}.to_json
# - resource with yaml array of strings; for ARs using serialize :bar, Array
View
71 activeresource/test/cases/association_test.rb
@@ -0,0 +1,71 @@
+require 'abstract_unit'
+
+require 'fixtures/person'
+require 'fixtures/beast'
+require 'fixtures/customer'
+
+
+class AssociationTest < Test::Unit::TestCase
+ def setup
+ @klass = ActiveResource::Associations::Builder::Association
+ end
+
+
+ def test_validations_for_instance
+ object = @klass.new(Person, :customers, {})
+ assert_equal({}, object.send(:validate_options))
+ end
+
+ def test_instance_build
+ object = @klass.new(Person, :customers, {})
+ assert_kind_of ActiveResource::Reflection::AssociationReflection, object.build
+ end
+
+ def test_valid_options
+ assert @klass.build(Person, :customers, {:class_name => 'Client'})
+
+ assert_raise ArgumentError do
+ @klass.build(Person, :customers, {:soo_invalid => true})
+ end
+ end
+
+ def test_association_class_build
+ assert_kind_of ActiveResource::Reflection::AssociationReflection, @klass.build(Person, :customers, {})
+ end
+
+ def test_has_many
+ External::Person.has_many(:people)
+ assert_equal 1, External::Person.reflections.select{|name, reflection| reflection.macro.eql?(:has_many)}.count
+ end
+
+ def test_has_one
+ External::Person.has_one(:customer)
+ assert_equal 1, External::Person.reflections.select{|name, reflection| reflection.macro.eql?(:has_one)}.count
+ end
+
+ def test_has_many
+ External::Person.send(:has_many, :people)
+ assert_equal 1, External::Person.reflections.select{|name, reflection| reflection.macro.eql?(:has_many)}.count
+ end
+
+ def test_has_one
+ External::Person.send(:has_one, :customer)
+ assert_equal 1, External::Person.reflections.select{|name, reflection| reflection.macro.eql?(:has_one)}.count
+ end
+
+ def test_belongs_to
+ External::Person.belongs_to(:Customer)
+ assert_equal 1, External::Person.reflections.select{|name, reflection| reflection.macro.eql?(:belongs_to)}.count
+ end
+
+ def test_defines_belongs_to_finder_method_with_instance_variable_cache
+ Person.defines_belongs_to_finder_method(:customer, Customer, 'customer_id')
+
+ person = Person.new
+ assert !person.instance_variable_defined?(:@customer)
+ person.stubs(:customer_id).returns(2)
+ Customer.expects(:find).with(2).once()
+ 2.times{person.customer}
+ assert person.instance_variable_defined?(:@customer)
+ end
+end
View
35 activeresource/test/cases/associations/builder/belongs_to_test.rb
@@ -0,0 +1,35 @@
+
+require 'abstract_unit'
+
+require 'fixtures/person'
+require 'fixtures/beast'
+require 'fixtures/customer'
+
+
+class ActiveResource::Associations::Builder::BelongsToTest < Test::Unit::TestCase
+ def setup
+ @klass = ActiveResource::Associations::Builder::BelongsTo
+ end
+
+
+ def test_validations_for_instance
+ object = @klass.new(Person, :customer, {})
+ assert_equal({}, object.send(:validate_options))
+ end
+
+ def test_instance_build
+ object = @klass.new(Person, :customer, {})
+ Person.expects(:defines_belongs_to_finder_method).with(:customer, Customer, 'customer_id')
+ assert_kind_of ActiveResource::Reflection::AssociationReflection, object.build
+ end
+
+
+ def test_valid_options
+ assert @klass.build(Person, :customer, {:class_name => 'Person'})
+ assert @klass.build(Person, :customer, {:foreign_key => 'person_id'})
+
+ assert_raise ArgumentError do
+ @klass.build(Person, :customer, {:soo_invalid => true})
+ end
+ end
+end
View
14 activeresource/test/cases/base_test.rb
@@ -1077,6 +1077,20 @@ def test_parse_deep_nested_resources
end
end
+ def test_parse_resource_with_given_has_one_resources
+ Customer.send(:has_one, :mother, :class_name => "external/person")
+ luis = Customer.find(1)
+ assert_kind_of External::Person, luis.mother
+ end
+
+ def test_parse_resources_with_given_has_many_resources
+ Customer.send(:has_many, :enemies, :class_name => "external/person")
+ luis = Customer.find(1)
+ luis.enemies.each do |enemy|
+ assert_kind_of External::Person, enemy
+ end
+ end
+
def test_load_yaml_array
assert_nothing_raised do
Person.format = :xml
View
59 activeresource/test/cases/reflection_test.rb
@@ -0,0 +1,59 @@
+require 'abstract_unit'
+
+require 'fixtures/person'
+require 'fixtures/customer'
+
+
+
+class ReflectionTest < Test::Unit::TestCase
+
+ def test_correct_class_attributes
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {})
+ assert_equal :people, object.name
+ assert_equal :test, object.macro
+ assert_equal({}, object.options)
+ end
+
+ def test_correct_class_name_matching_without_class_name
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {})
+ assert_equal Person, object.klass
+ end
+
+ def test_correct_class_name_matching_as_string
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {:class_name => 'Person'})
+ assert_equal Person, object.klass
+ end
+
+ def test_correct_class_name_matching_as_symbol
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {:class_name => :person})
+ assert_equal Person, object.klass
+ end
+
+ def test_correct_class_name_matching_as_class
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {:class_name => Person})
+ assert_equal Person, object.klass
+ end
+
+ def test_correct_class_name_matching_as_string_with_namespace
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {:class_name => 'external/person'})
+ assert_equal External::Person, object.klass
+ end
+
+ def test_foreign_key_method_with_no_foreign_key_option
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :person, {})
+ assert_equal 'person_id', object.foreign_key
+ end
+
+ def test_foreign_key_method_with_with_foreign_key_option
+ object = ActiveResource::Reflection::AssociationReflection.new(:test, :people, {:foreign_key => 'client_id'})
+ assert_equal 'client_id', object.foreign_key
+ end
+
+ def test_creation_of_reflection
+ object = Person.create_reflection(:test, :people, {})
+ assert_equal ActiveResource::Reflection::AssociationReflection, object.class
+ assert Person.reflections[:people].present?
+ assert_equal Person, Person.reflections[:people].klass
+ end
+
+end
View
7 activeresource/test/fixtures/person.rb
@@ -1,3 +1,10 @@
class Person < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000"
end
+
+module External
+ class Person < ActiveResource::Base
+ self.site = "http://atq.caffeine.intoxication.it"
+ end
+end
+
Something went wrong with that request. Please try again.