I have a 1-n relationship defined as follows:
class User
field :email, type: String
embeds_many :papers
end
class Paper
embedded_in :user
end
If I try and access the parent fields (user) from the child (paper) like so:
User.all.map(:papers).flatten.first.user.email
Then I get nil :(
Accessing like this works fine though:
User.all.first.papers.first.user.email
It's a mispelling lire report on comment. To call a method on a map,you need use & before your symbol.
Try:
User.all.map(&:papers).flatten.first.user.email
Related
is it possible to have virtual attributes for embedded models?
I have a class A:
class A
include Mongoid::Document
field :name, type: String
embedded_in :b
def pnl=(p)
end
def pnl
"p"
end
def as_json(options={})
attrs = super(methods: [:pnl])
attrs
end
end
and class B:
class B
include Mongoid::Document
field :name, type: String
embedds :a
end
If I take a record from B:
b = B.first
b.as_json
I dont get the virtual attributes from embedded class a - I only see the persistent attributes. My controller is supposed to return b inclusive all virtual attributes of their embedded objects - but this doesnt work.
If I do:
b.a.as_json
Then I get the virtual attributes of embedded class A but this doesnt help. If I do not use embedded but referenced relationship (belongs_to/ has_one) it works all fine, but I thought embedded relationship would be better for my use case.
CORRECTION: It also doesnt work with referenced relationship. It seems virtual attributes are not supported for any relationship?
Is there a way I also get the virtual attributes calling b.as_json?
Thanks,
Michael
seems as_json doesnt recursivle call as_json. The solution is to overwrite serializable_hash instead of as_json and it works as expected. Not sure what is the reason it doesnt work with as_json.
Rgds
Michaek
I have this code in my model.
belongs_to :user, -> { includes :user_profile }
I went through shoulda doc i did not found any way to test that proc
containing includes part. Please help.
Assuming the model in question is named Car (you haven't posted the name of the enclosing class so I'm totally making this up) you can test it as:
describe 'user' do
let(:car) { create_car_somehow }
it 'preloads the user_profile association' do
expect(car.user.association(:user_profile)).to be_loaded
end
end
#association returns metadata of the specified association (of type ActiveRecord::Associations::BelongsToAssociation and so on). The returned object responds to #loaded? which returns true if the association has been loaded and false otherwise.
Consider the following model setup
class Template < ActiveRecord::Base
belongs_to :detail, polymorphic: true, dependent: :destroy
accepts_nested_attributes_for :detail
end
class EbayTitleTemplate < ActiveRecord::Base
has_one :template, as: :detail
end
And here is a working factory
FactoryGirl.define do
factory :template do
merchant
channel
trait :ebay_title do
association :detail, factory: :template_ebay_title
end
factory :ebay_title_template, traits: [:ebay_title]
end
factory :template_ebay_title, class: EbayTitleTemplate do
name "eBay Title Template"
title "Super Cool Hat"
sub_title "Keeps the sun away"
description "The best hat available!"
end
end
The following works for me
create(:ebay_title_template) # creates both records, creating a new Merchant and Channel for me
create(:ebay_title_template, merchant: Merchant.first, channel: Channel.first) # creates both records with explicit channel and merchant
Now what I'd like to also do is pass in custom attributes to override the defaults. Something like this:
create(:ebay_title_template, title: "Overwrite the title", sub_title: "Overwrite the subtitle")
What ends up happening is that I get the error ArgumentError: Trait not registered: title
Somehow FactoryGirl thinks that I'm passing in a trait or the Template factory doesn't recognize title as an attribute.
I've tried using transients to allow custom args to pass through the Template and using a callback in the :template_ebay_title factory to map the attribute to the model column like so:
transient do
custom_args nil
end
after(:create) do |record, evaluator|
evaluator.custom_args.each do |key, value|
record.key = value
end
end
And then I create like this:
create(:ebay_title_template, custom_args: {title: "Overwrite", sub_title: "Overwrite"})
This results in the error NoMethodError: undefined methodcustom_args' for #`
So either there's a way to do this and I'm doing it wrong, or I need a completely new approach. Keep in mind, there will be dozens of associations that will need to be defined as traits (or something else) so I can't possibly assign specific transients to be passed through.
How can I achieve my goal of creating a factory that creates the parent and the polymorphic association, allowing me to pass in arbitrary attributes for the polymorphic association, and return the parent object?
If you are wanting to pass the values within a single hash value, you need to pass them like so:
create(:ebay_title_template, custom_args: {title: "Overwrite the title", sub_title: "Overwrite the subtitle"})
Otherwise you can create new transient values, and pass them the way you are passing them now:
transient do # use ignore with FactoryGirl/FactoryBot < v4.7
title nil
sub_title nil
end
When adding more attributes like above you can then iterate over the evaluator's overrides instance variable:
evaluator.instance_variable_get(:#overrides).each do |key, value|
puts key, value
end
Hope that helps!
My application model allows Patients to have CustomFields. All patients have the same customs fields. Customs fields are embedded in the Patient document. I should be able to add, update and remove custom fields and such actions are extended to all patients.
class Patient
include Mongoid::Document
embeds_many :custom_fields, as: :customizable_field
def self.add_custom_field_to_all_patients(custom_field)
Patient.all.add_to_set(:custom_fields, custom_field.as_document)
end
def self.update_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.update_custom_field(custom_field) }
end
def update_custom_field(custom_field)
self.custom_fields.find(custom_field).update_attributes({ name: custom_field.name, show_on_table: custom_field.show_on_table } )
end
def self.destroy_custom_field_on_all_patients(custom_field)
Patient.all.each { |patient| patient.remove_custom_field(custom_field) }
end
def remove_custom_field(custom_field)
self.custom_fields.find(custom_field).destroy
end
end
class CustomField
include Mongoid::Document
field :name, type: String
field :model, type: Symbol
field :value, type: String
field :show_on_table, type: Boolean, default: false
embedded_in :customizable_field, polymorphic: true
end
All pacients have the same customs fields embedded in. Adding a custom field works very well. My doubt is about updating and destroying.
This works, but it is slow. It makes a query for each pacient. Ideally I would just be able to say to MongoDB 'update the document with id: that is embedded in the array *custom_fields* for all documents in the Patient collection'. Idem for destroy.
How can I do this in Mongoid?
I am using Mongoid 3.1.0 & Rails 3.2.12
I don't think there is a way you can do that with a good efficiency with embedded documents.
Maybe you should consider having a referenced relationship between your models, so that you can use the delete_all and update_all methods on the collection.
Is there a way to get embedded documents to initialize automatically on construction in mongoid? What I mean is given that User which embeds a garage document. I have to write the following code to fully set up the user with the garage:
user = User.create!(name: "John")
user.build_garage
user.garage.cars << Car.create!(name: "Bessy")
Is there a way I can skip calling user.build_garage?
Thanks
Mongoid 3 have autobuild option which tells Mongoid to instantiate a new document when the relation is accessed and it is nil.
embeds_one :label, autobuild: true
has_one :producer, autobuild: true
You can add a callback to the User model like this:
class User
...
after_initialize do |u|
u.build_garage unless u.garage
end
...
end
This callback fires after each instantiation of the class, so it fires after 'find' and 'new'.