How to make attribute_names list all attribute names in a document with dynamic attributes - ruby-on-rails-4

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.

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]

Related

Custom model name in Rails 4 validation

I have a class that looks something like this:
class OrganicBipedalLifeform < ActiveRecord::Base
# Has the field 'name'
validate :presence_of_name
private
def presence_of_name
errors.add(:base, "name can't be blank") unless name.present?
end
end
And I want the validation error message to use a custom string that excludes (or modifies) the model name, say 'Human/Vulcan name can't be blank'.
If I want this to be the default message for validation errors on this model, is there a better approach than changing the flash details in every view which might display validation errors? Ie by changing something on the model itself?
Apologies if this has been answered elsewhere. I've found a lot of posts about customising the field name, but none about modifying the name of the model itself.
ETA: #TomDunning #Dan, I think I misidentified the source of the problem (or at least didn't make it sufficiently specific), so am creating a new thread to ask what I hope is a better question.
I think you can replace :base with self.class_name or self.class.table_name or a similar class method.
That is bad design, just use this:
validate :name, presence: true
"name can't be blank" would be the default error anyway.
If you then want to extract these later just call my_record.errors or similar.
For a custom error message
validate :name, presence: { message: 'must not be blank' }

How can I set the value of objectID in algolia rails?

I am using algolia gem in my rails app. I am currently combining 3 different models into one index for a combined search of our case studies, blog posts and videos.
I am having trouble as indexing one model will overwrite objects from another model as Algolia appears to use the rails generated ID to map to objectID column.
posts.rb
algoliasearch auto_index: false, if: :open?, index_name: "combined_#{Rails.env}" do
attribute :objectID do
"p#{id}"
end
end
video.rb and case_studies.rb
In the same fashion I try to set attribute :objectID to "v#{id}" for videos as well as set attribute :objectID to "c#{id}" for case studies
This virtual attribute style did not work, so I attempted to create an actual string column on the db and reindex that way to no avail.
If anyone have any ideas here I would really appreciate it and I am running low.
You can use the custom ObjectID inside each of your Model.
Inside your model
def custom_algolia_id
"v#{id}"
end
algoliasearch id: :custom_algolia_id do
attribute :title, :length
end

rails 4.1 / active record data_store hash not being initialized/serialized

Getting around this data store thing that rails provide, I have something that troubles me
Let's say I have a simple post model, with a title, and that for some reason I'd like to store somehow dynamics attributes in another field, call "post_garbage"
In the post model, I have the simple serializer :
class Post < ActiveRecord::Base
serialize :post_garbage
end
As I said, and besides what the rails doc explains, no named accessors are set on this post_garbage field
Playing in the console :
p = Post.first
p.post_garbage["the_stuff"] = "something"
p.save
I get an error, obviously. "the_stuff" is unknown.
Stopping right here, you'd like to dump this data_store thing, safer and easier to create a new field to get the job done (I don't get the utility of it if you have to set an accessor on a data_store field).
* But *
If first an empty hash is saved in this post_garbage field :
p = Post.first
p.post_garbage = {}
p.save
The following becomes possible :
p = Post.first
p.post_garbage["whatever_i_have_in_mind"] = "some value"
p.save
Right after that :
p.post_garbage["it_then_get_dynamic"] = "and that's pretty cool"
p.save
It gets schemaless with no need to specify accessors; kind of provide the expected thing.
So I wonder : many would expect a schemaless thing with the datastore, but it is not said to be used like this by the doc. But it works, with what looks like a bad trick. Wonders…
In the opposite, what's the use of the serialize :post_garbage, if, when empty, it does not returns an empty hash ?
I'd really appreciate some experienced feedback on this one as I feel like I miss something
Thanks a lot
This might have to do with the fact that upon object initialization there is no value in p.post_garbage.
p.post_garbage["the_stuff"] = "something"
Is trying to implicitly treat post_garbage as a hash.
Which would end up being similar to
unset_variable = nil
unset_variable['hash_key'] = 'hash_value'
Using the serialize method for an attribute tells Rails it should expect to condense an Object into a key value map and store it in the database as text. But that doesn't necessarily mean your object is instantiated with this as a hash. It could be any object in particular. But that object needs to be assigned to your attribute before it can be serialized. Serialize may work out the magic to store and reconstitute it on load, but it will not make any assumptions about the form of that object if uninitialized. So without any indication of what object post_garbage is Ruby will not expect it to be a hash.
As for why does it not return an empty has when empty? There is a difference between an empty object and a missing object. (ie: {} vs nil). They are not the same thing, often a missing object is treated like an empty object. But there is value in that distinction, which I expect Rails preserves when loading a serialized attribute.
Perhaps you should try initializing it on create with something like this?
class Post < ActiveRecord::Base
serialize :post_garbage
after_initialize :default_values
private
def default_values
self.post_garbage ||= {}
end
end

Fake select field for Simple Form

I'm using Simple Form, and I have a few fields that are not associated with my model. I found using this fake field option to be a good solution:
https://github.com/plataformatec/simple_form/wiki/Create-a-fake-input-that-does-NOT-read-attributes
I thought this was cleaner than adding an attr_accessor value for my fields, and it works great for text input fields. I'm hoping to accomplish the same thing with a Select Field.
I found this thread, but I couldn't find anything else:
https://github.com/plataformatec/simple_form/issues/747
Has anyone found a similar option for a Fake Select Input? Thanks!
Assuming you'll use that "fake select" for UI purposes (probably as a mean to modify the form fields to present the user using Javascript?) and you don't want to deal with the value in the controller, you could just use select_tag with any field name, instead of the simple_form f.input. The value will be sent to the server, but it will be outside the model params, so you can just ignore it in the controller.
If I misunderstood your question, please clarify.
If your just trying to get the name='whatever' instead of having name='model[whatever]' I've found it easiest to just specify the name attribute in input_html { name: 'whatever', id: 'whatever' } hash which over rides the default model[attribute].
Otherwise you could create a fake_select_input.rb which would be similar to fake_input.rb but obviously use a select_tag instead and do something like as: :fake_select

Rails: Invalid single-table inheritance type error

So, I am working on migrating this php site with an existing database which I cannot change over to Rails. There is a table: Quotes with a column named type. Whenever I try and create a model of this and set the type, it tells me the following error:
ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: HOME is not a subclass of Quotes)
I don't understand why it thinks its inheriting because it's not supposed to. My create method looks like this:
quote = Quotes.create(
agent_id: agent.id,
client_id: client.id,
type: 'HOME',
status: 0,
date_created: DateTime.now
)
If I comment out the type, everything works fine. But with the Type it errors.
I resolved this by setting the models inheritance_column to nil. Active Record Models can inherit from a table through the attribute :type, setting the inheritance_column to nil removes that attribute allowing you to have a database column named type
class Quote < ActiveRecord::Base
self.inheritance_column = nil
end
I hate having potential gotchas deep in the code especially in the intial processes like generating a model. Better to just change the reserved word to something else and free yourself up to take advantage of inheritance column later if the need comes up. A cleaner solution is listed here -> rename a database column name using migration
It reads;
Execute $> rails generate migration ChangeColumnName
where, ChangeColumnName is the name of our migration. This can be any name.
Now, edit the generated migration file at db/migrate/_change_column_name.rb
class ChangeColumnName < ActiveRecord::Migration
def change
rename_column :table_name, :old_column, :new_column
end
end
$> rake db:migrate
You will have to edit controller and view files e.g. if the model name is Product then you will likely edit these files
/app/views/products/_form.html.erb
/app/views/products/show.html.erb
/app/controllers/products_controller.erb
/app/views/products/index.html.erb