Rails - has_many association in run time - ruby-on-rails

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?

Related

Rails 5 - belongs_to throws validation errors when data is valid

Using Rails 5.1.3 with Mongoid 6.2.1, I am getting a validation error when data is valid. Here are the 2 classes I have noticed an issue with so far:
character.rb
belongs_to :user, inverse_of: :characters
belongs_to :bdo_class, class_name: 'BDOClass'
user.rb
has_many :characters, inverse_of: :user
I do user.characters.new(...) with BDO class specified, I save it and it saves.
If I then do a character search and save, eg;
ch = Character.where(:name => 'MyCharacter').first
ch.save!
I get a validation error: User can't be blank, Bdo class can't be blank
If I check ch.user_id myself I can see that the field is correct and valid, it references the ObjectId that it should.
I looked up possible fixes for this issue in Rails 5, all recommended I set optional: true on the classes. However I would like this field to be required. It also doesn't fix the issue because ch.user still returns nil.
Result of Character.where(:name => 'Drahcir').first
#<Character _id: 5a099cbabc80b75e845a997a, name: "Drahcir", name_case: "drahcir", level: "58", ap: 0, aap: 0, dp: 0, is_primary: true, user_id: BSON::ObjectId('5a099b48bc80b75e03a4ac3e'), bdo_class_id: BSON::ObjectId('5a098e86bc80b75998d141c9')>
Result of User.where(:username => 'Nishikin').first
#<User _id: BSON::ObjectId('5a099d6dbc80b75ee6e6d2d7'), guild_master: nil, member: nil, officer: nil, user_case: "nishikin", username: "Nishikin">
Sergio pointed out in the comments that user_id does not match the _id of the parent user. Which leads me to the question of why user.characters.new(...) linked the wrong ObjectId, same goes for the reference to BDOClass.
Any help is greatly appreciated :)

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.

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>

How to select unique records based on foreign key column in Rails?

I have the following model structure in my Rails 4.1 application:
delivery_service.rb
class DeliveryService < ActiveRecord::Base
attr_accessible :name, :description, :courier_name, :active, :country_ids
has_many :prices, class_name: 'DeliveryServicePrice', dependent: :delete_all
end
delivery_service_price.rb
class DeliveryServicePrice < ActiveRecord::Base
attr_accessible :code, :price, :description, :min_weight, :max_weight, :min_length, :max_length,
:min_thickness, :max_thickness, :active, :delivery_service_id
belongs_to :delivery_service
end
As you can see, a delivery service has many delivery service prices. I'm trying to retrieve records from the delivery service price table; selecting the record with the lowest price attribute within the unique scope of the foreign key, delivery_service_id (so essentially the cheapest delivery service price per delivery service).
How can I select unique records from a table, with the foreign key attribute as the scope?
I hope I've explained this enough, let me know if you need anymore information.
Thanks
UPDATE #1:
Example of what I'm trying to achieve:
delivery_service_prices table:
id: 1, price: 2.20, delivery_service_id: 1
id: 2, price: 10.58, delivery_service_id: 1
id: 3, price: 4.88, delivery_service_id: 2
id: 4, price: 1.20, delivery_service_id: 2
id: 5, price: 14.99, delivery_service_id: 3
expected results:
id: 1, price: 2.20, delivery_service_id: 1
id: 4, price: 1.20, delivery_service_id: 2
id: 5, price: 14.99, delivery_service_id: 3
Due to PostgreSQL being more strict with abiding the SQL standard (rightly so), it requires a bit of tweaking to get the correct results.
The following query returns the correct results for the lowest delivery service price, per delivery service:
DeliveryServicePrice.select('DISTINCT ON (delivery_service_id) *').order('delivery_service_id, price ASC')
I need to add the delivery_service_id attribute to the order condition, or PostgreSQL throws the following column error:
PG::InvalidColumnReference: ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions
Hope this helps anyone who stumbles upon it!
To get the minimum for a single record you can use
DeliveryServicePrice.where(delivery_service_id: x).order(:price).limit(1).first
or if you have a delivery_service object available
delivery_service.prices.order(:price).limit(1).first
UPDATE
If you want all minimums for all service_delivery_ids you can use a group query
DeliveryServicePrice.group(:delivery_service_id).minimum(:price)
which will get you almost where you want to go
{
1: 2.20,
2: 1.20,
3: 14.99
}
with a hash containing the delivery_service_id and the price. (you can't see the price_id )

Resources