MongoDB creates parent objects when I try to create child objects - ruby-on-rails

This model:
class SimCustomer < Customer
index({ user_id: 1 }, { background: true })
belongs_to :user, :inverse_of => :sim_customers
end
inherits from this model:
class Customer
include Mongoid::Document
include Mongoid::Timestamps
field :mail_address, type: String
end
I create the indexes from my terminal:
bundle exec rake db:mongoid:create_indexes
But this creates indexes on the Customer instead of the SimCustomer:
I, [2014-11-13T16:21:17.210343 #11407] INFO -- : MONGOID: Created indexes on Customer:
I, [2014-11-13T16:21:17.210381 #11407] INFO -- : MONGOID: Index: {:user_id=>1}, Options: {:background=>true}
And when I try to batch insert SimCustomer objects it creates Customer objects instead:
SimCustomer.collection.insert(Array.new << {mail_address: "hello#hello.com", user_id: "54652f5b43687229b4060000"})
# => #<Customer _id: 54654b7b6220ff4f28364ee9, created_at: nil, updated_at: nil, mail_address: "hello#hello.com", _type: "Customer">
How can I fix this?

This sets up Single Collection Inheritance:
class SimCustomer < Customer
That means that both Customer and SimCustomer will be stored in the customers collection inside MongoDB and they'll be differentiated using the _type field.
Specifying an index in SimCustomer:
class SimCustomer < Customer
index({ user_id: 1 }, { background: true })
will create the index on the customers collection because that's where SimCustomers are stored.
The same collection chicanery is causing your problem with your bulk insert. If you look at SimCustomer.collection.name you'll find that it says 'customers' so of course SimCustomer.collection.insert will create new Customers. If you want to create SimCustomers by hand then specify the _type:
SimCustomer.collection.insert(
_type: 'SimCustomer',
mail_address: "hello#hello.com",
user_id: "54652f5b43687229b4060000"
)
Note that I dropped that strange looking Array.new << stuff, I don't know where you learned that from but it is unnecessary when inserting on object and odd looking if you were inserting several, if you want to insert several then just use an array literal:
SimCustomer.collection.insert([
{ ... },
{ ... },
...
])
Your next problem is going to be that string in user_id. That really should be a Moped::BSON::ObjectId or you'll end up with a string inside the database and that will make a mess of your queries. Mongoid may know what type a property should be but neither Moped nor MongoDB will. You'll want to use Moped::BSON::ObjectId('54652f5b43687229b4060000') instead.

Related

How to do a qwery witch filter ActiveRecord::Relation by linked relation number

I have to tables in my Rails app which are linked by a ActiveRecord::Relation like so.
# Table name: lease_rents
# id :bigint not null, primary key
class LeaseRent < ApplicationRecord
has_many :lease_tenant_payments,
...
end
class LeaseTenantPayment < ApplicationRecord
belongs_to :lease_rent
...
end
each LeaseRent can have 0 or multiple LeaseTenantPayment.
in my Rails console i am trying to get only the LeaseRent witch have at least one or more LeaseTenantPayment.
to do so i have been able to get the number of LeaseTenantPayment linked to a specific LeaseRent
LeaseRent.all.where('id = 1').first.lease_tenant_payments.count
I want to get an ActiveRecord::Relation array whitch contain each LeaseRent witch have at least 1 or more LeaseTenantPayment. something like this:
#<ActiveRecord::Relation [#<LeaseRent id: ...>,<LeaseRent id: ...>,<LeaseRent id: ...>,...]>
Does anyone have some idea of how to get it?
Use a join, it would only return rows with lease_tenant_payments.
LeaseRent.joins(:lease_tenant_payments)
If you want to use them in a loop, you may need to also use includes
LeaseRent.joins(:lease_tenant_payments).includes(:lease_tenant_payments)
finaly got my solutions !
there are serval way to do it.
solution 1 :
LeaseRent.joins(:lease_tenant_payments).includes(:lease_tenant_payments)
solution 2 :
LeaseRent.joins(:lease_tenant_payments).distinct
solution 3 (my favorite cause it allow to revert it very easily juste by removing the '.not' ) :
LeaseRent.includes(:lease_tenant_payments).where.not(lease_tenant_payments: { id: nil })
i am using that way :
scope "Aucun Paiement", :all, group: :unpayed do |rents|
rents.includes(:lease_tenant_payments).where(lease_tenant_payments: { id: nil })
end
scope "Reliquat", :all, group: :unpayed do |rents|
rents.includes(:lease_tenant_payments).where.not(lease_tenant_payments: { id: nil })
end

find a user by model associations rails

I have a model Camera in which
belongs_to :user, :foreign_key => 'owner_id', :class_name => 'EvercamUser'
i have asscociation like this. when i do Camera.first
#<Camera id: 6, created_at: "2013-12-12 17:30:32", updated_at: "2015-11-19 10:19:33", exid: "dublin-rememberance-floor2", owner_id: 4, is_public: true
i can get owner id, is there any way to create such function that , along side getting owner id, i can get the data which linked with this id for example at id = 4
#<EvercamUser id: 4, created_at: "2013-12-12 16:43:46", updated_at: "2015-04-16 15:23:19", firstname: "Garrett", lastname: "Heaver", username: "garrettheaver"
this user is present, what if when i do Camera.first then instead of OnwerID, how can i get the owners Name?
Any help will be appreciated!
how can i get the owners Name
You'd call the associative object on the Camera object:
#camera = Camera.find x
#user = #camera.user
#user.name #-> outputs name of associated user object
... this will allow you to call the attributes of the child object on it: #camera.user.name or #camera.user.email, etc
Off topic, but I always include a reference to delegate for this type of issue; it avoids the law of demeter (where you're using more than one point to access data).
This would allow you to use:
#app/models/camera.rb
class Camera < ActiveRecord::Base
belongs_to :user, foreign_key: :owner_id, class_name: 'EvercamUser'
delegate :name, to: :user, prefix: true #-> #camera.user_name
end
#camera = Camera.find x
#camera.user_name #-> outputs the user's name on the camera object (not user object)
To give you some context, Rails uses ActiveRecord to invoke/create objects for you.
In line with the object orientated nature of Rails, ActiveRecord is known as an ORM (Object Relationship Mapper). This basically allows you to create an object through ActiveRecord, and if it is associated to another (as Rails does with its associations), it will append the associated object onto the parent.
Thus, when you're asking about calling owner_id, you're referring to the foreign_key of the association (the database column which joins the two tables together):
What you need is to reference the associated object, which I've detailed above.
What about using join here?
Camera.all.joins(:evercamusers)
Camera.where(:id => 1).joins(:users).first
Note: I'm a bit unsure if the correct parameter should be ":users" or ":evercamusers"
http://apidock.com/rails/ActiveRecord/QueryMethods/joins
You could also add methods to your class to do this.
class Camera < ActiveRecord::Base
belongs_to :user, :foreign_key => 'owner_id', :class_name => 'EvercamUser'
def firstname
self.user.firstname
end
end
When you try to output data from Camera like this:
#<Camera id: 6, created_at: "2013-12-12 17:30:32", updated_at: "2015-11-19 10:19:33", exid: "dublin-rememberance-floor2", owner_id: 4, is_public: true
It won't show. But if you call the method like this, it should work:
Camera.first.firstname # "Garrett"
Also, if JSON is acceptable you could override the as_json method.
def as_json(options={})
{ :firstname => self.user.firstname }
end
Then call it with
Camera.first.as_json
If you need to do it with all, simply loop it
Camera.all.each { |c| puts c.firstname }

Get all tree from a model object

I have
#total = Purchase::Total.find(1);
Total model have:
has_many :items
belongs_to :member
belongs_to :company
..................
Also companies model has
has_many :addresses
has_one :subscription
..................
and a lot more
How can I get a tree from the #total object containing all the has_one, belongs_to dependencies?
I.E.
<Purchase::Total id: 3, member_id: 4, created_at: \"2015-11-25 14:47:46\", updated_at: \"2015-11-25 14:47:46\", affiliate_company_id: nil, is_paid: false, currency: 1, company_id: 37020, ser_id: 2>
<Company id: 37020, name: \"Andrew\", parent_id: 37019, member_company_id: 37019, payment_company_id: 37019, widget_id: 3003359>
And so ..... (I did the example with: #total.inspect and #total.company.inspect), and I need something like inspect to return automatically all the objects.
Using reflect_on_all_associations
Take a Queue and a Hash and add Total (model name) to it.
Pop a model name, get all associated models and add them queue. Also, using the tablize name of current model, create a new entry in hash and add the tablized names of associated models.
If queue is not empty, go to 2.
At the end, your hash should look like:
{ total: { company: [ :subscription, :addresses ] }, items: { associated: { another: :another_one } } }
Then you can use this in your query:
Total.where().join(hash[:total])
It will fetch all the associated data as well. Then you can simply loop through the attributes. If attribute type is ActiveRecord (or similar), then its an associated model data.

Rails CollectionProxy randomly inserts in wrong order

I'm seeing some weird behaviour in my models, and was hoping someone could shed some light on the issue.
# user model
class User < ActiveRecord::Base
has_many :events
has_and_belongs_to_many :attended_events
def attend(event)
self.attended_events << event
end
end
# helper method in /spec-dir
def attend_events(host, guest)
host.events.each do |event|
guest.attend(event)
end
end
This, for some reason inserts the event with id 2 before the event with id 1, like so:
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 2, name: "dummy-event", user_id: 1>, #<Event id: 1, name: "dummy-event", user_id: 1>
But, when I do something seemlingly random - like for instance change the attend_event method like so:
def attend_event(event)
self.attended_events << event
p self.attended_events # random puts statement
end
It gets inserted in the correct order.
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 1, name: "dummy-event", user_id: 1>, #<Event id: 2, name: "dummy-event", user_id: 1>
What am I not getting here?
Unless you specify an order on the association, associations are unordered when they are retrieved from the database (the generated sql won't have an order clause so the database is free to return things in whatever order it wants)
You can specify an order by doing (rails 4.x upwards)
has_and_belongs_to_many :attended_events, scope: -> {order("something")}
or, on earlier versions
has_and_belongs_to_many :attended_events, :order => "something"
When you've just inserted the object you may see a different object - here you are probably seeing the loaded version of the association, which is just an array (wrapped by the proxy)

Mongoid - two update modifier in single call to update a document in mongodb

I am trying to experiment with mongodb, mongoid and rails. I have a simple Task and Comment model in Rails, where Comments are embedded into Tasks. Now Task has attribute called comment_count. Is there a way of increment the count as well as push a new comment together in a single call.
Task model:
class Task
include Mongoid::Document
field :name
field :desc
field :comment_count, type: Integer, default: 0
embeds_many :comments
end
Comment Model:
class Comment
include Mongoid::Document
field :entry
embedded_in :task
end
Below is the operation which I want to do in a single call.
1.9.3p194 :025 > task.comments.push(Comment.new(entry: "This is a comment"))
=> [#<Comment _id: 509e1708a490b3deed000003, _type: nil, entry: "First comment">, #<Comment _id: 509e1716a490b3deed000004, _type: nil, entry: "Second comment">, #<Comment _id: 509e1aa3a490b3deed000005, _type: nil, entry: "This is a comment">]
1.9.3p194 :026 > task.inc(:comment_count, 1)
=> 3
I actually intend to get a way of using multiple update modifiers like $inc, $push, $pop etc in a single update call. Similar to what we can do directly in mongo shell.
Please help.
Thanks
Unfortunately, Mongoid does not seem to support counter_cache as ActiveRecord does.
You could use an after_save and an after_destroy callback on your Comment model to implement this, respectively incrementing / decrementing the parent's counter.

Resources