Table column names to array in ruby - ruby

In Rails I would simply use:
MyModel.column_names
to access the tables column names, but I am enduring my first fumblings with Sinatra (in order to better learn Ruby) and need to get the column names of a table into an array.
Previously, I have iterated over the params hash to extract key, value pairs:
params.each do |key, value|
#manipulate keys and values
end
and know that using:
#mm = MyModel.new
#mm.each do ....
leads to an undefined method 'each' error.
logger.info #mm.inspect
logs something like:
#<MyModel #id=nil #start=nil #end=nil #yada=nil #created_at=nil #updated_at=nil #foreign_id=nil>
So, how do I get those blasted column names I can see so clearly into an array?
Thanks...
UPDATE
Thanks to Doon for pointing me in the right direction with Datamapper properties.
I ended up making a private method column_names:
def self.column_names
self.properties.collect {|column| column.name.to_s }
end
which works a treat

Are you looking for properties ?
http://rubydoc.info/gems/dm-core/1.1.0/DataMapper/Model/Property

For any Ruby object you have the methods instance_variables that returns the array of that object’s attributes. Is that too generic for you? I’m not familiar with DataMapper, there may be a specific method to do the equivalent of column_names in ActiveRecord.

Related

How to access Sequel result with dot operator in ruby?

I am newbie to Sequel and ruby and I have one thing need your help.
In a word, I can't access database query result with dot operator.
I am using sequel adapter in padrino ruby project.
For example,
persons = Person.all
persons.each do |p|
puts p.name . # this output correct person name, as 'john'
end
But if I do some query
persons = Person.where(:age=>20)
persons.each do |p|
puts p.name . # this line cause error
end
I compared their data types and there are different each other.
puts persons # in first case - array
#<Gig:0x007fbdb6d64ef0>
#<Gig:0x007fbdb6d64838>
#<Gig:0x007fbdb6d641f8>
puts persons # in second case - object
#<Sequel::Postgres::Dataset:0x007fbdbc614898>
So I tried to change result to hash array in second case to access fields with dot operator
persons_hash= persons.collect do |p|
ro.to_hash
end
In this case, I was able to access user name with person[0][:name], but I couldn't access with dot operator.
So I want to know how should I have to do to access Sequel query result using dot operator.
Thanks :)
persons.each do |p|
puts p.name . # this line cause error
end
What exact error are you getting here? I'm guessing an undefined method error? Seems you may be familiar with ActiveRecord syntax. I have not used sequel myself, but it is a bit different from AR. According to their docs, you would do something like this
persons.map(:name) # => ['Harry', 'Steve', 'Israel', ...]
The all method returns an array of hashes, where each hash corresponds to a record.
For your above example, I would try the following:
persons.each do |p|
puts p[:name] . # here we are now accessing the name hash key instead of trying to access the name method.
end
You want to access the name key of each hash being iterated over. Because you are iterating through an array OF hashes. This is why you could access that data with person[0][:name]. You were calling the 0th item of the persona array and accessing its 'name' hash key.

Handling Arrays of custom objects in Mongoid

I'm having some issues with creating a Mongoid document that includes an array of custom objects.
I my particular case I intend to store an array of BaseDevice objects. The BaseDevice class is already mongified and serializes from/to a plain hash using Mongoid's custom fields support. This works pretty well on single object.
For storing an array of BaseDevice, I've created the following class:
class BaseDeviceArray < Array
class << self
def demongoize(object)
object ? object.map{ |obj| BaseDevice.demongoize obj } : new
end
def evolve(object)
case
when BaseDeviceArray then object.mongoize
else object
end
end
end
def mongoize
self.map(&:mongoize)
end
end
The mongoid document looks like this
class MongoPeriph
include Mongoid::Document
field :devices, type: BaseDeviceArray
end
Let's say some_devices is an array containing two BaseDevice instances.
What happens is the following: when I assign some_devices to the devices fields of the MongoPeriph instance that works correctly.
mp = MongoPeriph.create
mp.devices = some_devices
mp.devices # => [#<BaseDevice:0x007fa84bac0080>,#<BaseDevice:0x007fa84baaff78>]
When try to send push, pop, shift, unshift methods to the devices field within the mongoid document, nothing seems to happen. The changes are not appearing on the mp object. Also when referencing one of the objects by index (i.e. when calling mp.devices[0].some_method) the world does not change.
When popping objects from the array, on every pop a new object is given. This is expected as the deserializer is instantiating a new BaseDevice object for every pop, but the internal field is not updated i.e. the object stays there and one can pop endlessly.
Using the BaseDeviceArray separate from a mongoid document works as expected:
foo = BaseDeviceArray.new
foo << BaseDevice.new
results in an array with a BaseDevice object.
Btw. I found one other approach to this on the net. It is a more generalized way of implementing what I need, but it monkey-patches Mongoid. Something I try to avoid. Moreover that solution seems to have the same issue my approach has.
Issue in your code is that you have #mongoize (instance) method but you actually need ::mongoize (class) method. You never create an instance of BaseDeviceArray thus instance methods are useless.
Here's an example of how I did the ::mongoize method where I actually have in mongo a Hash with a single key with array value. Also I wanted to make the resulting array into a hash with ids as keys for easier lookup.
def demongoize(hash)
return validate_hash(hash)["TestRecord"].each_with_object({}) do |r, m|
rec = TestRecord.new(r)
m[rec.case_id] = rec
end
end
def mongoize(object)
case object
when Array then {"TestRecord" => object.map(&:mongoize)}
when Hash
if object["TestRecord"]
# this gets actually called when doing TestRun.new(hash)
mongoize(demongoize(object))
else
{"TestRecord" => object.values.map(&:mongoize)}
end
else raise("dunno how to convert #{object.class} into records JSON")
end
end
def evolve(object)
# can't see how we want to process this here yet
# docs.mongodb.com/ruby-driver/master/tutorials/6.0.0/mongoid-documents
object
end
I guess op task done long ago but thought somebody may find it useful.

In Ruby, group_by where I know there's only 1 element per group

I have a CSV file where one column is a primary key. When I do this:
CSV.read(ARGV[0], headers: true).group_by {|r| r['myKey']}
I get a hash table from key to a list of rows, where the list is always length 1.
Is there a version of group_by which asserts that there's only a single value per key, and creates a hash from key to that single value?
Failing that, is there something like .first which asserts that there's exactly one element in the array/enumerable? I like my scripts to fail when my assumptions are wrong, rather than silently return the wrong thing.
If you use Rails you can use index_by method.
If you know the values r['myKey'] are unique, there's no point in using group_by. As I understand the question, you could do this:
rows = CSV.read(ARGV[0], headers: true)
Hash[rows.map { |r| r['myKey'] }.zip(rows)]
In Ruby 2.0+ the second row could be written:
rows.map { |r| r['myKey'] }.zip(rows).to_h
No. I don't believe there is. But you can solve your problem with each_with_object like so:
CSV.
read(ARGV[0], headers: true).
each_with_object({}) do |r, hash|
key = r['myKey']
value = r
hash[key] = value
end
It's a shame Ruby doesn't have this. Here's what I decided to go on, based on Humza's answer:
module Enumerable
def group_by_uniq
each_with_object({}) do |value, hash|
key = yield value
raise "Multiple values for key \"{key}\"!" unless ! hash.key?(key)
hash[key] = value
end
end
end
If you use your code in you first example you can run this code to check that all hashes are of length 1:
raise 'multiple entries per key!' unless my_hash.values.any?{|val| val.size!=1}
IF you can get the keys into an array you can check that they do not iclude duplicates by:
raise 'multiple entries per key!' unless my_keys.uniq.size == my_keys.size

Ruby on Rails, create an instance variable from another instance variable's property

I think this is an easy question but I can't seem to wrap my head around it to solve it. I want to create an instance variable from a property of another instance variable.
For example:
#posts = Post.find(:all, :conditions => ['day > ?', Date.today], :order => 'day')
I want to get another instance variable that contains all of the dates from the #posts. Is there a way of quickly getting it without having to create a helper/function?
Thanks!
#posts.map(&:day)
which is a shorthand for
#posts.map do |post|
post.day
end
The result is an array of the value of each post.day
What Wayne has is a appropriate. I prefer collect instead of map, but they are equivalent.
#dates = #posts.collect{|post| post.day}.uniq
Notice, uniq at the end will give you the unique values.

Ruby arrays and non-incremental indexes

I have an array in ruby, and I am setting the index to id of object like below.
My first question is:
This code works:
#array = Array.new(#objects.size)
for i in 0...#objects.size
#array[i] = #objects[i].value
end
but when I do:
#array[#objects[i].id] = #objects[i].value
it says:
undefined method [] for nil::NilClass
I tried putting 100 or 1000 instead of i to make sure it's not about "index out of range", but those worked, I tried converting id to int by using to_i even though it should already be an int, but it still doesn't work. I don't get it.
My second question is:
If I get to make the ids work, does saying Array.new(#objects.size) become usless?
I am not using indexes 0 to size but IDs, so what is happening? Is it initializing indexes 0...size to nil or is it just creating a space for up to x objects?
EDIT:
So I've been told it is better to use Hash for this, and I agree, But I still seem to have the same error in the same situation (just changed Array.new(#objects.size)toHash.new)
Thats not how Arrays work in Ruby. You can however use a hash to do this, and look them up using the method you want:
#lookup_hash = Hash.new
for i in 0...#objects.size
#lookup_hash[#objects[i].id] = #objects[i].value
end
Now you can do:
#lookup_hash[#some_object.id]
And it will return that object's value as you have stored it.
Additional Info
You could also rewrite your loop like this, since you dont need the index anymore:
#lookup_hash = Hash.new
#objects.each do |obj|
#lookup_hash[obj.id] = obj.value
end
A little bit more readable in my opinion.
Your're trying to use an array like a hash. Try this:
Hash[#objects.map{|o| [o.id, o.value] }]
Take a look at the Array and Hash documentations.
#array = #objects.map { |obj| obj.value }
You can, but you don't need to specify the size when creating an array. Anyway, try to use the functional capabilities of Ruby (map, select, inject) instead of C-like imperative loops.
You could use map to do this in a rubyish way:
#array = #objects.map { |o| o.value }

Resources