Searchkick / Rails working example with Mongoid - ruby-on-rails

I can't figure how to properly hook a view / form for searching through my docs with elasticsearch + mongoid.
My Searchkick config is working – I get the results inside the rails console. But I don't know exactly where to put things in what order in the controller/model/view to make it work. From the rails console I get the search results, docs are properly mapped and everything is fine.
I can't find a working example of an simple search form with searchkick including an working example of how a model, controller and a view should look like.
Anybody got an working example to checkout with Rails 4.1rc1 / Mongoid4 / Elasticsearch 1.0.1?

I've got rails 4.0.2, Mongoid4, and ElasticSearch >= 1
Anyway, this works for us in the controller:
class CropSearchesController < ApplicationController
def search
query = params[:q].to_s
#crops = Crop.search(query,
limit: 25,
partial: true,
misspellings: {distance: 2},
fields: ['name^20',
'common_names^10',
'binomial_name^10',
'description'],
boost_by: [:guides_count]
)
if query.empty?
#crops = Crop.search('*', limit: 25, boost_by: [:guides_count])
end
# Use the crop results to look-up guides
crop_ids = #crops.map { |crop| crop.id }
#guides = Guide.search('*', where: {crop_id: crop_ids})
render :show
end
end
And this is our model:
class Crop
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Slug
searchkick
field :guides_count, type: Fixnum, default: 0
field :name
field :common_names, type: Array
validates_presence_of :name
field :binomial_name
field :description
belongs_to :crop_data_source
field :sun_requirements
field :sowing_method
field :spread, type: Integer
field :row_spacing, type: Integer
field :height, type: Integer
embeds_many :pictures, cascade_callbacks: true, as: :photographic
accepts_nested_attributes_for :pictures
def search_data
as_json only: [:name, :common_names, :binomial_name, :description, :guides_count]
end
slug :name
end
And then you just accesses the #crops in the view like you would normally.
You can have a look at our source code on github: https://github.com/openfarmcc/OpenFarm
(Just stumbled across this question while looking for how to do the ordering, figured it out, but thought I'd just copy paste this here in case anyone finds this useful).

Related

MongoDB validation failed from has_and_belongs_to_many relation

I am building a simple script to populate a MongoDB database. This is my first time using a NoSQL DB and I feel I may be thinking about this problem from a SQL DB standpoint.
The basis of this script is to populate a database that holds a few collections that relate to one another. But when I run my script, I see an invalid errors when building/saving the documents.
I have three collections; Book, Author, and Style, with the following relationships.
A Book has many Authors
An Author has many Books
An Author has many Styles
A Style has many Authors
The models are defined as followed:
# Book Model
class Book
include Mongoid::Document
include Mongoid::Timestamps
field :title, type: String
validates :title, presence: true
has_and_belongs_to_many :authors
index({ title: 'text' })
end
# Author Model
class Author
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
validates :name, presence: true
has_and_belongs_to_many :books
has_and_belongs_to_many :styles
index({ name: 1 }, { unique: true })
end
# Style Model
class Style
include Mongoid::Document
include Mongoid::Timestamps
field :type, type: String
validates :type, presence: true
has_and_belongs_to_many :authors
index({ type: 1 }, { unique: true, name: "type_index" })
end
And then this is my script:
# script.rb
book = Book.new
book.title = "Good Omens"
['Neil Gaiman', 'Terry Pratchett'].each do |author_name|
author = Author.find_by(name: author_name)
if author.nil?
author = Author.new(name: author_name)
end
# a list of writing styles this author can have
# pretend that there's a list of styles per author
literary_styles.each do |style_name|
style = Style.find_by(type: style_name)
if style.nil?
author.styles.build(Style.new(type: style_name))
else
unless author.styles.include? style.id
author.styles << style
end
end
end
author.valid? #=> false
author.errors #=> #messages={:styles=>["is invalid"]}
book.author.build(book.attributes)
book.save
end
The Book document is created, but the Author and Style do not persist due to the invalid Style validation error. I wish I could see exactly what is causing the validations to fail, but the messaging is very vague. I suspect it is coming from some built in validation from the has_and_belongs_to_many relation between Author and Style but I can't put my finger on it.
What I find interesting is that the Book document has an author_ids property which is populated with id's but when I jump into the console, there are no authors that can be pulled up or tied to the Book.
Happy to give more info if needed.
I think I'd need to know a bit more to definitively tell you what the issue is -- What version of Mongoid are you using? Are you running this script on an empty database, or do the Author and Style documents already exist? What do those look like?
That being said, I do see a couple of bugs in your script, though I'm not sure if they're causing your problem:
if style.nil?
author.styles.build(Style.new(type: style_name))
else
...
I believe the line inside the if statement will throw an error. Instead, it should say author.styles.build(type: style_name).
unless author.styles.include? style.id
author.styles << style
end
The expression after unless is always going to evaluate to false because author.styles is an array of Style objects, not style ids. Instead, it should probably say author.style_ids.include? style.id or author.styles.include? style
Let me know if that helps! I'm happy to debug some more if you provide me with the extra info I requested.

How to create index of an instance method in elasticsearch-rails?

What I want to do
https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model
Using this gem, I wanna create indexes of User model, including output of method named full_name. User has columns of id, first_name, last_name.
class User
...
def full_name
first_name + last_name
end
...
end
What I did
module UserSearchable
extend ActiveSupport::Concern
included do
include Searchable
settings index: {
...
}
mappings do
indexes :id, type: :integer
indexes :first_name, type: text
indexes :last_name, type: text
indexes :full_name, type: text, as: 'full_name'
end
def as_indexed_json(options={})
as_json(
methods: [:full_name]
)
end
end
end
I referred to this question.
Index the results of a method in ElasticSearch (Tire + ActiveRecord)
But this doesn't work, ending up with not containing full_name index in the response.
I'm new to elasticsearch and elasticsearch-rails.
How can I fix this?
Sorry, I just forgot to reload changes in the code!
And It works without as: 'full_name' in current version of the gem.

ActiveAdmin Globalize create index filters

I'm using Globalize and ActiveAdmin, and I've now installed a gem from a fork of ActiveAdminGlobalize
Everything that is described in the readme is working, but I'd like to add a filter to the Active Admin Index.
So, for the model stuff.rb
class Stuff < ApplicationRecord
translates :name
active_admin_translates :name do
validates_presence_of :name
end
end
And the class in app/admin/stuff.rb
ActiveAdmin.register Stuff do
index do
translation_status
column :name
end
filter :name
end
How do I make the filter :name to work?
Thanks
I'm using the regular ActiveAdmin gem and, after scratching my head for quite some time, found that the following works:
filter :translations_name_contains, as: :string
Of course you can change name with any other attributes you have translated with Globalize
filter :translations_title_contains, as: :string
To tie everything up nicely, I like to customize the label to avoid the default one AA creates:
filter :translations_title_contains, as: :string, label: "Search", placeholder: "Search page title..."
Hope this helps, thanks!

How can you update/destroy one embed document that is inside of all documents on a collection

My application model allows Patients to have CustomFields. All patients have the same customs fields. Customs fields are embedded in the Patient document. I should be able to add, update and remove custom fields and such actions are extended to all patients.
class Patient
include Mongoid::Document
embeds_many :custom_fields, as: :customizable_field
def self.add_custom_field_to_all_patients(custom_field)
Patient.all.add_to_set(:custom_fields, custom_field.as_document)
end
def self.update_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.update_custom_field(custom_field) }
end
def update_custom_field(custom_field)
self.custom_fields.find(custom_field).update_attributes({ name: custom_field.name, show_on_table: custom_field.show_on_table } )
end
def self.destroy_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.remove_custom_field(custom_field) }
end
def remove_custom_field(custom_field)
self.custom_fields.find(custom_field).destroy
end
end
class CustomField
include Mongoid::Document
field :name, type: String
field :model, type: Symbol
field :value, type: String
field :show_on_table, type: Boolean, default: false
embedded_in :customizable_field, polymorphic: true
end
All pacients have the same customs fields embedded in. Adding a custom field works very well. My doubt is about updating and destroying.
This works, but it is slow. It makes a query for each pacient. Ideally I would just be able to say to MongoDB 'update the document with id: that is embedded in the array *custom_fields* for all documents in the Patient collection'. Idem for destroy.
How can I do this in Mongoid?
I am using Mongoid 3.1.0 & Rails 3.2.12
I don't think there is a way you can do that with a good efficiency with embedded documents.
Maybe you should consider having a referenced relationship between your models, so that you can use the delete_all and update_all methods on the collection.

Generate a ruby class in memory

I have a requirement to convert an ActiveRecord model class into a MongoDB Document class automatically. I am able to do so using a rails generator which will read the attributes of a model and generate the new document.rb.
If a ActiveRecord model class looks like below:
class Project < ActiveRecord::Base
attr_accessible :completed, :end_date, :name, :start_date
end
Then, a generated class confirming to Mongoid's structure will be as below:
class ProjectDocument
field :name, type: String
field :start_date, type: Date
field :end_date, type: Date
field :completed, type: Boolean
field :created_at, type: Time
field :updated_at, type: Time
end
But I don't want to store a different document files, one for each model. I want to be able to generate this document class on the fly, whenever the rails application is started.
Is this possible? Is this approach of generating and using classes from memory advised? I don't have constraints on changes to AR model structure; the document is flexible w.r.t data structure and changed columns will get added automatically.
My first attempt would look something like this:
klass = Project
new_class = Object.const_set(klass.name + "Document", Class.new)
klass.columns.each do |c|
new_class.class_eval do
field c.name.to_sym, type: c.type
end
end
You'll almost certainly have to do something more complicated to set the field type correctly, but this should give you a good starting point.

Resources