I have two mongoid models:
bid.rb and supplier.rb
class Bid
include Mongoid::Document
field :amount, type: BigDecimal
embeds_one :supplier
end
I would like to query the embedded :supplier :name and display the :bid :amount in a JSON response.
I have tried this all kinds of ways, the furthest I got was:
Bid.all.pluck(:supplier, :amount) which only returns the supplier id and amount.
Right now I can write bid_data = Bid.all to get the following JSON response:
{"bid_data":
[{"_id":{"$oid":"58a0a9a531d77f01529a22ba"},
"amount":"5335.0",
"supplier":{"_id":{"$oid":"58a087b131d77f01529a229c"},"name":"Ford","comment":3}
}]}
How can I query the Bid.supplier.name in a controller?
Ideally I would like something like Bid.all.pluck(:supplier.name, :amount)
When you say:
embeds_one :supplier
you're really saying that the bids collection has a Hash field called supplier and that Hash should be wrapped up in the usual Mongoid::Document stuff. That means that you can query supplier like any other Hash:
Bid.where('supplier.name' => 'Ford')
Related
I'm finding it tricky to traverse associations in Active Admin.
In my application I have a SupportSession which appears on an Invoice. Through an association with SupportAllocation I can traverse up the chain to get the SupportRate(s) chargeable on a SupportSession. I can use these rates in calculations for the value of the invoice.
My models are:
class Invoice < ApplicationRecord
has_one :support_session
class SupportSession < ApplicationRecord
belongs_to :invoice, optional: true
belongs_to :support_allocation
class SupportAllocation < ApplicationRecord
has_many :support_sessions
has_many :support_rates
class SupportRate < ApplicationRecord
belongs_to :support_allocation
I've created an Active Admin resource for Invoice where I'd like to do some calculations:
ActiveAdmin.register Invoice do
index do
selectable_column
column 'Reference', :reference, :sortable => 'invoices.reference'
column 'Raised', :created_at, :sortable => 'invoices.created_at' do |invoice|
invoice.created_at.strftime('%Y-%m-%d')
end
column 'Due by', :due_by, :sortable => 'invoices.due_by'
column :value do |invoice|
# The next line retrieves an array of SupportRate(s) associated with this SupportSession on this invoice
invoice.support_session.support_allocation.support_rates.each do |support_rate|
# If the current SupportSession's support_type matches the SupportRate's support_type
if support_rate.support_type == invoice.support_session.support_type
# Do some calculations based on the support_rate.price attribute, plus some other bits I've omitted
end
end
# The next line returns the correct price but obviously it's too clumsy and doesn't do the match on support_type
# invoice.support_session.support_allocation.support_rates[0].price
end
actions
end
end
I can see that data is being retrieved correctly. I can also see it is an array. But if I try to do anything with it, e.g. print out support_rate.price within my 'If' condition, I just get (e.g. for the first record):
[#<SupportRate id: 3, support_type: "mentoring", price: 0.3e2, support_allocation_id: 2>, #<SupportRate id: 13, support_type: "study_skills", price: 0.45e2, support_allocation_id: 2>]
In this specific example, the matching support_type was 'study_skills' - I need to use that support_rate's price for my calculations.
I imagine the solution lies in doing some sort of loop through the array or matching rates, as I've attempted? Active Admin doesn't seem to like it though.
Thank you for your help.
I fixed this by calling a query on the associations available (the find_by method):
Invoice.calculate_value(invoice.support_session.support_allocation.support_rates.find_by(support_type: invoice.support_session.support_type).price
I remembered that an association is an object so inherits all the usual query methods available to it.
In order to do the necessary calculations, I just created a custom function that I hand the values to, it then returns a calculated value:
column :value do |invoice|
Invoice.calculate_value(invoice.support_session.support_allocation.support_rates.find_by(support_type: invoice.support_session.support_type).price, invoice.support_session.rounded_duration_mins)
end
I have an application where users can create many travels, and they can invite their facebook friends. In the travel document, there is a field "participants" that is an embedded document, Participant model embedded in Travel model.
Here are my models :
class Travel
include Mongoid::Document
include Mongoid::Timestamps
# relations
belongs_to :user
# fields
field :title, type: String
field :description, type: String
field :begin_date, type: Date
field :end_date, type: Date
field :budget, type: Integer
field :go_back, type: Boolean
field :title_namespace, type: String
# friends
embeds_many :participants
accepts_nested_attributes_for :participants
end
class Participant
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :user_id, type: String
# relations
embedded_in :travel, :inverse_of => :participants
end
When I try to display travel where users have been invited, with this request :
#travel_participations = Travel.where('participants.user_id' => #user.id)
I don't have any result, even if I have this line in byebug :
#<Mongoid::Criteria
selector: {"participants.user_id"=>BSON::ObjectId('592c8da58511989ec850921e')}
options: {}
class: Travel
embedded: false>
So when I put this on my view :
<% unless #participations.nil? %>
<% #travel_participations.each do |travel_participation| %>
<p> <%= travel_participation.title %> </p>
<% end %>
<% end %>
I tried with .all, .first, .to_a, .as_json, no results ... Some one know where is the problem ?
You have this in your embedded model:
field :user_id, type: String
but your query is using a BSON::ObjectId:
Travel.where('participants.user_id' => #user.id)
as shown in the raw query:
selector: {"participants.user_id"=>BSON::ObjectId('592c8da58511989ec850921e')}
Your embedded document probably has a string field like:
"user_id": "592c8da58511989ec850921e"
rather than the ObjectId you're looking for:
"user_id": ObjectId("592c8da58511989ec850921e")
so you won't find what you're looking for due to the type mismatch.
Either fix the embedded field's type:
field :user_id, type: BSON::ObjectId
or query it as the string it is:
Travel.where('participants.user_id' => #user.id.to_s)
Changing the type will involve fix up whatever data you already have, changing the query is ugly in a different way.
Sometimes Mongoid will convert between strings and ObjectIds for you, sometimes it won't. When I used Mongoid I patched to_bson_id methods into BSON::ObjectId, String, Mongoid::Document, ... so that I could say things like:
Model.where(:some_id => some_id.to_bson_id)
and not have to constantly worry about what type some_id was. I also made sure that all ID fields were always specified as BSON::ObjectId.
I want to query with mongoid , I have following models
class Score
include Mongoid::Document
field :value, :type => Integer
belongs_to :user
end
class User
include Mongoid::Document
field :name, :type => String
field :age, :type => Integer
has_many :scores
I want to query all the scores from collection with their users. but users object should have only have the 'name' field in it
I would be something like
Score.find.all.includes(:user).only(:name)
Please tell the correct syntax for this
Score references User, so it is not possible to retrieve them both in one query, because there are no joins in MongoDB.
Either denormalize and include user name in Score or do several queries.
If I have a model called Product
class Product
include Mongoid::Document
field :product_id
field :brand
field :name
...
belongs_to :store
And then I have a model called Store
class Store
include Mongoid::Document
field :name
field :store_id
...
has_many :products
def featured_products
Products.where(:feature.exists => true).and(store_id: self[:store_id]).asc(:feature).asc(:name)
end
How do I create an accessible #store.featured_products which is the results of this query? Right now I get an error that reads
uninitialized constant Store::Products
Use Product, not Products.
I just stumbled on this looking for something else and although the above answer is correct, and the question is ages old, it is very inefficient for what is being asked. As I stumbled on it, so might others.
The example usage above wished to scope the products relationship to just featured products, so a model such as this would work faster (assumed Mongoid 3.0+):
class Product
include Mongoid::Document
field :product_id
field :brand
field :name
field :feature
...
belongs_to :store
scope :featured, where(:feature.exists => true).asc(:feature).asc(:name)
end
class Store
include Mongoid::Document
field :name
field :store_id
...
has_many :products
end
Then instead of #store.featured_products, you could call #store.products.featured. Now if you assume Mongoid 2.x was used, as this was 2011, and looking at this then Mongoid 1.x maybe not have had scopes, the same thing could be achieved like this:
class Product
include Mongoid::Document
field :product_id
field :brand
field :name
field :feature
...
belongs_to :store
end
class Store
include Mongoid::Document
field :name
field :store_id
...
has_many :products
def featured_products
self.products.where(:feature.exists => true).asc(:feature).asc(:name)
end
end
Now the reason both of these are more efficient that just a boolean search on the Products collection is that they start with a cursor creation across the _id field. This is indexed and limits the subsequent where query to just the related documents. The difference would be noticeable on most collection sizes, however the bigger the collection grew the more time would be wasted on a boolean of the entire collection.
Having two models: Car (e.g Audi, Mercedes) and Option (ABS, Laser Headlights, Night Vision ...). Car habtm options.
Suppose both for Audi and Mercedes "Night Vision" option is available.
But to have it in Mercedes you need to pay some extra money for this option. So as I'm guessing I need to extend somehow my Option model to store options's extra price for some cars. Car model I think should also be modified. But can't imagine how.
My aim to achieve behaviour something like this:
Audi.options.night_vision.extra_price => nil
Mercedes.options.night_vision.extra_price => 300
Of course I don't want to duplicate "Night Vision" option in options collection for every car.
Thanks.
This is neither the simplest, or most elegant, just an idea that should work based on assumptions on how you may have implemented the options. Please come back to me if you need more help.
To achieve audi.options.night_vision.extra_price I assume that you have a models such as:
class car
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :options do
def night_vision
#target.find_by(name:'night_vision')
end
end
end
class option
include Mongoid::Document
field :name, type: String
field :extra_price, type: Float
has_and_belongs_to_many :cars
end
This would enable you to do:
audi = Car.find_by(name:'audi')
audi.options.night_vision.extra_price
If the above assumptions are correct, you should be able to mod your classes like so:
class option
include Mongoid::Document
attr_accessor :extra_price
field :name, type: String
has_and_belongs_to_many :cars
embeds_many :extras
end
class extra
include Mongoid::Document
field :car_id, type: String
field :price, type: String
embedded_in :option
end
class car
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :options do
def night_vision
extra = #target.find_by(name:'night_vision')
extra_price = extra.prices.find_by(car_id: #target._id.to_s) if extra
extra.extra_price = extra_price.price if extra && extra_price
return extra
end
def find_or_create_option(args)
extra = #target.find_or_create_by(name:args)
price = extra.extras.find_or_create_by(car_id:#target._id.to_s)
price.set(price: args.price
end
end
end
This should then enable you to populate your options like:
audi.options.find_or_create_option({name:'night_vision', price:2310.30})
bmw.options.find_or_create_option({name:'night_vision', price:1840.99})
audi.options.night_vision.extra_price
=> 2310.30
bmw.options.night_vision.extra_price
=> 1840.99
And if you attempted to find night_vision on a car that did not have night_vision you would get:
skoda.options.night_vision
=> nil
skoda.options.night_vision.extra_price
=> NoMethodError (undefined method 'extra_price' for nil:NilClass)