How can I set virtual attributes in the mongoid search gem - ruby-on-rails

I am having a hard time with the mongoid_search gem on a few fronts and I wonder if it is working properly. As per the instructions, I am trying to create virtual attributes so that I can search on custom keywords that I create. Here are the instructions from the web page.
You can also search in "virtual" fields by defining them as methods. This can be useful when you have a method with dynamic fields (i.e. variable schema)
class ModelWithDynamicFields
...
search_in :search_data
def search_data
# concatenate all String fields' values
self.attributes.select{|k,v| v.is_a?(String) }.values.join(' ')
end
end
Mongoid_search will run the method before save and use it's output to populate the _keywords field.
I've found that this is not the case and Im trying to find outif I am doing something wrong or if there may be a problem with the gem.
Here is my code.
class User
search_in :search_data
def search_data
["#{self.attributes["email_address"]}", "#{self.attributes["first_name"]}", "#{self.attributes["last_name"]}"]
end
What I want the output to be in the _keywords array is the output of my virtual search_data method which should be this
[allanj#gmail.com allan jones]
This should be the EXACT keywords array that I want yet, the gem keeps ignoring the output of this code and goes to the default behavior. Is there something I can do to fix this?

Related

Is there a more idiomatic way to update an ActiveRecord attribute hash value?

Given a person ActiveRecord instance: person.phones #=> {home: '00123', office: '+1-45'}
Is there a more Ruby/Rails idiomatic way to do the following:
person_phones = person.phones
person_phones[:home] = person_phones[:home].sub('001', '+1')
person.update_column :phones, person_phones
The example data is irrelevant.
I only want to sub one specific hash key value and the new hash to be saved in the database. I was wondering if there was a way to do this just calling person.phones once, and not multiple times
Without changing much behaviour:
person.phones[:home].sub!('001', '+1')
person.save
There are a few important differences here:
You modify the string object by using sub! instead of sub. Meaning that all other variables/objects that hold a reference to the string will also change.
I'm using save instead of update_column. This means callbacks will not be skipped and all changes are saved instead of only the phones attribute.
From the comment I make out you're looking for a one liner, which isn't mutch different from the above:
person.tap { |person| person.phones[:home].sub!('001', '+1') }.save
You can use the before_validation callback on your model.
Like this:
class Phone < ApplicationRecord
validates :home, US_PHONE_REGEX
before_validation :normalize_number
private
def normalize_number
home.gsub!(/^001/, '+1')
end
end
Note: I haven't tested this code, it's meant to show an approach only.
If you're looking to normalize also an international number, evaluate if the use of a lib like phony wouldn't make more sense, or the rails lib https://github.com/joost/phony_rails based on it.
EDIT
since the comment clarify you only want to change the values of the hash in one like you can use Ruby's method transform_values!:
phones.transform_values!{|v| v.gsub(/^001/, '+1')}

Override gem method in rails

I want to let users add interests -TV shows in this case-, and to make sure they type a correct tv show, I'm going to search imdb first and let them select one of the returning titles.
I found this gem https://github.com/ariejan/imdb which is doing almost what I need. If I search for "The vampire diaries", it will return it and 200 extra matches.
I went through the gem and I found that he does the querying part here https://github.com/ariejan/imdb/blob/master/lib/imdb/search.rb.
def self.query(query)
open("http://akas.imdb.com/find?q=#{CGI.escape(query)};s=tt")
end
That query basically uses this link http://akas.imdb.com/find?q= and returns everything that can find given the input - movies, tv shows, episodes. Now I found a more advanced query which uses type and some other params. So I could actually return only 4 results in that case instead of 250. All I have to do is to replace that query with http://www.imdb.com/search/title?title=The%20Vampire%20Diaries&title_type=tv_series.
How do I override that search method?
You can re-open the class to override the method:
class Imdb::Search
def self.query(query)
# your custom logic here
end
end
Note that you can call super(query) in your version to get the result of the original.
You can use class_eval and put it in a decorators folder
app/decorators/imdb/search_decorator.rb
class Imdb::Search.class_eval do
def self.query(query)
end
end

Ruby on Rails - Titleize Exceptions

I have a model called Organizations that contains fields for its address. In the model I have the statement before_save { self.address_line_1 = address_line_1.titleize } and just realized this is changing addresses with PO Box to Po Box.
Another example: I also have a standard Users model with first name/last name. Titleize will change a person's first name from TJ to Tj. Or, if their last name is hyphenated it will go from Smith-Jones to Smith Jones.
With the PO box I would know the exception ahead of time, but not for user's names. Is there any way to allow for these exceptions at all while still having the core titlsize functionality?
I would recommend trying to avoid changing the semantics of titlelize, though, to avoid issues later when you might expect it, in another part of the same application, to do what it's really intended to do. Since you're looking for some fairly specialized functionality for titleize I'd create a new, similar method which you could monkey patch into the String class, as above, called something like, abook_titleize (address book titleize):
class String
def abook_titleize
if allow_titleize(self)
titleize
else
# Check for other behaviors, such as if "self" is all consonants
# or if self is found in a predetermined list of acronyms,
# perhaps return self.upcase
self.upcase
end
end
private
def allow_titleize(s)
# Write some code here that determines if you want this string
# to be titleized and return true if so, otherwise false
end
end
Or something like that. You could make this as simple or as elaborate as you wish. If you really want to change titleize bahavior itself (again, I wouldn't recommend), then:
class String
:alias_method :old_titleize, :titleize
def titleize
if allow_titleize(self)
old_titleize
else
...

More complex ActiveRecord object serialization

I have a model and in that model I'm generating a more complex field than I've done before. I've serialized hashes and arrays, but this field is the result of Gibberish::RSA.generate_keypair ( https://github.com/mdp/gibberish ). Which is more or less a private/public key pair in a ruby wrapper, to my understanding.
Working from the command line, I can do an update_attributes and the result of the generation gets stored in the text field. When doing rake db:seed or creating an instance, this doesn't work, I get a yaml string that indicates several types of Gibberish objects.
How do I do more complex activerecord serialization beyond hashes and arrays? Or how do I approach a greater understanding of what I'm trying to do?
Code:
def generate_keypair
self.update_attributes(:rsakey => Gibberish::RSA.generate_keypair(1024) )
end
which I call on the associated model creation, basic call the Gibberish wrapper
Then the output I get for the field myresource.rsakey
"--- !ruby/object:Gibberish::RSA::KeyPair\nkey:
!ruby/object:OpenSSL::PKey::RSA {}\ncipher:
!ruby/object:OpenSSL::Cipher::Cipher {}\n"
Updating the attributes works from the rails command line, but not while seeding or creating. Other ways I attempted to add serialize so far have completely ruined the process or the created instances.
EDIT: solved bluntly by just calling 'to_s' on the result of the keypair generation method, which just saves it as a text field that 'works for now' until it needs to be more elegant.
The underlying issue seems to be that the openssl library doesn't implement YAML dumping:
YAML.dump(OpenSSL::PKey::RSA.generate(1024))
#=> "--- !ruby/object:OpenSSL::PKey::RSA {}"
If you are using rails 3.1 you can define custom serializers, like so
class KeySerializer
def dump(key)
key.to_pem
end
def load(data)
data && OpenSSL::Pkey::RSA.new(data)
end
end
Then in your class you can do
class Foo < ActiveRecord::Base
serialize :key, KeySerializer.new
end

Rename ActiveResource properties

I am consuming JSON data from a third party API, doing a little bit of processing on that data and then sending the models to the client as JSON. The keys for the incoming data are not named very well. Some of them are acronyms, some just seem to be random characters. For example:
{
aikd: "some value"
lrdf: 1 // I guess this is the ID
}
I am creating a rails ActiveResource model to wrap this resource, but would not like to access these properties through model.lrdf as its not obvious what lrdf really is! Instead, I would like some way to alias these properties to another property that is named better. Something so that I can say model.id = 1 and have that automatically set lrdf to 1 or puts model.id and have that automatically return 1. Also, when I call model.to_json to send the model to the client, I dont want my javascript to have to understand these odd naming conventions.
I tried
alias id lrdf
but that gave me an error saying method lrdf did not exist.
The other option is to just wrap the properties:
def id
lrdf
end
This works, but when I call model.to_json, I see lrdf as the keys again.
Has anyone done anything like this before? What do you recommend?
Have you tried with some before_save magic? Maybe you could define attr_accessible :ldrf, and then, in your before_save filter, assign ldrf to your id field. Haven't tried it, but I think it should works.
attr_accessible :ldrf
before_save :map_attributes
protected
def map_attributes
{:ldrf=>:id}.each do |key, value|
self.send("#{value}=", self.send(key))
end
end
Let me know!
You could try creating a formatter module based on ActiveResource::Formats::JsonFormat and override decode(). If you had to update the data, you'd have to override encode() also. Look at your local gems/activeresource-N.N.N/lib/active_resource/formats/json_format.rb to see what the original json formatter does.
If your model's name is Model and your formatter is CleanupFormatter, just do Model.format = CleanupFormatter.
module CleanupFormatter
include ::ActiveResource::Formats::JsonFormat
extend self
# Set a constant for the mapping.
# I'm pretty sure these should be strings. If not, try symbols.
MAP = [['lrdf', 'id']]
def decode(json)
orig_hash = super
new_hash = {}
MAP.each {|old_name, new_name| new_hash[new_name] = orig_hash.delete(old_name) }
# Comment the next line if you don't want to carry over fields missing from MAP
new_hash.merge!(orig_hash)
new_hash
end
end
This doesn't involve aliasing as you asked, but I think it helps to isolate the gibberish names from your model, which would never have to know those original names existed. And "to_json" will display the readable names.

Resources