module Scrivito # @api public module Associations # Searches for CMS objects containing one or more attributes linking to this CMS object. # # So the search returns the CMS objects in which at least one +html+, # +link+, +linklist+, +reference+ or +referencelist+ attribute links to this CMS object. # # Attributes inside {Scrivito::BasicWidget Widgets} are considered, too. # # @return [Scrivito::ObjSearchEnumerator] # @api public def backlinks Obj.where(:*, :links_to, self) end # @api public module ClassMethods # Specifies a one-to-one association with another class. # # This method defines a foreign attribute for this class. # If the other class contains the foreign attribute, then you should use # {Scrivito::Associations::ClassMethods#has_many has_many} instead. # # This method defines a +reference+ attribute with the given +name+. # Only instances of +obj_class+ are valid values for this attribute. # # @example # class Author < Obj # attribute :first_name, :string # attribute :last_name, :string # end # # class Book < Obj # attribute :title, :string # belongs_to :author # end # # s_hawking = Author.create(first_name: 'Stephen', last_name: 'Hawking') # history_of_time = Book.create(title: 'A Brief History of Time', author: s_hawking) # # history_of_time.author.first_name # # => 'Stephen' # # @example with an optional +obj_class+ # class Book < Obj # belongs_to 'publisher', obj_class: Person # end # # @param [Symbol, String] name name of the attribute. See # {Scrivito::AttributeContent::ClassMethods#attribute attribute} for details. # @param [Class] obj_class (optional) specifies (for use in the UI) the valid class # _of_ _the_ _value_ in this attribute. If no +obj_class+ is given, an +obj_class+ is # derived from the given +name+. For example, from the +name+ +'author'+ # the +obj_class+ +Author+ is derived. # @return [void] # @api public def belongs_to(name, obj_class: name) klass = to_single_class(obj_class) attribute(name, :reference, only: klass) end # Specifies a one-to-many association. # # This method defines a foreign attribute for this class. # If the other class contains the foreign attribute, then you should use # {Scrivito::Associations::ClassMethods#has_many has_many} instead. # # This method defines a +referencelist+ attribute with the given +name+. # Only instances of +obj_class+ are valid values for this attribute. # # @example # class Author < Obj # attribute :first_name, :string # attribute :last_name, :string # end # # class Book < Obj # attribute :title, :string # belongs_to_many :authors # end # # s_hawking = Author.create(first_name: 'Stephen' last_name: 'Hawking') # l_hawking = Author.create(first_name: 'Lucy', last_name: 'Hawking') # secret_key_to_the_universe = Book.create( # title: "George's Secret Key to the Universe", authors: [s_hawking, l_hawking]) # # secret_key_to_the_universe.authors.map(&:first_name) # # => ['Stephen', 'Lucy'] # # @example with an optional +obj_class+ # class Book < Obj # belongs_to_many 'reviewers', obj_class: Person # end # # @param [Symbol, String] name name of the attribute. See # {Scrivito::AttributeContent::ClassMethods#attribute attribute} for details. # @param [Class] obj_class (optional) specifies (for use in the UI) the valid class # _of_ _the_ _value_ in this attribute. If no +obj_class+ is given, an +obj_class+ # is derived from the given +name+. For example, from the +name+ +'authors'+ the +obj_class+ # +Author+ is derived. # @return [void] # @api public def belongs_to_many(name, obj_class: name) klass = to_single_class(obj_class) attribute(name, :referencelist, only: klass) end # Specifies a one-to-many or many-to-many association with another class. # # This method should only be used if the other class contains the foreign attribute. # If the current class contains the foreign attribute, then you should use # {Scrivito::Associations::ClassMethods#belongs_to belongs_to} or # {Scrivito::Associations::ClassMethods#belongs_to_many belongs_to_many} instead. # # This methods generates a new instance method ++. # This newly generated method searches for all +obj_class+ instances, # where the +foreign_attribute+ references this instance. # This newly generated method returns an # {https://ruby-doc.org/core/Enumerable.html +Enumerable+} of {Scrivito::BasicObj BasicObjs} # instances. # # @example # class Book < Obj # attribute :title, :string # belongs_to :author # end # # class Author < Obj # attribute :first_name, :string # attribute :last_name, :string # has_many :books # end # # s_hawking = Author.create(first_name: 'Stephen', last_name: 'Hawking') # history_of_time = Book.create(title: 'A Brief History of Time', author: s_hawking) # grand_design = Book.create(title: 'The Grand Design', author: s_hawking) # # s_hawking.books.to_a # # => [, ] # # s_hawking.books.map(&:title) # # => ['A Brief History of Time', 'The Grand Design'] # # @example with an optional +obj_class+ and +foreign_attribute+ # class Book < Obj # belongs_to_many 'reviewers', obj_class: Person # end # # class Person < Obj # has_many :reviewed_books, foreign_attribute: 'reviewers', obj_class: Book # end # # a_person = Person.all.first # # => # # a_person.reviewed_books.to_a # # => [, ] # # @param [Symbol, String] name name of the search method. # @param [Class] obj_class (optional) limit search to instances of +obj_class+. # If no +obj_class+ is given, the +obj_class+ is derived from the given +name+. # For example, from the +name+ +'books'+ the +obj_class+ +Book+ is derived. # @param [Symbol, String] foreign_attribute (optional) name of the foreign attribute of the # other class (e.g. as defined by # {Scrivito::Associations::ClassMethods#belongs_to belongs_to} or # {Scrivito::Associations::ClassMethods#belongs_to_many belongs_to_many}). # If no +foreign_attribute+ is given, the foreign attribute is derived from this class. # For example, from the class +Author+ the +foreign_attribute+ +[:author, :authors]+ # is derived. # @return [void] # @api public def has_many(name, obj_class: name, foreign_attribute: nil) klass = to_single_class(obj_class) attributes = to_attribute_names(foreign_attribute) define_method(name) do klass.where(attributes, :refers_to, self) end end private def to_single_class(value) return value if value.is_a?(Class) value.to_s.camelize.singularize.constantize end def to_attribute_names(foreign_attribute) return [foreign_attribute] if foreign_attribute single_attribute = to_s.demodulize.underscore [single_attribute, single_attribute.pluralize] end end end end