SearchCop erweitert Ihre ActiveRecord-Modelle, um Volltextsuchmaschinen-ähnliche Abfragen über einfache Abfragezeichenfolgen und Hash-basierte Abfragen zu unterstützen. Angenommen, Sie haben ein Book
mit verschiedenen Attributen wie title
, author
, stock
, price
und available
. Mit SearchCop können Sie Folgendes ausführen:
Book . search ( "Joanne Rowling Harry Potter" )
Book . search ( "author: Rowling title:'Harry Potter'" )
Book . search ( "price > 10 AND price < 20 -stock:0 (Potter OR Rowling)" )
# ...
So können Sie Ihren Modellen eine Suchabfragezeichenfolge zuweisen und Sie, die Administratoren und/oder Benutzer Ihrer App erhalten leistungsstarke Abfragefunktionen, ohne dass zusätzliche Suchserver von Drittanbietern integriert werden müssen, da SearchCop die Volltextindexfunktionen Ihres RDBMS nutzen kann auf datenbankunabhängige Weise (derzeit werden MySQL- und PostgreSQL-Volltextindizes unterstützt) und optimiert die Abfragen, um sie optimal zu nutzen. Lesen Sie weiter unten mehr.
Auch komplexe Hash-basierte Abfragen werden unterstützt:
Book . search ( author : "Rowling" , title : "Harry Potter" )
Book . search ( or : [ { author : "Rowling" } , { author : "Tolkien" } ] )
Book . search ( and : [ { price : { gt : 10 } } , { not : { stock : 0 } } , or : [ { title : "Potter" } , { author : "Rowling" } ] ] )
Book . search ( or : [ { query : "Rowling -Potter" } , { query : "Tolkien -Rings" } ] )
Book . search ( title : { my_custom_sql_query : "Rowl" } } )
# ...
Fügen Sie diese Zeile zur Gemfile Ihrer Anwendung hinzu:
gem 'search_cop'
Und dann ausführen:
$ bundle
Oder installieren Sie es selbst als:
$ gem install search_cop
Um SearchCop für ein Modell zu aktivieren, include SearchCop
und geben Sie die Attribute an, die Sie für Suchanfragen innerhalb eines search_scope
verfügbar machen möchten:
class Book < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :title , :description , :stock , :price , :created_at , :available
attributes comment : [ "comments.title" , "comments.message" ]
attributes author : "author.name"
# ...
end
has_many :comments
belongs_to :author
end
Sie können natürlich auch beliebig mehrere search_scope
Blöcke angeben:
search_scope :admin_search do
attributes :title , :description , :stock , :price , :created_at , :available
# ...
end
search_scope :user_search do
attributes :title , :description
# ...
end
SearchCop analysiert die Abfrage und ordnet sie datenbankunabhängig einer SQL-Abfrage zu. Somit ist SearchCop nicht an ein bestimmtes RDBMS gebunden.
Book . search ( "stock > 0" )
# ... WHERE books.stock > 0
Book . search ( "price > 10 stock > 0" )
# ... WHERE books.price > 10 AND books.stock > 0
Book . search ( "Harry Potter" )
# ... WHERE (books.title LIKE '%Harry%' OR books.description LIKE '%Harry%' OR ...) AND (books.title LIKE '%Potter%' OR books.description LIKE '%Potter%' ...)
Book . search ( "available:yes OR created_at:2014" )
# ... WHERE books.available = 1 OR (books.created_at >= '2014-01-01 00:00:00.00000' and books.created_at <= '2014-12-31 23:59:59.99999')
SearchCop verwendet die Methoden „begin_of_year“ und „end_of_year“ von ActiveSupport für die Werte, die beim Erstellen der SQL-Abfrage für diesen Fall verwendet werden.
Natürlich erzielen diese LIKE '%...%'
Abfragen keine optimale Leistung, aber sehen Sie sich den folgenden Abschnitt über die Volltextfunktionen von SearchCop an, um zu verstehen, wie die resultierenden Abfragen optimiert werden können.
Da Book.search(...)
ein ActiveRecord::Relation
zurückgibt, können Sie die Suchergebnisse auf jede erdenkliche Weise vor- oder nachbearbeiten:
Book . where ( available : true ) . search ( "Harry Potter" ) . order ( "books.id desc" ) . paginate ( page : params [ :page ] )
Wenn Sie eine Abfragezeichenfolge an SearchCop übergeben, wird diese analysiert, analysiert und zugeordnet, um schließlich eine SQL-Abfrage zu erstellen. Genauer gesagt erstellt SearchCop beim Parsen der Abfrage Objekte (Knoten), die die Abfrageausdrücke darstellen (Und-, Oder-, Nicht-, String-, Datums- usw. Knoten). Um die SQL-Abfrage zu erstellen, verwendet SearchCop das Besucherkonzept, wie es beispielsweise in Arel verwendet wird, sodass es für jeden Knoten einen Besucher geben muss, der den Knoten in SQL umwandelt. Wenn kein Besucher vorhanden ist, wird eine Ausnahme ausgelöst, wenn der Abfrage-Generator versucht, den Knoten zu „besuchen“. Die Besucher sind für die Bereinigung der vom Benutzer bereitgestellten Eingaben verantwortlich. Dies geschieht in erster Linie durch Anführungszeichen (String-, Tabellennamen-, Spalten-Anführungszeichen usw.). SearchCop verwendet die vom ActiveRecord-Verbindungsadapter bereitgestellten Methoden zum Bereinigen/Quotieren, um eine SQL-Injection zu verhindern. Obwohl wir vor Sicherheitsproblemen nie 100 % sicher sein können, nimmt SearchCop Sicherheitsprobleme ernst. Bitte melden Sie sich verantwortungsbewusst über die Sicherheitsabteilung von flakks dot com, falls Sie sicherheitsrelevante Probleme feststellen.
SearchCop unterstützt JSON-Felder für MySQL sowie JSON-, JSONB- und Hstore-Felder für Postgres. Derzeit wird erwartet, dass Feldwerte immer Zeichenfolgen sind und es werden keine Arrays unterstützt. Sie können JSON-Attribute angeben über:
search_scope :search do
attributes user_agent : "context->browser->user_agent"
# ...
end
Dabei ist context
eine JSON/JSONB-Spalte, die beispielsweise Folgendes enthält:
{
"browser" : {
"user_agent" : " Firefox ... "
}
}
Standardmäßig, dh wenn Sie SearchCop nichts über Ihre Volltextindizes mitteilen, verwendet SearchCop LIKE '%...%'
-Abfragen. Leider können diese Abfragen keine SQL-Indizes verwenden, es sei denn, Sie erstellen einen Trigram-Index (nur Postgres), sodass jede Zeile von Ihrem RDBMS gescannt werden muss, wenn Sie nach Book.search("Harry Potter")
oder ähnlichem suchen. Um die Nachteile von LIKE
-Abfragen zu vermeiden, kann SearchCop die Volltextindexfunktionen von MySQL und PostgreSQL nutzen. Um bereits vorhandene Volltextindizes zu verwenden, weisen Sie SearchCop einfach an, diese zu verwenden:
class Book < ActiveRecord :: Base
# ...
search_scope :search do
attributes :title , :author
options :title , :type => :fulltext
options :author , :type => :fulltext
end
# ...
end
SearchCop ändert dann transparent seine SQL-Abfragen für die Attribute mit Volltextindizes in:
Book . search ( "Harry Potter" )
# MySQL: ... WHERE (MATCH(books.title) AGAINST('+Harry' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Harry' IN BOOLEAN MODE)) AND (MATCH(books.title) AGAINST ('+Potter' IN BOOLEAN MODE) OR MATCH(books.author) AGAINST('+Potter' IN BOOLEAN MODE))
# PostgreSQL: ... WHERE (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Harry') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Harry')) AND (to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Potter') OR to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Potter'))
Offensichtlich liefern diese Abfragen nicht immer die gleichen Ergebnisse wie Wildcard- LIKE
-Abfragen, da wir nach Wörtern und nicht nach Teilzeichenfolgen suchen. Allerdings bieten Volltextindizes in der Regel natürlich eine bessere Leistung.
Darüber hinaus ist die obige Abfrage noch nicht perfekt. Um es noch weiter zu verbessern, versucht SearchCop, die Abfragen zu optimieren, um Volltextindizes optimal zu nutzen und gleichzeitig eine Mischung mit Nicht-Volltextattributen zu ermöglichen. Um Abfragen noch weiter zu verbessern, können Sie Attribute gruppieren und ein Standardfeld für die Suche angeben, sodass SearchCop nicht mehr in allen Feldern suchen muss:
search_scope :search do
attributes all : [ :author , :title ]
options :all , :type => :fulltext , default : true
# Use default: true to explicitly enable fields as default fields (whitelist approach)
# Use default: false to explicitly disable fields as default fields (blacklist approach)
end
Jetzt kann SearchCop die folgende, noch nicht optimale Abfrage optimieren:
Book . search ( "Rowling OR Tolkien stock > 1" )
# MySQL: ... WHERE ((MATCH(books.author) AGAINST('+Rowling' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Rowling' IN BOOLEAN MODE)) OR (MATCH(books.author) AGAINST('+Tolkien' IN BOOLEAN MODE) OR MATCH(books.title) AGAINST('+Tolkien' IN BOOLEAN MODE))) AND books.stock > 1
# PostgreSQL: ... WHERE ((to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Rowling') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Rowling')) OR (to_tsvector('simple', books.author) @@ to_tsquery('simple', 'Tolkien') OR to_tsvector('simple', books.title) @@ to_tsquery('simple', 'Tolkien'))) AND books.stock > 1
auf die folgende, leistungsfähigere Abfrage:
Book . search ( "Rowling OR Tolkien stock > 1" )
# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('Rowling Tolkien' IN BOOLEAN MODE) AND books.stock > 1
# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', 'Rowling | Tokien') and books.stock > 1
Was passiert hier? Nun, wir haben all
als Namen einer Attributgruppe angegeben, die aus author
und title
besteht. Da wir außerdem all
als Volltextattribut angegeben haben, geht SearchCop davon aus, dass ein zusammengesetzter Volltextindex für author
und title
vorhanden ist, sodass die Abfrage entsprechend optimiert wird. Schließlich haben wir all
als Standardattribut für die Suche festgelegt, sodass SearchCop andere Attribute, wie z. B. stock
, ignorieren kann, solange sie nicht direkt in Abfragen angegeben werden (z. B. für stock > 0
).
Andere Abfragen werden auf ähnliche Weise optimiert, sodass SearchCop versucht, die Volltexteinschränkungen innerhalb einer Abfrage zu minimieren, nämlich MATCH() AGAINST()
für MySQL und to_tsvector() @@ to_tsquery()
für PostgreSQL.
Book . search ( "(Rowling -Potter) OR Tolkien" )
# MySQL: ... WHERE MATCH(books.author, books.title) AGAINST('(+Rowling -Potter) Tolkien' IN BOOLEAN MODE)
# PostgreSQL: ... WHERE to_tsvector('simple', books.author || ' ' || books.title) @@ to_tsquery('simple', '(Rowling & !Potter) | Tolkien')
Um einen Volltextindex für books.title
in MySQL zu erstellen, verwenden Sie einfach:
add_index :books , :title , :type => :fulltext
Für zusammengesetzte Indizes, die beispielsweise für das oben bereits angegebene all
verwendet werden, verwenden Sie:
add_index :books , [ :author , :title ] , :type => :fulltext
Bitte beachten Sie, dass MySQL Volltextindizes für MyISAM und ab MySQL-Version 5.6+ auch für InnoDB unterstützt. Weitere Einzelheiten zu MySQL-Volltextindizes finden Sie unter http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html
In Bezug auf PostgreSQL gibt es mehrere Möglichkeiten, einen Volltextindex zu erstellen. Eine der einfachsten Möglichkeiten ist jedoch:
ActiveRecord :: Base . connection . execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', title))"
Darüber hinaus sollten Sie für PostgreSQL das Schemaformat in config/application.rb
ändern:
config . active_record . schema_format = :sql
Bezüglich zusammengesetzter Indizes für PostgreSQL verwenden Sie:
ActiveRecord :: Base . connection . execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', author || ' ' || title))"
Um NULL-Werte mit PostgreSQL korrekt zu verarbeiten, verwenden Sie COALESCE sowohl zum Zeitpunkt der Indexerstellung als auch bei der Angabe des search_scope
:
ActiveRecord :: Base . connection . execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', COALESCE(author, '') || ' ' || COALESCE(title, '')))"
Plus:
search_scope :search do
attributes :title
options :title , :type => :fulltext , coalesce : true
end
Um ein anderes PostgreSQL-Wörterbuch als simple
zu verwenden, müssen Sie den Index entsprechend erstellen und SearchCop darüber informieren, z. B.:
search_scope :search do
attributes :title
options :title , :type => :fulltext , dictionary : "english"
end
Weitere Einzelheiten zu PostgreSQL-Volltextindizes finden Sie unter http://www.postgresql.org/docs/9.3/static/textsearch.html
Falls Sie Nicht-Volltext-Attribute für Suchanfragen verfügbar machen (Preis, Lagerbestand usw.), profitieren die entsprechenden Suchanfragen, wie z. B. Book.search("stock > 0")
, von den üblichen Nicht-Volltext-Indizes. Daher sollten Sie für jede Spalte, die Sie Suchanfragen zur Verfügung stellen, einen üblichen Index sowie einen Volltextindex für jedes Volltextattribut hinzufügen.
Falls Sie keine Volltextindizes verwenden können, weil Sie beispielsweise noch MySQL 5.5 verwenden und InnoDB oder ein anderes RDBMS ohne Volltextunterstützung verwenden, können Sie Ihr RDBMS so einstellen, dass es übliche Nicht-Volltextindizes für Zeichenfolgenspalten verwendet, wenn Sie diese nicht benötigen linker Platzhalter in LIKE
Abfragen. Geben Sie einfach die folgende Option an:
class User < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :username
options :username , left_wildcard : false
end
# ...
so dass SearchCop den Platzhalter ganz links weglässt.
User . search ( "admin" )
# ... WHERE users.username LIKE 'admin%'
Ebenso können Sie auch den rechten Platzhalter deaktivieren:
search_scope :search do
attributes :username
options :username , right_wildcard : false
end
Wenn Sie mehrere Felder in einem Suchbereich definieren, verwendet SearcCop standardmäßig den AND-Operator, um die Bedingungen zu verketten, z. B.:
class User < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :username , :fullname
end
# ...
end
Eine Suche wie User.search("something")
generiert also eine Abfrage mit den folgenden Bedingungen:
... WHERE username LIKE ' %something% ' AND fullname LIKE ' %something% '
Es gibt jedoch Fälle, in denen die Verwendung von AND als Standardoperator nicht erwünscht ist. SearchCop ermöglicht es Ihnen daher, dies zu überschreiben und stattdessen OR als Standardoperator zu verwenden. Eine Abfrage wie User.search("something", default_operator: :or)
generiert die Abfrage mithilfe von OR, um die Bedingungen zu verketten
... WHERE username LIKE ' %something% ' OR fullname LIKE ' %something% '
Bitte beachten Sie abschließend, dass Sie es auch auf Volltextindizes/-abfragen anwenden können.
Wenn Sie durchsuchbare Attribute aus einem anderen Modell angeben, z
class Book < ActiveRecord :: Base
# ...
belongs_to :author
search_scope :search do
attributes author : "author.name"
end
# ...
end
SearchCop lädt standardmäßig die referenzierten Assoziationen eager_load
, wenn Sie Book.search(...)
ausführen. Wenn Sie das automatische eager_load
nicht wünschen oder spezielle Vorgänge ausführen müssen, geben Sie einen scope
an:
class Book < ActiveRecord :: Base
# ...
search_scope :search do
# ...
scope { joins ( :author ) . eager_load ( :comments ) } # etc.
end
# ...
end
SearchCop überspringt dann das automatische Laden von Assoziationen und verwendet stattdessen den Bereich. Sie können scope
auch zusammen mit aliases
verwenden, um beliebig komplexe Verknüpfungen durchzuführen und in den verknüpften Modellen/Tabellen zu suchen:
class Book < ActiveRecord :: Base
# ...
search_scope :search do
attributes similar : [ "similar_books.title" , "similar_books.description" ]
scope do
joins "left outer join books similar_books on ..."
end
aliases similar_books : Book # Tell SearchCop how to map SQL aliases to models
end
# ...
end
Auch Assoziationen von Verbänden können referenziert und genutzt werden:
class Book < ActiveRecord :: Base
# ...
has_many :comments
has_many :users , :through => :comments
search_scope :search do
attributes user : "users.username"
end
# ...
end
SearchCop versucht, den Klassennamen und den SQL-Alias eines Modells aus den angegebenen Attributen abzuleiten, um Datentypdefinitionen usw. automatisch zu erkennen. Dies funktioniert normalerweise recht gut. Falls Sie benutzerdefinierte Tabellennamen über self.table_name = ...
verwenden oder wenn ein Modell mehrfach verknüpft ist, kann SearchCop jedoch nicht auf die Klassen- und SQL-Aliasnamen schließen, z. B
class Book < ActiveRecord :: Base
# ...
has_many :users , :through => :comments
belongs_to :user
search_scope :search do
attributes user : [ "user.username" , "users_books.username" ]
end
# ...
end
Damit Abfragen funktionieren, müssen Sie hier users_books.username
verwenden, da ActiveRecord Benutzern in seinen SQL-Abfragen einen anderen SQL-Alias zuweist, da das Benutzermodell mehrfach verknüpft ist. Da SearchCop das User
-Modell jetzt jedoch nicht aus users_books
ableiten kann, müssen Sie Folgendes hinzufügen:
class Book < ActiveRecord :: Base
# ...
search_scope :search do
# ...
aliases :users_books => :users
end
# ...
end
um SearchCop über den benutzerdefinierten SQL-Alias und die Zuordnung zu informieren. Darüber hinaus können Sie die Verknüpfungen jederzeit selbst über einen scope {}
-Block plus aliases
durchführen und Ihre eigenen benutzerdefinierten SQL-Aliase verwenden, um unabhängig von Namen zu werden, die von ActiveRecord automatisch zugewiesen werden.
Abfragezeichenfolgenabfragen unterstützen AND/and
, OR/or
, :
, =
, !=
, <
, <=
, >
, >=
, NOT/not/-
, ()
, "..."
und '...'
. Standardoperatoren sind AND
und matches
. OR
hat Vorrang vor AND
. NOT
kann nur als Infix-Operator für ein einzelnes Attribut verwendet werden.
Hash-basierte Abfragen unterstützen and: [...]
und or: [...]
, die ein Array von not: {...}
, matches: {...}
, eq: {...}
, not_eq: {...}
annehmen not_eq: {...}
, lt: {...}
, lteq: {...}
, gt: {...}
, gteq: {...}
und query: "..."
Argumente. Darüber hinaus ermöglicht query: "..."
die Erstellung von Unterabfragen. Die anderen Regeln für Abfragezeichenfolgenabfragen gelten auch für Hash-basierte Abfragen.
SearchCop bietet außerdem die Möglichkeit, benutzerdefinierte Operatoren zu definieren, indem in search_scope
ein generator
definiert wird. Sie können dann mit der Hash-basierten Abfragesuche verwendet werden. Dies ist nützlich, wenn Sie Datenbankoperatoren verwenden möchten, die von SearchCop nicht unterstützt werden.
Bitte beachten Sie, dass Sie bei der Verwendung von Generatoren dafür verantwortlich sind, die Werte zu bereinigen/in Anführungszeichen zu setzen (siehe Beispiel unten). Andernfalls lässt Ihr Generator die SQL-Injection zu. Benutzen Sie daher Generatoren bitte nur, wenn Sie wissen, was Sie tun.
Wenn Sie beispielsweise eine LIKE
-Abfrage durchführen möchten, bei der ein Buchtitel mit einer Zeichenfolge beginnt, können Sie den Suchbereich wie folgt definieren:
search_scope :search do
attributes :title
generator :starts_with do | column_name , raw_value |
pattern = " #{ raw_value } %"
" #{ column_name } LIKE #{ quote pattern } "
end
end
Wenn Sie die Suche durchführen möchten, verwenden Sie sie wie folgt:
Book . search ( title : { starts_with : "The Great" } )
Sicherheitshinweis: Die vom Generator zurückgegebene Abfrage wird direkt in die Abfrage interpoliert, die an Ihre Datenbank gesendet wird. Dies eröffnet einen potenziellen SQL-Injection-Punkt in Ihrer App. Wenn Sie diese Funktion verwenden, möchten Sie sicherstellen, dass die von Ihnen zurückgegebene Abfrage sicher ausgeführt werden kann.
Bei der Suche in booleschen Feldern, Datums-/Uhrzeitfeldern, Zeitstempelfeldern usw. führt SearchCop eine Zuordnung durch. Die folgenden Abfragen sind äquivalent:
Book . search ( "available:true" )
Book . search ( "available:1" )
Book . search ( "available:yes" )
sowie
Book . search ( "available:false" )
Book . search ( "available:0" )
Book . search ( "available:no" )
Für Datums- und Zeitstempelfelder erweitert SearchCop bestimmte Werte in Bereiche:
Book . search ( "created_at:2014" )
# ... WHERE created_at >= '2014-01-01 00:00:00' AND created_at <= '2014-12-31 23:59:59'
Book . search ( "created_at:2014-06" )
# ... WHERE created_at >= '2014-06-01 00:00:00' AND created_at <= '2014-06-30 23:59:59'
Book . search ( "created_at:2014-06-15" )
# ... WHERE created_at >= '2014-06-15 00:00:00' AND created_at <= '2014-06-15 23:59:59'
Eine Verkettung von Suchen ist möglich. Durch die Verkettung ist SearchCop jedoch derzeit nicht in der Lage, die einzelnen Abfragen für Volltextindizes zu optimieren.
Book . search ( "Harry" ) . search ( "Potter" )
wird erzeugen
# MySQL: ... WHERE MATCH(...) AGAINST('+Harry' IN BOOLEAN MODE) AND MATCH(...) AGAINST('+Potter' IN BOOLEAN MODE)
# PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry') AND to_tsvector(...) @@ to_tsquery('simple', 'Potter')
anstatt
# MySQL: ... WHERE MATCH(...) AGAINST('+Harry +Potter' IN BOOLEAN MODE)
# PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry & Potter')
Wenn Sie also Volltextindizes verwenden, vermeiden Sie besser eine Verkettung.
Bei Verwendung von Model#search
verhindert SearchCop bequem, dass bestimmte Ausnahmen ausgelöst werden, falls die an ihn übergebene Abfragezeichenfolge ungültig ist (Analysefehler, inkompatible Datentypfehler usw.). Stattdessen gibt Model#search
eine leere Beziehung zurück. Wenn Sie jedoch bestimmte Fälle debuggen müssen, verwenden Sie Model#unsafe_search
, wodurch diese ausgelöst werden.
Book . unsafe_search ( "stock: None" ) # => raise SearchCop::IncompatibleDatatype
SearchCop bietet reflektierende Methoden, nämlich #attributes
, #default_attributes
, #options
und #aliases
. Mit diesen Methoden können Sie beispielsweise ein individuelles Suchhilfe-Widget für Ihre Modelle bereitstellen, das die zu durchsuchenden Attribute sowie die Standardattribute usw. auflistet.
class Product < ActiveRecord :: Base
include SearchCop
search_scope :search do
attributes :title , :description
options :title , default : true
end
end
Product . search_reflection ( :search ) . attributes
# {"title" => ["products.title"], "description" => ["products.description"]}
Product . search_reflection ( :search ) . default_attributes
# {"title" => ["products.title"]}
# ...
Ab Version 1.0.0 verwendet SearchCop semantische Versionierung: SemVer
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)