on ruby console when I do Resource.all it give me the following:
[<Resource id:'...', name_translated:{"en"=>'vehicle',"fr"=>'véhicule'}> ...]
How do I make a selection such that Resource.find_by_name_translated("vehicle")
This would work, I think it's not the most efficient way though:
#app/models/resource.rb
def self.find_by_english_name(name)
Resource.all.select do |resource|
resource.name_translated['en'] == name
end
end
if you want to be able to find by multiple languages (defaulting to english) with one method, try this:
def self.find_by_name(name, language = 'en')
Resource.all.select do |resource|
resource.name_translated[language] == name
end
end
Since you're using Postgres this can also be written as follows:
def self.find_by_name(name, language = 'en')
Resource.where("name_translated ->> '#{language}' = '#{name}'")
end
I'd use regex query if your db doesn't allow to query by json fields:
Resource.where("name_translated LIKE '#{translated_name}%'")
Related
I have the following Ruby code:
module BigTime
FOO1_MONEY_PIT = 500
FOO2_MONEY_PIT = 501
class LoseMoney
##SiteName = 'FOO1'
#site_num = ##SiteName_MONEY_PIT
def other_unimportant_stuff
whatever
end
end
end
So, what I'm trying to do here is set the SiteName and then use SiteName and combine it with the string _MONEY_PIT so I can access FOO1_MONEY_PIT and store its contents (500 in this case) in #site_num. Of course, the above code doesn't work, but there must be a way I can do this?
Thanks!!
If you want to dynamically get the value of a constant, you can use Module#const_get:
module BigTime
FOO1_MONEY_PIT = 500
FOO2_MONEY_PIT = 501
class LoseMoney
##SiteName = 'FOO1'
#site_num = BigTime.const_get(:"#{##SiteName}_MONEY_PIT")
end
end
Do not, under any circumstance, use Kernel#eval for this. Kernel#eval is extremely dangerous in any context where there is even the slightest possibility that an attacker may be able to control parts of the argument.
For example, if a user can choose the name of the site, and they name their site require 'fileutils'; FileUtils.rm_rf('/'), then Ruby will happily evaluate that code, just like you told it to!
Kernel#eval is very dangerous and you should not get into the habit of just throwing an eval at a problem. It is a very specialized tool that should only be employed when there is no other option (spoiler alert: there almost always is another option), and only after a thorough security review.
Please note that dynamically constructing variable names is already a code smell by itself, regardless of whether you use eval or not. It pretty much always points to a design flaw somewhere. In general, you can almost guaranteed replace the multiple variables with a data structure. E.g. in this case something like this:
module BigTime
MONEY_PITS = {
'FOO1' => 500,
'FOO2' => 501,
}.freeze
class LoseMoney
##SiteName = 'FOO1'
#site_num = MONEY_PITS[##SiteName]
end
end
You can refactor this as to use a Hash for your name lookups, and a getter method to retrieve it for easy testing/validation. For example:
module BigTime
MONEY_PITS = { FOO1: 500, FOO2: 501 }
MONEY_PIT_SUFFIX = '_MONEY_PIT'
class LoseMoney
##site = :FOO1
def initialize
site_name
end
def site_name
#site_name ||= '%d%s' % [MONEY_PITS[##site], MONEY_PIT_SUFFIX]
end
end
end
BigTime::LoseMoney.new.site_name
#=> "500_MONEY_PIT"
I'm new to MongoDB and databases in general. I'm using Ruby and I would like to query against a specific UUID in the database.
The ID is stored as _id and the value is '101b437a-be16-44f6-b0b0-0201cdee6510'
I have the following that usually queries my database:
field = '_id:'
value = 101b437a-be16-44f6-b0b0-0201cdee6510
def query_field(field,value)
query = {#{field}: value}
#result = #mongo_interface.get(query)
expect(#result.count).to be >= 1
puts "Number of matched values: #{#result.count}"
end
def get(param_hash, collection_name = nil)
col_name = (collection_name.nil? || collection_name.empty?) ? #collection : collection_name
#docs = #db[col_name].find(param_hash)
end
When I look within the _id field, I'm assuming it's stored as some sort of binary key and thus isn't found using my search.
Is there some conversion I could/should do to make the query above work?
Thank you.
Using an ODM like Mongoid will ease your pain. Add it to your Gemfile:
gem 'mongoid'
and run bundle install. Make sure you skimmed through the installation guide to add all the necessary configs.
Then include the following line to your model/class, say:
class Product
include Mongoid::Document
...
end
You'll be able to query the records like Product.find(id) right after.
I'm looking to match one value (A sku from a website) to the same value (A sku from lookup.csv) and return a corresponding model (From lookup.csv).
Here's sample data from lookup.csv:
SKU , Model
2520045 , DQ.SUNAA.002
7423599 , DA.MX00.1CC
9547543 , DX.MF01.2BM
Here's my code thus far:
url = "http://www.bestbuy.com/site/acer-aspire-23-touch-screen-all-in-one-intel-core-i5-8gb-memory-2tb-hard-drive-black/2520045.p?id=1219547718151&skuId=2520045"
page = Nokogiri::HTML(open(url))
sku = page.css('span#sku-value').text
#model = match the sku to the sku in lookup.csv and return corresponding model
puts model
I know that I can open the file with
open("lookup.csv", 'r')
but past that, I'm not quite sure how to match/return a corresponding value.
Any help is appreciated!
The suggestion of Aoukar would work but be slow with large data sets.
Here a better solution, read the CSV once, using the CSV gem (no need to reinvent the wheel) and store the data in a hash, after that you can just ask fort the right Model, here a working sample.
I'm using the CSV data in the DATA part of the script here so I don't need the CSV file itself.
require "csv"
lookup = {}
CSV.parse(DATA, col_sep: " , ", headers: true, force_quotes: false, :quote_char => "\x00").each do |row|
lookup.merge! Hash[row['SKU'], row['Model']]
end
lookup #{"2520045"=>"DQ.SUNAA.002", "7423599"=>"DA.MX00.1CC", "9547543"=>"DX.MF01.2BM"}
lookup['2520045'] #"DQ.SUNAA.002"
__END__
_ ,SKU , Model #the first element is to work around a bug in CSV used this way
2520045 , DQ.SUNAA.002
7423599 , DA.MX00.1CC
9547543 , DX.MF01.2BM
you can try this code, but I didn't test it so it might need some modifications, I've written it as a function since it's what I'm used to.
def search(path,key) #path to file, and word to search for
File.open(path,'r') do |file| #open file
file.readlines.each { |line| #read lines array
if line.split(' , ')[0] == key #match the SKU
return line.split(' , ')[1] #return the Model
end
}
end
end
I have gone over the documentation, and I can't find a specific way to go about this. I have already added some dynamic attributes to a model, and I would like to be able to iterate over all of them.
So, for a concrete example:
class Order
include Mongoid::Document
field :status, type: String, default: "pending"
end
And then I do the following:
Order.new(status: "processed", internal_id: "1111")
And later I want to come back and be able to get a list/array of all the dynamic attributes (in this case, "internal_id" is it).
I'm still digging, but I'd love to hear if anyone else has solved this already.
Just include something like this in your model:
module DynamicAttributeSupport
def self.included(base)
base.send :include, InstanceMethods
end
module InstanceMethods
def dynamic_attributes
attributes.keys - _protected_attributes[:default].to_a - fields.keys
end
def static_attributes
fields.keys - dynamic_attributes
end
end
end
and here is a spec to go with it:
require 'spec_helper'
describe "dynamic attributes" do
class DynamicAttributeModel
include Mongoid::Document
include DynamicAttributeSupport
field :defined_field, type: String
end
it "provides dynamic_attribute helper" do
d = DynamicAttributeModel.new(age: 45, defined_field: 'George')
d.dynamic_attributes.should == ['age']
end
it "has static attributes" do
d = DynamicAttributeModel.new(foo: 'bar')
d.static_attributes.should include('defined_field')
d.static_attributes.should_not include('foo')
end
it "allows creation with dynamic attributes" do
d = DynamicAttributeModel.create(age: 99, blood_type: 'A')
d = DynamicAttributeModel.find(d.id)
d.age.should == 99
d.blood_type.should == 'A'
d.dynamic_attributes.should == ['age', 'blood_type']
end
end
this will give you only the dynamic field names for a given record x:
dynamic_attribute_names = x.attributes.keys - x.fields.keys
if you use additional Mongoid features, you need to subtract the fields associated with those features:
e.g. for Mongoid::Versioning :
dynamic_attribute_names = (x.attributes.keys - x.fields.keys) - ['versions']
To get the key/value pairs for only the dynamic attributes:
make sure to clone the result of attributes(), otherwise you modify x !!
attr_hash = x.attributes.clone #### make sure to clone this, otherwise you modify x !!
dyn_attr_hash = attr_hash.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
or in one line:
x.attributes.clone.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
So, what I ended up doing is this. I'm not sure if it's the best way to go about it, but it seems to give me the results I'm looking for.
class Order
def dynamic_attributes
self.attributes.delete_if { |attribute|
self.fields.keys.member? attribute
}
end
end
Attributes appears to be a list of the actual attributes on the object, while fields appears to be a hash of the fields that were predefined. Couldn't exactly find that in the documentation, but I'm going with it for now unless someone else knows of a better way!
try .methods or .instance_variables
Not sure if I liked the clone approach, so I wrote one too. From this you could easily build a hash of the content too. This merely outputs it all the dynamic fields (flat structure)
(d.attributes.keys - d.fields.keys).each {|a| puts "#{a} = #{d[a]}"};
I wasn't able to get any of the above solutions to work (as I didn't want to have to add slabs and slabs of code to each model, and, for some reason, the attributes method does not exist on a model instance, for me. :/), so I decided to write my own helper to do this for me. Please note that this method includes both dynamic and predefined fields.
helpers/mongoid_attribute_helper.rb:
module MongoidAttributeHelper
def self.included(base)
base.extend(AttributeMethods)
end
module AttributeMethods
def get_all_attributes
map = %Q{
function() {
for(var key in this)
{
emit(key, null);
}
}
}
reduce = %Q{
function(key, value) {
return null;
}
}
hashedResults = self.map_reduce(map, reduce).out(inline: true) # Returns an array of Hashes (i.e. {"_id"=>"EmailAddress", "value"=>nil} )
# Build an array of just the "_id"s.
results = Array.new
hashedResults.each do |value|
results << value["_id"]
end
return results
end
end
end
models/user.rb:
class User
include Mongoid::Document
include MongoidAttributeHelper
...
end
Once I've added the aforementioned include (include MongoidAttributeHelper) to each model which I would like to use this method with, I can get a list of all fields using User.get_all_attributes.
Granted, this may not be the most efficient or elegant of methods, but it definitely works. :)
I need to convert an embedded document onto its own collection, so it can be referenced from another collection.
Lets suppose I have a Parent that embeds many Childs.
I was thinking of something along this:
Parent.all.each do |p|
p.childs.all.each do |c|
c.raw_attributes['parent_id'] = p.id
end
p.save! #will save parent and cascade persist all childs onto their own coll
end
Is this an option? Ideally I would run this in a console and I would only change mongoid mappings from embed_* to has_*, so I wouldn't need to change the rest of my code or use another collection as staging.
I think, the code should look more like this (didn't test)
child_coll = Mongoid.database.collection('children')
Parent.all.each do |p|
p.childs.all.each do |c|
c.attributes['parent_id'] = p.id
child_coll.insert c.attributes # save children to separate collection
end
p.childs = nil # remove embedded data
p.save
end
After that, you can change your embeds_many to has_many and (hopefully) it should work well.
too little rep to comment, but I think Sergio's (otherwise very helpful) answer may be outdated. With mongoid 3.0.5 I couldn't use
child_coll = Mongoid.database.collection('children')
but instead used
child_coll = Mongoid.default_session[:children]
which did the trick for me
For me I need to remove the '_id' attribute before inserting otherwise I will get Duplicated key Error.
Here is an updated version of Sergio Tulentsev's approach with Pencilcheck's addition and an update of sbauch's correction.
First, leave the embeds_many/embedded_in statements in place in your models.
Second, run something like this block of code:
child_coll = Mongoid.client(:default).database.collection(:children)
Parent.all.each do |p|
p.childs.all.each do |c|
dup = c.attributes
dup['_id'] = nil
dup['parent_id'] = p.id
child_coll.insert_one dup # save children to separate collection
c.destroy
end
p.childs = nil # remove embedded data
p.save
end
Third, change your embeds_many to has_many and your embedded_in to belongs_to.
Fini.