0

I have a Rails 4.2 application with mongoid in which I'm importing csv files with test results. I can't define all fields in the model because they change from test to test and theres always around 700 of them. I use Dynamic Attributes and importing and displaying works fine.

I'm trying to use attribute_names method to get all attribute names but all I get is those defined in the model. If I don't define anything in the model it comes back with "_id" only. attributes method on the other hand can see attributes in the actual document on the other hand.

>> @results.first.attributes.count
=> 763
>> @results.first.attribute_names
=> ["_id"]

I also tried fields.keys, same problem

>> @results.first.fields.keys
=> ["_id"]

My model at the moment looks like this

class Result
  include Mongoid::Document
  include Mongoid::Attributes::Dynamic

  def self.import(file)
    CSV.foreach(file.path, headers: true) do |row|
        Result.create! row.to_hash
    end
  end
end

Can somebody explain how to make it work?

Any help greatly appreciated.

Bart C
  • 1,509
  • 2
  • 16
  • 17

1 Answers1

0

This part is not very clear in the documentation. and this answer doesn't address how you can make your case works ( I really don't know)... but it has one monkey patch at the end...

all I know is why this case not working...

as the documentation states

When dealing with dynamic attributes the following rules apply:

If the attribute exists in the document, Mongoid will provide you with your standard getter and setter methods.

For example, consider a person who has an attribute of "gender" set on the document:

# Set the person's gender to male.
person[:gender] = "Male"
person.gender = "Male"

# Get the person's gender.
person.gender

this is not your case... cause as it appears you are not defining any attributes in your model...

what applies in your case (from the code you showed and problem you described)

If the attribute does not already exist on the document,

Mongoid will not provide you with the getters and setters and will enforce normal method_missing behavior.

In this case you must use the other provided accessor methods: ([] and []=) or (read_attribute and write_attribute).

# Raise a NoMethodError if value isn't set.
person.gender
person.gender = "Male"

# Retrieve a dynamic field safely.
person[:gender]
person.read_attribute(:gender)

# Write a dynamic field safely.
person[:gender] = "Male"
person.write_attribute(:gender, "Male")

as you can see... there is no way for mongoid to add the setter and getter methods in runtime...

Monkey Patch

  • you could add a field (maybe string, array, hash, whatever suites you) to the document (attribute exists in the document)
  • on populating the document from the CSV row.. just save what are the fields of the CSV in that field... (hold the CSV keys in it)
  • use your predefined field (that holds the keys) instead of using .keys.

code example in your case.

class Result
  include Mongoid::Document
  include Mongoid::Attributes::Dynamic

  field :the_field_that_holds_the_keys, type: Array
  # ...
end

and in your controller:

@results.first.some_attribute
#=> method missing error
@results.first[:some_attribute]
#=> some_value
@results.first.the_field_that_holds_the_keys
#=> [:some_attribute, :some_other_attribute, :yada]
a14m
  • 7,808
  • 8
  • 50
  • 67