Git Product home page Git Product logo

search_cop's Introduction

SearchCop

Build Status Code Climate Gem Version

search_cop

SearchCop extends your ActiveRecord models to support fulltext search engine like queries via simple query strings and hash-based queries. Assume you have a Book model having various attributes like title, author, stock, price, available. Using SearchCop you can perform:

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)")
# ...

Thus, you can hand out a search query string to your models and you, your app's admins and/or users will get powerful query features without the need for integrating additional third party search servers, since SearchCop can use fulltext index capabilities of your RDBMS in a database agnostic way (currently MySQL and PostgreSQL fulltext indices are supported) and optimizes the queries to make optimal use of them. Read more below.

Complex hash-based queries are supported as well:

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"}})
# ...

Installation

Add this line to your application's Gemfile:

gem 'search_cop'

And then execute:

$ bundle

Or install it yourself as:

$ gem install search_cop

Usage

To enable SearchCop for a model, include SearchCop and specify the attributes you want to expose to search queries within a search_scope:

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

You can of course as well specify multiple search_scope blocks as you like:

search_scope :admin_search do
  attributes :title, :description, :stock, :price, :created_at, :available

  # ...
end

search_scope :user_search do
  attributes :title, :description

  # ...
end

How does it work

SearchCop parses the query and maps it to an SQL Query in a database agnostic way. Thus, SearchCop is not bound to a specific RDBMS.

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 is using ActiveSupport's beginning_of_year and end_of_year methods for the values used in building the SQL query for this case.

Of course, these LIKE '%...%' queries won't achieve optimal performance, but check out the section below on SearchCop's fulltext capabilities to understand how the resulting queries can be optimized.

As Book.search(...) returns an ActiveRecord::Relation, you are free to pre- or post-process the search results in every possible way:

Book.where(available: true).search("Harry Potter").order("books.id desc").paginate(page: params[:page])

Security

When you pass a query string to SearchCop, it gets parsed, analyzed and mapped to finally build up an SQL query. To be more precise, when SearchCop parses the query, it creates objects (nodes), which represent the query expressions (And-, Or-, Not-, String-, Date-, etc Nodes). To build the SQL query, SearchCop uses the concept of visitors like e.g. used in Arel, such that, for every node there must be a visitor, which transforms the node to SQL. When there is no visitor, an exception is raised when the query builder tries to "visit" the node. The visitors are responsible for sanitizing the user supplied input. This is primilarly done via quoting (string-, table-name-, column-quoting, etc). SearchCop is using the methods provided by the ActiveRecord connection adapter for sanitizing/quoting to prevent SQL injection. While we can never be 100% safe from security issues, SearchCop takes security issues seriously. Please report responsibly via security at flakks dot com in case you find any security related issues.

Fulltext index capabilities

By default, i.e. if you don't tell SearchCop about your fulltext indices, SearchCop will use LIKE '%...%' queries. Unfortunately, unless you create a trigram index (postgres only), these queries can not use SQL indices, such that every row needs to be scanned by your RDBMS when you search for Book.search("Harry Potter") or similar. To avoid the penalty of LIKE queries, SearchCop can exploit the fulltext index capabilities of MySQL and PostgreSQL. To use already existing fulltext indices, simply tell SearchCop to use them via:

class Book < ActiveRecord::Base
  # ...

  search_scope :search do
    attributes :title, :author

    options :title, :type => :fulltext
    options :author, :type => :fulltext
  end

  # ...
end

SearchCop will then transparently change its SQL queries for the attributes having fulltext indices to:

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'))

Obviously, these queries won't always return the same results as wildcard LIKE queries, because we search for words instead of sub-strings. However, fulltext indices will usually of course provide better performance.

Moreover, the query above is not yet perfect. To improve it even more, SearchCop tries to optimize the queries to make optimal use of fulltext indices while still allowing to mix them with non-fulltext attributes. To improve queries even more, you can group attributes and specify a default field to search in, such that SearchCop must no longer search within all fields:

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

Now SearchCop can optimize the following, not yet optimal query:

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

to the following, more performant query:

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

What is happening here? Well, we specified all as the name of an attribute group that consists of author and title. As we, in addition, specified all to be a fulltext attribute, SearchCop assumes there is a compound fulltext index present on author and title, such that the query is optimized accordingly. Finally, we specified all to be the default attribute to search in, such that SearchCop can ignore other attributes, like e.g. stock, as long as they are not specified within queries directly (like for stock > 0).

Other queries will be optimized in a similar way, such that SearchCop tries to minimize the fultext constraints within a query, namely MATCH() AGAINST() for MySQL and to_tsvector() @@ to_tsquery() for 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')

To create a fulltext index on books.title in MySQL, simply use:

add_index :books, :title, :type => :fulltext

Regarding compound indices, which will e.g. be used for the default field all we already specified above, use:

add_index :books, [:author, :title], :type => :fulltext

Please note that MySQL supports fulltext indices for MyISAM and, as of MySQL version 5.6+, for InnoDB as well. For more details about MySQL fulltext indices visit http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html

Regarding PostgreSQL there are more ways to create a fulltext index. However, one of the easiest ways is:

ActiveRecord::Base.connection.execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', title))"

Moreover, for PostgreSQL you should change the schema format in config/application.rb:

config.active_record.schema_format = :sql

Regarding compound indices for PostgreSQL, use:

ActiveRecord::Base.connection.execute "CREATE INDEX fulltext_index_books_on_title ON books USING GIN(to_tsvector('simple', author || ' ' || title))"

To handle NULL values with PostgreSQL correctly, use COALESCE both at index creation time and when specifying the 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

To use another PostgreSQL dictionary than simple, you have to create the index accordingly and you need tell SearchCop about it, e.g.:

search_scope :search do
  attributes :title

  options :title, :type => :fulltext, dictionary: "english"
end

For more details about PostgreSQL fulltext indices visit http://www.postgresql.org/docs/9.3/static/textsearch.html

Other indices

In case you expose non-fulltext attributes to search queries (price, stock, etc.), the respective queries, like Book.search("stock > 0"), will profit from the usual non-fulltext indices. Thus, you should add a usual index on every column you expose to search queries plus a fulltext index for every fulltext attribute.

In case you can't use fulltext indices, because you're e.g. still on MySQL 5.5 while using InnoDB or another RDBMS without fulltext support, you can make your RDBMS use usual non-fulltext indices for string columns if you don't need the left wildcard within LIKE queries. Simply supply the following option:

class User < ActiveRecord::Base
  include SearchCop

  search_scope :search do
    attributes :username

    options :username, left_wildcard: false
  end

  # ...

such that SearchCop will omit the left most wildcard.

User.search("admin")
# ... WHERE users.username LIKE 'admin%'

Similarly, you can disable the right wildcard as well:

search_scope :search do
  attributes :username

  options :username, right_wildcard: false
end

Default operator

When you define multiple fields on a search scope, SearcCop will use by default the AND operator to concatenate the conditions, e.g:

class User < ActiveRecord::Base
  include SearchCop

  search_scope :search do
    attributes :username, :fullname
  end

  # ...
end

So a search like User.search("something") will generate a query with the following conditions:

... WHERE username LIKE '%something%' AND fullname LIKE '%something%'

However, there are cases where using AND as the default operator is not desired, so SearchCop allows you to override it and use OR as the default operator instead. A query like User.search("something", default_operator: :or) will generate the query using OR to concatenate the conditions

... WHERE username LIKE '%something%' OR fullname LIKE '%something%'

Finally, please note that you can apply it to fulltext indices/queries as well.

Associations

If you specify searchable attributes from another model, like

class Book < ActiveRecord::Base
  # ...

  belongs_to :author

  search_scope :search do
    attributes author: "author.name"
  end

  # ...
end

SearchCop will by default eager_load the referenced associations, when you perform Book.search(...). If you don't want the automatic eager_load or need to perform special operations, specify a scope:

class Book < ActiveRecord::Base
  # ...

  search_scope :search do
    # ...

    scope { joins(:author).eager_load(:comments) } # etc.
  end

  # ...
end

SearchCop will then skip any association auto loading and will use the scope instead. You can as well use scope together with aliases to perform arbitrarily complex joins and search in the joined models/tables:

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

Assocations of associations can as well be referenced and used:

class Book < ActiveRecord::Base
  # ...

  has_many :comments
  has_many :users, :through => :comments

  search_scope :search do
    attributes user: "users.username"
  end

  # ...
end

Custom table names and associations

SearchCop tries to infer a model's class name and SQL alias from the specified attributes to autodetect datatype definitions, etc. This usually works quite fine. In case you're using custom table names via self.table_name = ... or if a model is associated multiple times, SearchCop however can't infer the class and SQL alias names, e.g.

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

Here, for queries to work you have to use users_books.username, because ActiveRecord assigns a different SQL alias for users within its SQL queries, because the user model is associated multiple times. However, as SearchCop now can't infer the User model from users_books, you have to add:

class Book < ActiveRecord::Base
  # ...

  search_scope :search do
    # ...

    aliases :users_books => :users
  end

  # ...
end

to tell SearchCop about the custom SQL alias and mapping. In addition, you can always do the joins yourself via a scope {} block plus aliases and use your own custom sql aliases to become independent of names auto-assigned by ActiveRecord.

Supported operators

Query string queries support AND/and, OR/or, :, =, !=, <, <=, >, >=, NOT/not/-, (), "..." and '...'. Default operators are AND and matches, OR has precedence over AND. NOT can only be used as infix operator regarding a single attribute.

Hash based queries support and: [...] and or: [...], which take an array of not: {...}, matches: {...}, eq: {...}, not_eq: {...}, lt: {...}, lteq: {...}, gt: {...}, gteq: {...} and query: "..." arguments. Moreover, query: "..." makes it possible to create sub-queries. The other rules for query string queries apply to hash based queries as well.

Custom operators (Hash based queries)

SearchCop also provides the ability to define custom operators by defining a generator in search_scope. They can then be used with the hash based query search. This is useful when you want to use database operators that are not supported by SearchCop.

Please note, when using generators, you are responsible for sanitizing/quoting the values (see example below). Otherwise your generator will allow SQL injection. Thus, please only use generators if you know what you're doing.

For example, if you wanted to perform a LIKE query where a book title starts with a string, you can define the search scope like so:

search_scope :search do
  attributes :title

  generator :starts_with do |column_name, raw_value|
    pattern = "#{raw_value}%"
    "#{column_name} LIKE #{quote pattern}"
  end
end

When you want to perform the search you use it like this:

Book.search(title: { starts_with: "The Great" })

Security Note: The query returned from the generator will be interpolated directly into the query that goes to your database. This opens up a potential SQL Injection point in your app. If you use this feature you'll want to make sure the query you're returning is safe to execute.

Mapping

When searching in boolean, datetime, timestamp, etc. fields, SearchCop performs some mapping. The following queries are equivalent:

Book.search("available:true")
Book.search("available:1")
Book.search("available:yes")

as well as

Book.search("available:false")
Book.search("available:0")
Book.search("available:no")

For datetime and timestamp fields, SearchCop expands certain values to ranges:

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'

Chaining

Chaining of searches is possible. However, chaining does currently not allow SearchCop to optimize the individual queries for fulltext indices.

Book.search("Harry").search("Potter")

will generate

# 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')

instead of

# MySQL: ... WHERE MATCH(...) AGAINST('+Harry +Potter' IN BOOLEAN MODE)
# PostgreSQL: ... WHERE to_tsvector(...) @@ to_tsquery('simple', 'Harry & Potter')

Thus, if you use fulltext indices, you better avoid chaining.

Debugging

When using Model#search, SearchCop conveniently prevents certain exceptions from being raised in case the query string passed to it is invalid (parse errors, incompatible datatype errors, etc). Instead, Model#search returns an empty relation. However, if you need to debug certain cases, use Model#unsafe_search, which will raise them.

Book.unsafe_search("stock: None") # => raise SearchCop::IncompatibleDatatype

Reflection

SearchCop provides reflective methods, namely #attributes, #default_attributes, #options and #aliases. You can use these methods to e.g. provide an individual search help widget for your models, that lists the attributes to search in as well as the default ones, etc.

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"]}

# ...

Semantic Versioning

Starting with version 1.0.0, SearchCop uses Semantic Versioning: SemVer

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

search_cop's People

Contributors

anaice avatar danieldias51 avatar fabiomr avatar jakecraige avatar kfprimm avatar kmdtmyk avatar madbomber avatar mitsuru avatar mrkamel avatar shivashankar-ror avatar urubatan avatar weirdobeaver avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

search_cop's Issues

Models using different database

Basically I have a site model which is in a different master database.

I have the following search in an associated model:

attributes :name, :description, site: ["site.site_code", "site.name"]

Whats happening is that the search query is generated using the fields in the master database. But the table join is is looking for the sites table on the application database.

Sub-scopes in Rails 4

Thanks for this! I'd like to optimize my query by using scope (see "scope { joins(:author).eager_load(:comments) } # etc." in the docs). That formulation doesn't work in Rails 4. Can you update with tips on what would work for Rails 4?

ActiveRecord::ConfigurationError in v1.2.1

We upgraded to the latest version and we started having the above error. We have gone back to v1.2.0 for now as that works for us.

The complete error message: ActiveRecord::ConfigurationError: Can't join 'Notes::Note' to association named 'notes/notes'; perhaps you misspelled it?

Note: The searchable model is Note, namespaced under the Notes module.

I hope to find some time this week to look deeper into this and see if I can figure out exactly what is happening.

Searchable tags

I was wondering how it might work if tags relating to a book using the acts-as-taggable-on gem which uses a taggings joining table.

Would this be compatible with the kind of model associations that the attr_searchable gem allows?

If so, how would you declare it at the top of the Book model?

Would it use the indexes added to tags and taggings tables?
I remember seeing on the pg_search gem that any searches of associated tables would not use the indexes on the associated tables (not sure why) if I understand it correctly (https://github.com/Casecommons/pg_search#searching-through-associations).

I could 'cache' the tags_list as a field on the books table as well as adding it to the taggings/tags tables using a before_save callback so that the search would only need to look at fields on a single table but I was curious about the use of indexes on associated tables and whether the associations would allow for tags via a linking table.

Thanks

Postgres DatetimeFieldOverflow

We have had a few production errors like this one:

The user is searching for 00393488235373. Searchcop searches a number of fields, including some which are date/timestamps, treating this integer as a year. Postgres gets upset because the year is too large for it, and crashes, something like:

PG::DatetimeFieldOverflow: ERROR:  date/time field value out of range: "393488235373-01-01 00:00:00.000000"
LINE 1: ...'%00393488235373%' OR ("accounts"."created_at" >= '393488235...
                                                             ^
: SELECT [...snipped...] WHERE (("accounts"."first_name" ILIKE '%00393488235373%' OR "accounts"."last_name" ILIKE '%00393488235373%' OR "accounts"."email" ILIKE '%00393488235373%' OR "accounts"."phone" ILIKE '%00393488235373%' OR ("accounts"."created_at" >= '393488235373-01-01 00:00:00.000000' AND "accounts"."created_at" <= '393488235373-12-31 23:59:59.999999') OR  [...snipped...] 

The search is not related models

I have 2 models. They are not related.

class Event < ActiveRecord::Base
end

class Movie < ActiveRecord::Base
end

How to search for both events.title and movies.name?

globalize with search_cop - unknown attribute

I'm trying to use the globalize gem and search_cop together. In my model I have:

class Museum < ApplicationRecord
  include SearchCop

  translates :name, :address, :description, :facilities, :hours, :tickets

  search_scope :search do
    attributes :name, :address
    options :name, :type => :fulltext
    options :address, :type => :fulltext
  end
end

But when I go to search I get:

irb(main):006:0> Museum.search("art")
SearchCop::UnknownAttribute: Unknown attribute museums.name

But the name works with Globalize (it stores translations in a separate table)

irb(main):001:0> Museum.first.name
   (23.2ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  Museum Load (4.6ms)  SELECT  `museums`.`id`, `museums`.`source`, `museums`.`source_id`, `museums`.`country`, `museums`.`email`, `museums`.`latitude`, `museums`.`longitude`, `museums`.`phone`, `museums`.`website`, `museums`.`created_at`, `museums`.`updated_at` FROM `museums` ORDER BY `museums`.`id` ASC LIMIT 1
  Museum::Translation Load (8.4ms)  SELECT `museum_translations`.* FROM `museum_translations` WHERE `museum_translations`.`museum_id` = 1
=> "Rocky Mountain House Museum"

Any ideas how to set up search_cop so it will work with the Globalize does things?

query association two levels deep

I have a person model that has many identifications (Identification model has an attribute number) and each identification has an identification_type (IdentificationType model has an attribute name)

I want to search for all people that have a identification number of 12345678 and identification_type that has a name of 'ssn'. Can this be accomplished in search_cop?

wildcard in fulltext mode

When searching without fulltext (and :left_wildcard => false) the query is constructed as field LIKE 'foo%'. However in fulltext mode, the query is MATCH(table.field) AGAINST('+foo' IN BOOLEAN MODE), which looks for exact matches instead of a wildcard type search, which would be +foo*.

So, it seems like fulltext query elements should have * appended automatically.

Can search_cop work with Postgres hstore?

(somewhat related to #31)

I am evaluating my options in regards to having arbitrary data that is still searchable in Rails and Postgres. One option I am looking at is the Postgres hstore structure.

Is there any chance that this can work with search_cop?

Searching a table with hstore column, looks like this:

Profile.where("settings->'color' = ?", "yellow")
# => #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>

I would love to have something much simpler in search_cop - specifically in the string search format - maybe like this:

Profile.search "settings.color = yellow and <some additional free text expressions>"

Or perhaps an alternative (additional) approach:

search_scope :search do
   attributes :color, :resolution
   options :color, type: :hstore, field: :settings
   options :resolution, type: :hstore, field: :settings
end

and then, we can just use the plain attribute name (without column name prefix):

Profile.search "color = yellow or resolution = 1024x486"

If this works - or can be made to work - this would be super cool.

Add support to custom mappers

I've hacked some code (https://github.com/wagner/search_cop/commit/e66ec0156fee0e0782f39eea1b2b94a0c2fe5b95) to allow a custom mapper for a brainspec/enumerize attribute:

class Comment < ActiveRecord::Base
  include SearchCop
  extend Enumerize

  belongs_to :user

  enumerize :state, in: {:new => 1, :approved => 2, :rejected => 3}, default: :new

  search_scope :search do
    ...
    options :state, :enumerator => Comment.state
  end
end

Comment.search("state: rejected")

But after I realized a custom mapper option could be more useful to everyone with similar needs. Something like this:

  search_scope :search do
    ...
    options :state, :mapper => lambda { |value| Comment.state.find_value(value).value }
  end

What do you think?

README error?

I was just looking through the README when I saw this example

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' and books.created_at <= '2014-12-31 00:00:00')

Shouldn't that last time string be "23:59:59" ??? if not, then your search misses all but one second of the last day of the year.

Matching multiple in a has_many

class Company < ApplicationRecord
  include Inspectable
  include SearchCop

  has_many :technology_detections, autosave: true, dependent: :destroy
  has_many :technologies, through: :technology_detections
  
  search_scope :search do
    attributes all: [:name, :description]
    options :all, type: :fulltext, default: true
    
    attributes :name, :description, :market_cap, :raised,
               :annual_revenue, :contacts_count, :employees

    attributes contact: ["contacts.role", "contacts.full_name"]
    attributes technology: 'technologies.name'
  end

If I perform the search, Company.search("technology:rails technology:relic")

the following query is generated :

SELECT  DISTINCT `companies`.`id` FROM `companies` LEFT OUTER JOIN `contacts` ON `contacts`.`company_id` = `companies`.`id` AND `contacts`.`active` = TRUE LEFT OUTER JOIN `technology_detections` ON `technology_detections`.`company_id` = `companies`.`id` LEFT OUTER JOIN `technologies` ON `technologies`.`id` = `technology_detections`.`technology_id` WHERE (((`technologies`.`name` IS NOT NULL AND LOWER(`technologies`.`name`) LIKE '%rails%') AND (`technologies`.`name` IS NOT NULL AND LOWER(`technologies`.`name`) LIKE '%relic%'))) LIMIT 11

What I am trying to accomplish is to return all of the companies that have the technologies of rails and relic associated with them. But the search is returning an empty set.

But I know that there are companies in my data that meet this requirement.

Error when using with non-standard pluralization in class name

My class for windows devices is named Device::Windows, rather than Device::Window... becuase Window isn't the OS name. Device::Mac works great with search_cop though :)

class Device::Windows < ApplicationRecord
  include SearchCop

  search_scope :search do
    attributes :serial_number, :hostname
  end
end
bc-windows:development [44] pry(main)> Device::Windows.search('kuzmik')
NameError: uninitialized constant Device::Window
from /Users/kuzmik/.rbenv/versions/2.6.5/gemsets/widows/gems/activesupport-5.2.4/lib/active_support/inflector/methods.rb:285:in `const_get'
windows:development [45] pry(main)>

I tried setting up inflections in the rails initializer, but it didn't seem to do the trick.

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.plural 'Device::Windows', 'Device::Windows'
  inflect.singular 'Device::Windows', 'Device::Windows'
end

I assume it's something silly I have misconfigured somewhere, but any help you can provide would be appreciated. I'd love to drop ransack and use search_cop instead!

Ignoring MySQL stop words for full-text search

Is there a way to ignore stop-words for MySQL full-text search? At the moment if we enter a search string that contains stop words, the stop words also get added to the SQL query with + signand the search result is empty.

mysql> SELECT  artifact_name, title FROM `posts` WHERE ((MATCH(`posts`.`artifact_name`, `posts`.`title`, `posts`.`body`) AGAINST('+stranger +in +a +strange' IN BOOLEAN MODE)));
Empty set (0.00 sec)

Note that the above query returns an empty set.

The below query works fine as the stop words are removed.

mysql> SELECT  artifact_name, title FROM `posts` WHERE ((MATCH(`posts`.`artifact_name`, `posts`.`title`, `posts`.`body`) AGAINST('+stranger +strange' IN BOOLEAN MODE)));
+----------------------------+----------------------------------------------------------------------+
| artifact_name              | title                                                                |
+----------------------------+----------------------------------------------------------------------+
| Stranger in a Strange Land | You can't copy the driver without indexing the back-end SSL circuit! |
| Stranger in a Strange Land | Digitized incremental challenge.                                     |
+----------------------------+----------------------------------------------------------------------+
2 rows in set (0.00 sec)

The stop gap can be that at the application level we could filter stop words before submitting the query. However, I would think this should be a common issue and it would be great if search_cop could help with this.

RAILS 6 - Search in ActionText

Hello,

I'd like to know if I can search in "rich_text" (ActionText) with SearchCop ? If yes, how to do this ?

  has_rich_text :content

  include SearchCop

  search_scope :search do
    attributes :title
  end

Thanks,
Thomas

How to force to use LIKE query on nonstring fields?

SELECT `orders`.* FROM `orders` WHERE `orders`.`deleted_at` IS NULL AND ((`orders`.`id` = 2059000 OR `orders`.`number` = 2059000 OR `orders`.`comment` LIKE '%2059000%'))

As you see only comment field is looked up with LIKE statement. But I wanna find orders with numbers: 20590001, 20590002, 20590003 and so on.

Suggestion: Option for `exact: true` (or `right_wildcard: false`)

Hi,

I have noticed that exact search was somewhat discussed in #23, but I think this issue is different.

I know there is the option to disable left wildcard, but:

  • I did not find an option to disable right wildcard, or even better;
  • I was hoping to have an option that is called exact, that disables both left and right wildcards.
search_scope :search do
  attributes :first_name, :last_name, :location
  # was hoping for this:
  options :location, exact: true
  # or at least this:
  options :location, left_wildcard: false, right_wildcard: false
end

Search word that include % does not work correctly

For example, items table has this records.

SELECT * FROM items;
id name
1 The 100% complete guide for Ruby on Rails
2 100G byte SSD
class Item < ApplicationRecord
  include SearchCop

  search_scope :search do
    attributes :name
  end

end

Item.search('100%')

I expected this returns a record with id 1 only. But, this returns both records.

Possible to search nested/polymorphic models?

Hi!

I have the following model setup:

class User < ActiveRecord::Base
  belongs_to :meta, polymorphic: true
end

class Client < ActiveRecord::Base
  has_one :user, as: :meta, dependent: :destroy
end

class Coach < ActiveRecord::Base
  has_one :user, as: :meta, dependent: :destroy
end

class Appointment < ActiveRecord::Base
  belongs_to :coach
  belongs_to :client
end

I would like to try to search both clients and coaches by their names and email address, however since these attributes are not on the client or coach model themselves, but are on the polymorphic user model, the following does not work.

search_scope :search do          
  attributes client:   ['client.user.first_name', 'client.user.last_name', 'client.user.email']
  attributes coach:  ['coach.user.first_name', 'coach.user.last_name', 'coach.user.email']
end

I've tried setting up various search scopes and aliases with no luck. Is there a way to do this?

generator for attribute options

Hello @mrkamel

I was wondering if it would be possible to use a generator directly in the options of an attribute.

search_scope :search do
  attributes person: [ "person.full_name", "person.display_name", "person.doc_id"]
  options :person, left_wildcard: false
  attributes partner_doc_id: ["person.doc_id" ]
  options :partner_doc_id, raw_ilike: true

  #...

  generator :raw_ilike do |column_name, raw_value|
    "(  #{column_name} IS NOT NULL AND
       regexp_replace(#{column_name}, '[^a-zA-Z0-9]+', '','g') ILIKE ('%' || regexp_replace('#{raw_value}', '[^a-zA-Z0-9]+', '','g') || '%') 
      )
     "
  end
end

I know the generator is designed for hash based queries, but it would be a very useful feature to be able to apply it directly to an attribute.

Suggestion: Add support for sort in the free text search

It would be super nice if the free expression search would support sorting, so we can do:

User.search "status = active and comments > 1 sort : -joined_on"

or some other human-friendly syntax. Here I assumed - for descending order, and + (or no prefix) for ascending order.

Is this considered out of scope for search_cop?
I just find myself beginning to enjoy just giving the users a single search field, but now I still have to provide them with "Sort By" UI controls, which I would love to retire as well.

Support for PostgreSQL ENUM

Great gem, @mrkamel!

One small thing: Support for PostgreSQL enums would be great. Currently, it fails with a NameError. Example:

CREATE TYPE public.person_gender AS ENUM (
    'male',
    'female'
);

CREATE TABLE public.people (
    id bigint NOT NULL,
    name character varying,
    gender public.person_gender
);
class Person < ApplicationRecord
  include SearchCop

  enum gender: {
    male: 'male',
    female: 'female'
  }

  search_scope :search do
    attributes :gender, :name
  end
end

Person.search("gender:male")
=> NameError: Uninitialized constant SearchCopGrammar::Attributes::Enum

Problem with custom select / PG function

Hello!
i'm trying to use search_cop with my quite complex model code. Basically, i have a custom postgres function and i'm using it like that:

@users = User.all.select('CUSTOM_FUNCTION(users.id) AS custom_function')
@users.last.id  #=> 1
@users.last.custom_function  #=> "result"

However, when i apply a search_cop method search() to the code above, i'm not able to access my custom_function result row:

@users = User.all.search('query').select('CUSTOM_FUNCTION(users.id) AS custom_function')
@users.last.id  #=> 1
@users.last.custom_function  #=> nil

What's more interesting - i can see that db query produced by the chain above looks quite proper and i'm able to use it in pgAdminII which gives me a custom_function column with the proper value.

Have you any idea what can be wrong in here or what can i do about it?

Thanks in advance.

Problems with Paranoia gem

Basically I have a site model which has acts_as_paranoid on it. That gem places a default scope on the paranoid model: where("deleted_at IS NULL")

I have the following search in an associated model:

attributes :name, :description, site: ["site.site_code", "site.name"]

I get the following error when running this search:

Mysql2::Error: Unknown column 'sites.deleted_at' in 'field list'

Attribute (case insensitive / ILIKE)

Hello,

Thanks for this excellent gem!

I have a doubt: is it possible to add an ignore case option for attribute?

class Book < ActiveRecord::Base
  # ...

  search_scope :search do
    attributes :title, :author

    # uses ILIKE when :ignore_case
    options :title, :ignore_case => true
    options :author, :ignore_case => true
  end

  # ...
end

Citext support on Postgres

Citext column type is useful on Postgres for case-insesitive comparisons [0], currently searching for such column fails with

     NameError:
       uninitialized constant SearchCopGrammar::Attributes::Citext

placing following code in initializer fixes it (I could add a PR but not sure about tests)

module SearchCopGrammar
  module Attributes
    class Citext < String; end
  end
end

module SearchCop
  module Visitors
    class Visitor
      alias :visit_SearchCopGrammar_Attributes_Citext :visit_attribute
    end
  end
end

[0] https://www.postgresql.org/docs/10/static/citext.html

Searching for empty or non empty with the free text expression

I was going through the README to see if I missed it, but could not find anything related.

Is there a way / operator to search for empty or non empty values?

What I have in mind is like GitHub and Gmail search. For example, to search for users with non-empty email, one of these would be nice to have:

  • has:email
  • email:*
  • email:empty (and email:any as the opposite)

Is this possible? If not, is there any other way to search for empty/non empty attributes?

While on that note, it would also be nice to have the below syntax for booleans (like GitHub).

  • is:active # same as active:yes
  • is:issue

NoMethodError: undefined method `optimize!' for nil:NilClass

I've tried to use attr_searchable as per the docs but I'm getting an error:

# AR Model
class Museum < ActiveRecord::Base
  include AttrSearchable

  attr_searchable_options :name, :type => :fulltext
end

From the Rails console I try to search the model but get the following:

[4] pry(main)> Museum.search("Museo")
NoMethodError: undefined method `optimize!' for nil:NilClass
from /Users/barnacle/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/attr_searchable-0.0.3/lib/attr_searchable.rb:79:in `unsafe_search'
[5] pry(main)> Museum.search(name: "Museo")
=> []

I'm running the latest Rails 4.1.2 and attr_searchable (0.0.3). Any if why this isn't working?

Rails 4.2, arel 6: wrong number of arguments for #accept

Thanks for search_cop! I was using attr_searchable before, and I've been upgrading my app. I just started a new Rails 4.2 project and did the most basic search_cop installation.

class Bug < ActiveRecord::Base
  include SearchCop
  search_scope :search do
    attributes :title
  end
end

When I try a search, I get the following error:

Bug.search("cimon")
ArgumentError: wrong number of arguments (1 for 2)
    from /Users/adam/.rvm/gems/ruby-2.1.2/gems/arel-6.0.0.beta2/lib/arel/visitors/reduce.rb:6:in `accept'

I don't know this area very well, but it looks like arel 6 added a second argument to the #accept method:
rails/arel@a6a7c75
which search_cop uses in query_builder:
https://github.com/mrkamel/search_cop/blob/master/lib/search_cop/query_builder.rb#L12

I tried downgrading arel to lower version, but it said arel 6 is required for rails 4.2.

One model associated multiple times under different name

Hello!
I'm trying to use search_cop and everything is going great so far. However, i have encountered an issue with my database / model setup. One of my models looks like that:

job.rb

class Job < ActiveRecord::Base
  belongs_to :user, foreign_key: 'customer_id'
  belongs_to :customer, class_name: 'User'
  belongs_to :technician, class_name: 'User'
  belongs_to :author, class_name: 'User'
end

And i was wondering - how can i make it able to search by all these 3 associations ? Using the following schema does not seems to be working:

search_scope :search do
  attributes :status, :customer_id, :technician_id, :author_id, :ro_number,
             :end_behaviour, :ro_value, :created_at

  attributes :customer   => ['customer.name', 'customer.email', 'customer.phone']
  attributes :technician => ['technician.name', 'technician.email', 'technician.phone']
  attributes :author     => ['author.name', 'author.email', 'author.phone']
end

cause using Job.search('[email protected]') (while this email belongs to a technician user) produces the following output: https://gist.github.com/mbajur/2ccf856b2a1ff2be2348

I can feel that it can be something connected to aliases but i have no idea how should i implement it in my app. Thanks in advance for any clues! :)

conflict with ransack

My web app uses Active Admin that uses ransack. I cannot seem to use Search Cop as Ransack uses the search method. Is there any way to avoid conflict with search method in Search Cop and ransack?

I18N of operators

Is there an easy way to switch the operators to another language ('AND' -> 'UND', 'OR' -> 'ODER') or could that be easily implemented (e.g. by having pluggable grammars)?

DEPRECATION WARNING

DEPRECATION WARNING: #search is deprecated and will be removed in 2.3, please use #ransack instead (called from pry at (pry):21)

Can I escape operators?

I am trying to search the string "xxxxx==\n", is there a way I can escape those equal sign operators?

multiple where conditions and concatenated with AND

Hi,

I'm using search_cop to perform a full text search on two models with a relationship, but the way WHERE condition is generated is not allowing me to get any results.

The scenario:
I've a model lets call it A and it has a one to many relation with a model B.
This model B is a table with synonyms, so it just has a "synonym" and the foreign key for the model A.

I've created on the model A a grouped FTS key attributes all: [:name, :description, ...]
and on the model B I've just one field with FTS key.

My problem, the query generated has 2 "MATCH AGAINST" conditions, one for the first grouped key and one for the other key, exactly as it should, but they are concatenated by an AND then:

WHERE 
((MATCH(`A`.`name`, `A`.`description`, `A`.etc...) AGAINST('+my +search' IN BOOLEAN MODE))) 
AND 
((MATCH(`B`.`name`) AGAINST('+my +search' IN BOOLEAN MODE)))

Since the second table is a synonym table, if it doesn't match the search it prevents me from getting results...

Is there a way to use OR instead of an AND to concatenate the conditions?
If not, I'd be glad to contribute if you want.

I'm facing also other issue, the + added before each term.
This make the term required, so if the user type a word without a match, the hole query doesn't return results.
Is there an option to prevent to add the +?

I'd be glad to create a PR to add those features if you want. I would only need some tips from you.

Words containing brackets

I tried search_cop (1.2.3).
I can't search for words containing brackets, like "(" or ")".
Perhaps the sql query is not being executed.
Any ideas?

Rails 5 Support

Hi,

I'm considering this for a new API I'm building and I noticed that the home page mentions Rails 3 and Rails 4 only. Are there issues with Rails 5 or are you just assuming that's the defaut?

Thanks!

undefined method 'visit_SearchCopGrammar_Nodes_MatchesFulltext'

First, I want to thank you for your excellent library.
Unfortunately, I am encountering an issue which I can't figure out.

When a user submits a query that includes spaces, the server responds with

undefined method 'visit_SearchCopGrammar_Nodes_MatchesFulltext'
with the following backtrace.

search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:15:in `visit'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:23:in `block in visit_SearchCopGrammar_Nodes_Or'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:23:in `collect'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:23:in `visit_SearchCopGrammar_Nodes_Or'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:15:in `visit'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:55:in `visit_SearchCopGrammar_Nodes_Not'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:15:in `visit'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:19:in `block in visit_SearchCopGrammar_Nodes_And'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:19:in `collect'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:19:in `visit_SearchCopGrammar_Nodes_And'
search_cop (1.0.6) lib/search_cop/visitors/visitor.rb:15:in `visit'
search_cop (1.0.6) lib/search_cop/query_builder.rb:12:in `initialize'
search_cop (1.0.6) lib/search_cop.rb:61:in `new'
search_cop (1.0.6) lib/search_cop.rb:61:in `unsafe_search_cop'
search_cop (1.0.6) lib/search_cop.rb:53:in `search_cop'
search_cop (1.0.6) lib/search_cop.rb:44:in `block in search_scope'

I am running Mysql 5.6 and have set up my model with the following options:

  search_scope :search do
    attributes :question, :answer

    options :question, :type => :fulltext
    options :answer, :type => :fulltext
  end

I have verified that the fulltext indexes for question and answer are in place.

I am calling search scope with the following:

  Help.search(params[:q])

I do not encounter this issue when running in development locally.

Any ideas you have as to what is causing this error would be greatly appreciated

How to handle special search logic / virtual attributes

Hi,

I am not sure if this is related to #14 or not, but since that issue is old, I figured I will ask here.

I just started testing search_cop, and immediately bumped into two special cases that I am not sure how to solve (if at all possible):

Case 1: Full name virtual attribute

My model has first_name and last_name, and I would like to allow searching using

Member.search "name:'James Bond'"

Case 2: Local phone number virtual attribute

My model stores phone numbers using the full international format, but I would like to allow searching by using a local format. The local format is prefixed with a 0, while the international value is not.

Prior to using search_cop, I achieved both with something like the below:

def self.search(query)
  normalized_phone = query.phony_normalized || query
  where "
    members.phone ilike ?
    or (members.first_name || ' ' || members.last_name) ilike ?",
    "%#{normalized_phone}%", "%#{query}%", "%#{query}%"
end

Is there anything I missed in the documentation to allow this flexibility?

Rails4.2 compatibility when use colon(::) for class name.

I have posted question #4.
Today, upgrade to rails 4.2.

I'm using

  • ruby 2.1.3
  • search_cop 1.0.3

so, An error occurred like below.

NameError - uninitialized constant Setting:
  activesupport (4.2.0) lib/active_support/dependencies.rb:533:in `load_missing_constant'
  activesupport (4.2.0) lib/active_support/dependencies.rb:184:in `const_missing'
  activesupport (4.2.0) lib/active_support/inflector/methods.rb:261:in `block in constantize'
  activesupport (4.2.0) lib/active_support/inflector/methods.rb:259:in `constantize'
  activesupport (4.2.0) lib/active_support/core_ext/string/inflections.rb:66:in `constantize'
  search_cop (1.0.3) lib/search_cop_grammar/attributes.rb:70:in `klass_for'
  search_cop (1.0.3) lib/search_cop_grammar/attributes.rb:81:in `attribute_for'
  search_cop (1.0.3) lib/search_cop_grammar/attributes.rb:55:in `block in attributes'
  search_cop (1.0.3) lib/search_cop_grammar/attributes.rb:55:in `attributes'
  search_cop (1.0.3) lib/search_cop_grammar/attributes.rb:47:in `compatible?'
  search_cop (1.0.3) lib/search_cop_grammar.rb:87:in `block in evaluate'
  search_cop (1.0.3) lib/search_cop_grammar.rb:87:in `evaluate'
  search_cop (1.0.3) lib/search_cop_grammar.rb:14:in `evaluate'
  search_cop (1.0.3) lib/search_cop/grammar_parser.rb:18:in `parse'
  search_cop (1.0.3) lib/search_cop.rb:25:in `parse'
  search_cop (1.0.3) lib/search_cop/query_builder.rb:10:in `initialize'
  search_cop (1.0.3) lib/search_cop.rb:61:in `unsafe_search_cop'
  search_cop (1.0.3) lib/search_cop.rb:53:in `search_cop'
  search_cop (1.0.3) lib/search_cop.rb:44:in `block in search_scope'

My model code is

  include SearchCop
  search_scope :searching do
    attributes :name, :name_kana, :domain_name, :address
    attributes setting: ['account_settings.catch_copy', 'account_settings.formal_name']
    aliases account_settings: :setting
  end

  has_one :setting, dependent: :destroy

And I changed aliases to aliases account_settings: Account::Setting.
But error is NameError - uninitialized constant AccountSetting:.

Ability to validate search string without making query

Is there a way to validate a search string/query for a specific search scope?

For example if I have a search_scope of the following:

# my_model.rb MyModel
search_scope :search do
  attributes :value # value is an integer attribute type (not string)
  options :value, default: true
end

It would be nice to be able to call something like MyModel.validate_search("value:Foo") which would not actually make a query to the database but either raise an error (like theIncompatibleDatatype error) or return a boolean value.

This would allow us to validate/verify queries before being run and handle errors rather than silently failing when calling search("value:Foo") as it does now. The unsafe_search method is nice, but if there was a way to separate validation from querying that would be great.

My primary reason for this request is to better handle errors when using custom filters in the jsonapi_resources gem which has a custom verify method.

Ability to apply Custom Operators or Options to entire project

Is is possible to add Custom Operators or Options to every search in the entire project?

For example

# config/initializers/search_cop.rb

SearchCop.defaults do

  generator :like_string do |column_name, raw_value|
    pattern = quote("%#{raw_value}%")
    "#{column_name} LIKE #{pattern}"
  end

  options :all, :type => :fulltext, default: true

end

Since its probably 2 seperate works to add generators and options. Its of a much higher importance to add this functionality for the generators

【Question】When use colon(::) for class name.

Hi there.

  • Rails 4.1.2
  • attr_searchable 0.0.4

my setting is below..

models

  • Account
  • Setting

Account

class Account < ActiveRecord::Base
  attr_searchable :name
  attr_searchable setting: ["account_settings.formal_name"]
  has_one :setting
end

Setting

database tablename is "account_settings"

class Account::Setting < ActiveRecord::Base

end

Directory structure is ...

  • app/models
    • account
      • setting.rb
    • account.rb

I tried. but NameError happened.

uninitialized constant AccountSetting

How do I write to model setting?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.