Drying up Models for Solr Sunspot searchable with Concerns - sorting

I want to dry up my models; but Sunspot only allows one(1) "searchable" block in a model. making 2 just ignores the second one. Failed Example below:
digital.rb
class Digital < ActiveRecord::Base
include BaseConcerns
belongs_to :digitable, polymorphic: true
# IGNORES THIS
searchable do
string :url
text :url
string :remark
text :remark
end
end
base_concerns.rb
module BaseConcerns extend ActiveSupport::Concern
included do
before_create{
self.id = UUIDTools::UUID.timestamp_create().to_s.downcase if id.blank?
}
self.primary_key = 'id'
# DOES THIS
searchable do
text :id
time :created_at
time :updated_at
end
end
end
I want to DRY up my models using some concern and want some similarly named columns to be searchable; while others I can just specify; how can I do this?

Related

How to verify if an embedded field changed on before_save?

I am running Ruby 2.1 and Mongoid 5.0 (no Rails).
I want to track on a before_save callback whether or not an embedded field has changed.
I can use the document.attribute_changed? or document.changed methods to check normal fields, but somehow these don't work on relations (embed_one, has_one, etc).
Is there a way of detecting these changes before saving the document?
My model is something like this
class Company
include Mongoid::Document
include Mongoid::Attributes::Dynamic
field :name, type: String
#...
embeds_one :address, class_name: 'Address', inverse_of: :address
#...
before_save :activate_flags
def activate_flags
if self.changes.include? 'address'
#self.changes never includes "address"
end
if self.address_changed?
#This throws an exception
end
end
One example of how I save my document is:
#...
company.address = AddressUtilities.parse address
company.save
#After this, the callback is triggered, but self.changes is empty...
#...
I have read the documentation and Google the hell out of it, but I can't find a solution?
I have found this gem, but it's old and doesn't work with the newer versions of Mongoid. I want to check if there is another way of doing it before considering on trying to fix/pull request the gem...
Adding these two methods to your Model and calling get_embedded_document_changes should provide you an hash with the changes to all its embedded documents:
def get_embedded_document_changes
data = {}
relations.each do |name, relation|
next unless [:embeds_one, :embeds_many].include? relation.macro.to_sym
# only if changes are present
child = send(name.to_sym)
next unless child
next if child.previous_changes.empty?
child_data = get_previous_changes_for_model(child)
data[name] = child_data
end
data
end
def get_previous_changes_for_model(model)
data = {}
model.previous_changes.each do |key, change|
data[key] = {:from => change[0], :to => change[1]}
end
data
end
[ source: https://gist.github.com/derickbailey/1049304 ]

How to search the associated model values using 'dusen' gem

In rails 4.0.2, I am trying to use a search plugin called dusen. Using this, I can search same model's values but I am not able to search other(associated) model values. How can I achieve this for single association(has_one / belongs_to) & multi association(has_many) model values?
Reference link:
https://github.com/makandra/dusen
Gem which I am using is dusen (0.4.10)
In controller,
#query = params[:query] || ""
Contact.search(#query)
In model,
belongs_to :city, :class_name=>"City"
search_syntax do
search_by :text do |scope, phrases|
columns = [:name, :contact_number, :email]
scope.where_like(columns => phrases)
end
end
Here, It will search only :name, :contact_number, :email fields, if i try to add below piece of code then it will show an error like undefined method 'search_text' for #<Dusen::Description:0xb438a248>
search_text do
[city.name]
end
Please suggest a solution for this issue.
Assuming your model name is 'User', you'd set it up as follows:
# User.rb
belongs_to :city, :class_name=>"City"
search_syntax do
search_by :text do |scope, phrases|
# namespaced fields to search by.
columns = ["users.name", "users.contact_number", "users.email", "cities.name"]
# specify association to City in scope.
scope.joins(:city).where_like(columns => phrases)
end
end
I hope this helps!

Sequel - Query Many to Many Associations

I am having issues constructing the proper models, associations, and query for the following scenario and then returning results as JSON using Sequel with Ruby.
The database structure___
You can create a list of books. Each library contains books. Defined by the following:
db.create_table(:books) do
primary_key :id
String :name
String :author
DateTime :created
end
db.create_table(:libraries) do
primary_key :id
String :name
String :city
String :state
DateTime :created
end
db.create_table(:libraries_books) do
Integer :library_id
Integer :book_id
primary_key [:library_id, :book_id]
end
class Library < Sequel::Model(:libraries)
many_to_many :libraries_books, :left_key=>:library_id, :right_key=>:book_id, :join_table=>:libraries_books
one_to_many :libraries_books, :key=>:library_id
end
class LibraryBook < Sequel::Model(:libraries_books)
many_to_one :libraries
many_to_one :books
end
I am trying to determine the correct way to access all the book names for a given library. I initially tried to follow the Sequel Associations guide but was not able to figure out how I could use LibraryBook with associations to get all the books for a library and join on the Book model to get the proper columns.
After getting stuck with some of the methods described, I attempted to create my own query as such:
LibraryBook.select(:books.*)
.join_table(:inner, :libraries, :id => :library_id)
.join_table(:inner, :books, :id => :book_id)
.where(:library_id => 1)
Which seems to get me partially there. However, when I use the serialization extension, I get an error when the results are being converted:
undefined method `book_id' for #<LibraryGame:0x007fa9e904b470>
Any insight into that can be provided would be very helpful!
Try the following:
db.create_table(:books) do
primary_key :id
String :name
String :author
DateTime :created
end
db.create_table(:libraries) do
primary_key :id
String :name
String :city
String :state
DateTime :created
end
db.create_table(:books_libraries) do
foreign_key :library_id, :libraries, key: :id
foreign_key :book_id, :books, key: :id, index: true
primary_key [:library_id, :book_id]
end
class Library < Sequel::Model
many_to_many :books
end
class Book < Sequel::Model
many_to_many :libraries
end
Note renaming the libraries_books table to books_libraries and the use of the foreign_key directive for referential integrity. Conventions should allow things to just work.
Library[7].books # returns all books for library '7'
Or alternatively:
Book.where(libraries: Library[7])
Or multiple libraries:
Book.where(libraries: Library.where(id: [3,7,9]))
If sequel is not able to do the inflection for Library/Libraries then you may need to add your own inflection rule, eg:
Sequel.inflections do |inflect|
inflect.irregular 'Library', 'Libraries'
end

Mongoid model with hardcoded data

I have a mongoid model
class MyMongoidModel
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
field :data_id, :type => Integer
has_and_belongs_to_many :the_other_model, :class_name => 'class_name_model'
has_many :model2
def self.all
[
#.... the hardcoded data that will never be changed
]
end
end
it's used by the other model and it uses them as well. However, it contains the data that won't be changed for a very long time, let's say, at all. Thus, I don't want to retrieve it from db, I want it to be hardcoded and, at the same time, I want it acts like a normal mongoid model. Using caching is not what I'm looking for.
I hope you understand what I mean.
How do accomplish it?
There's a great gem called active_hash that provides this functionality for ActiveRecord: defining a fixed set of data as models you can reference/relate to normal models, but have it defined in code and loaded in memory (not stored/retrieved from DB).
https://github.com/zilkey/active_hash
Interestingly, since Mongoid and ActiveRecord both share common ActiveModel basis, you may be able to use active_hash with a Mongoid document now.
For example:
class Country < ActiveHash::Base
self.data = [
{:id => 1, :name => "US"},
{:id => 2, :name => "Canada"}
]
end
class Order
include Mongoid::Document
include Mongoid::Timestamps
has_one :country
end

Rails nested form on many-to-many: how to prevent duplicates?

I've setup a nested form in my rails 3.2.3 app, it's working fine, my models are:
class Recipe < ActiveRecord::Base
attr_accessible :title, :description, :excerpt, :date, :ingredient_lines_attributes
has_and_belongs_to_many :ingredient_lines
accepts_nested_attributes_for :ingredient_lines
end
and:
class IngredientLine < ActiveRecord::Base
attr_accessible :ingredient_id, :measurement_unit_id, :quantity
has_and_belongs_to_many :recipes
belongs_to :measurement_unit
belongs_to :ingredient
end
As above, a Recipe can have multiple IngredientLines and vice versa.
What I'm trying to avoid is record duplication on IngredienLine table.
For example imagine that for recipe_1 an IngredientLine with {"measurement_unit_id" => 1, "ingredient_id" => 1, "quantity" => 3.5} is associated, if for recipe_5 the IngredientLine child form is compiled by the user with the same values, I don't want a new record on IngredientLine table, but only a new association record in the join table ingredient_lines_recipes.
Note that currently I dont't have any IngredientLine controller as saving and updating IngredientLines is handled by nested form routines. Even my Recipe controller is plain and standard:
class RecipesController < ApplicationController
respond_to :html
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(params[:recipe])
flash[:notice] = 'Recipe saved.' if #recipe.save
respond_with(#recipe)
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.destroy
respond_with(:recipes)
end
def edit
respond_with(#recipe = Recipe.find(params[:id]))
end
def update
#recipe = Recipe.find(params[:id])
flash[:notice] = 'Recipe updated.' if #recipe.update_attributes(params[:recipe])
respond_with(#recipe)
end
end
My guess is that should be enough to override the standard create behavior for IngredientLine with find_or_create, but I don't know how to achieve it.
But there's another important point to take care, imagine the edit of a child form where some IngredientLines are present, if I add another IngredientLine, which is already stored in IngredientLine table, rails of course should not write anything on IngredientLine table, but should also distinguish between child records already associated to the parent, and the new child record for which needs to create the relation, writing a new record on the join table.
Thanks!
in Recipe model redefine method
def ingredient_lines_attributes=(attributes)
self.ingredient_lines << IngredientLine.where(attributes).first_or_initialize
end
Old question but I had the same problem. Forgot to add :id to white list with rails 4 strong_parameters.
For example:
widgets_controller.rb
def widget_params
params.require(:widget).permit(:name, :foos_attributes => [:id, :name, :_destroy],)
end
widget.rb
class Widget < ActiveRecord::Base
has_many :foos, dependent: :destroy
accepts_nested_attributes_for :foos, allow_destroy: true
end
foo.rb
class Foo < ActiveRecord::Base
belongs_to :widget
end
I have run into a similar situation and found inspiration in this answer. In short, I don't worry about the duplication of nested models until save time.
Translated to your example, I added autosave_associated_records_for_ingredient_lines to Recipe. It iterates through ingredient_lines and performs a find_or_create as your intuition said. If ingredient_lines are complex, Yuri's first_or_initialize approach may be cleaner.
I believe this has the behavior you're looking for: nested models are never duplicated, but editing one causes a new record rather than updating a shared one. There is the strong possibility of orphaned ingredient_lines but if that's a serious concern you could choose to update if that model has only one recipe with an id that matches the current one.

Resources