Get all tree from a model object - ruby-on-rails

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.

Related

Rails - has_many association in run time

I have a Account table. which has self association using ancestry gem. Account has ancestry column using which we can get the parent account.
Account
has_many :connections
Also, I have connections table. which
belongs_to :account
Now the set up I have has a parent account
id: 1, name: 'Parent', ancestry: NULL
child account:
id: 2, name: 'Child', ancestry: 1
and a connection:
id: 1, name: 'child-connection', account_id: 2
so I am able to get all connections of as account using
account.connections
I am trying to define a runtime has_many association specific to a class like this
Account.send(:has_many, :all_connections, ->(account) {Connection.where('account_id IN (?) OR account_id IN (?)', account.id, account.ancestry) }, class_name: Connection.name)
But doing account.all_connections always run,
SELECT `connections`.* FROM `connections` WHERE `connections`.`discarded_at` IS NULL AND `connections`.`account_id` = 447549 AND (account_id IN (447549) OR account_id IN (NULL))
Not sure why AND connections.account_id = 447549 is getting appended.
Is there an alternate way to achieve this?

How to seed belongs to and has many associantion with seeds.rb

I have two models. One is categories and the other one is products. I already populated the categories table and now I need to populate the products one with the category id.
I'm using faker for this population.
The models and the seeds.rb file are like this
class Product < ApplicationRecord
belongs_to :categories
end
class Category < ApplicationRecord
has_many :products
end
seeds.rb
category = Category.ids
100.times do
Product.create!([{
name_product: Faker::Commerce.product_name
},
{
value_product: Faker::Commerce.price
},
{
category_id: category.sample
}
])
end
But I keep getting the error "validation failed. Categories is necessary" even passing the category id.
If I comment the association inside the product model it works but this isn't a solution.
I think your problem lies in how you are attempting to create the products.
Your current code will attempt to create three products, as you're passing an array of hashes - two of which have no category ID:
Product.create!([
{
name_product: Faker::Commerce.product_name # << first product, no category
},
{
value_product: Faker::Commerce.price # << second product, no category
},
{
category_id: category.sample # << third product
}
])
It's a little unclear from your question but try one the following inside your 100.times do block instead:
Product.create!(
name_product: Faker::Commerce.product_name,
value_product: Faker::Commerce.price,
category_id: category.sample
)
Or:
Product.create!([
{
name_product: Faker::Commerce.product_name,
category_id: category.sample
},
{
value_product: Faker::Commerce.price,
category_id: category.sample
},
{
category_id: category.sample
}
])
The former will create a single record, if each instance has name_product, value_product and category_id columns; the latter three products, all of which have a category_id.
Let me know how you get on and if that helps at all.
Edit
Looks like your association is setup incorrectly. Try:
class Product < ApplicationRecord
belongs_to :category
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 }

Making a has_many relation avoiding childs to store paretn's id

class User
include Mongoid::Document
has_many :favorites, class_name: "Item"
end
class Item
include Mongoid::Document
belongs_to :user, dependent: :nullify
end
I want that the users have an array of favorites but in Item collection, the user_id is not stored. Is the approach I followed correct?
If I try to access a user favorites as User.last.favourites or try to add a favorite to a user, it takes for ever. Why is this?
Thanks
I believe you are missing the embedded_in :user on your Item class
class User
include Mongoid::Document
has_many :favorites, class_name: "Item"
end
class Item
# class is all lower case
include Mongoid::Document
# remove the relation to the user form the item
# so that it cannot save the user_id
end
the previous code should work, and won't allow you to save the user_id in a favorite.
so this code should work user.last.favorites #=> [Array of favorites]
but this code will through an exception user.last.favorites.last.user #=> method user not found
why it takes forever ?! I cannot judge unless i've seen the logs.
also don't go for the embedded solution, the embedded collection is only accessible from the parent... and i think this is not what you want to achieve...
in a simpler words: if you have a favorite that is called 'rails',
using the embedded solution described in the other answer would result in the following behaviour
p Favorites.all.to_a
#=> []
p user.first.favorites.first
#=> <Favorite id: 1, name: rails>
p user.last.favorites.first
#=> <Favorite id: 100, name: rails>
if you noticed:
the collections is not query-able except from the parent that embeds it.
the favorites collections is unique per user. 2 different users cannot share the same embedded document ( as in the previous code, both first and last user has the same favorite (what you intended) but they are actually 2 completely different objects.
the same code using the has_many relation would result in the following
p Favorites.all.to_a
#=> [<<Favorite id: 1, name: rails>]
p user.first.favorites.first
#=> <Favorite id: 1, name: rails>
p user.last.favorites.first
#=> <Favorite id: 1, name: rails>
# this is pseudocode but you will get the idea
user.last.favorites.first.name = `rails4` # then save
p user.first.favorites.first
#=> <Favorite id: 1, name: rails4>
p user.last.favorites.first
#=> <Favorite id: 1, name: rails4>

MongoDB creates parent objects when I try to create child objects

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.

Resources