mongoid equivalent of loaded? - ruby-on-rails

In a Rails app, using ActiveRecord with mysql, you can check to see if an association has been loaded:
class A
belongs_to :b
a = A.find(...
a.b.loaded? # returns whether the associated object has been loaded
Is there an equivalent in mongoid? ._loaded? used to work but no longer does.
UPDATE - adding example
class A
include Mongoid::Document
end
class B
include Mongoid::Document
belongs_to :a
end
a = A.new
b = B.new
b.a = a
b.a._loaded?
returns:
ArgumentError (wrong number of arguments (given 0, expected 1))

It's a enumerable method of this Class: Mongoid::Relations::Targets::Enumerable
_loaded?
it will return true and false if Has the enumerable been _loaded? This will be true if the criteria has been executed or we manually load the entire thing.

Maybe the purpose was not the same.
Now (Mongoid 7.0) _loaded?() is a private method, and in my case it always returns true.
The best I could find is ivar(): it returns the object if already loaded, or false if not loaded.
I'm not sure if this is a reliable solution. It depends on further availability of ivar() and the way objects are stored as instance variables.
> b = B.find('xxx')
=> (db request for "b")
> b.ivar('a')
=> false
> b.a
=> (db request for "a")
> b.ivar('a')
=> (returns "a" object, as when b.a is called)

You can test if includes(:your_association) has been added to the criteria like this:
inclusions = Criteria.inclusions.map(&:class_name)
inclusions.include?('YourAssociation)
For example:
Children.all.include(:parent).inclusions
=> [<Mongoid::Association::Referenced::BelongsTo:0x00007fce08c76040
#class_name="Parent", ...]
Children.all.inclusions
=> []

Related

How to get ActiveRecord non-persistant variable to save?

I'm trying to setup an attribute that isn't saved to the database but I can't work out how to change it and read the new value.
class User < ApplicationRecord
attribute :online, :boolean, default: false
end
in Rails Console:
User.first.online = true
=> true
User.first.online
=> false
I'm running Ruby-on-rails 5.2.4.1 and ruby 2.4.1
https://api.rubyonrails.org/v5.2.4.1/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
The line:
User.first
Creates an instance for the first user each time you call it.
User.first.equal?(User.first) #=> false
# ^^^^^^
# Equality — At the Object level, returns true only if obj
# and other are the same object.
You're setting the online attribute of a different instance than the one you're reading from (although they represent the same record). Store the user in a variable. That way you're working with the same instance for both the set and get call.
user = User.first
user.online = true
user.online #=> true

How to create a enum type and default to a specific value for new objects

I have a model
class Transaction < ActiveRecord::Base
end
I have a transaction_type column which is an integer.
How can I create an enumeration that I could map values to names like:
one_time = 1
monthly = 2
annually = 3
So in the db column, the values would be 1, 2 or 3.
Also, whenever I create a new instance, or save a model and the field wasn't set like:
#transaction = Transaction.new(params)
It should default to 1 (on_time).
I'm not sure how I can do this?
basically the same answer as Amit, slight variation
class TransactionType
TYPES = {
:one_time => 1,
:monthly => 2,
:annually => 3
}
# use to bind to select helpers in UI as needed
def self.options
TYPES.map { |item| [item[0], item[1].to_s.titleize] }
end
def self.default
TYPES[:one_time]
end
end
one way to control the default value
class Transaction < ActiveRecord::Base
before_create :set_default_for_type
def set_default_for_type
type = TransactionType.default unless type.present?
end
end
but - best way is to just apply the defaults on your database column and let ActiveRecord get it from there automatically
NOTE: it might also make sense to just have a TransactionType ActiveRecord object instead of above, depends on your situation, i.e.
# on Transaction with type_id:integer
belongs_to :type, class_name: "TransactionType"
You can map the values by creating a constant either in the same Transaction model or by creating a new module and place it inside that as explained by #KepaniHaole
In Transaction model, you can do it like :
class Transaction < ActiveRecord::Base
TRANSACTION_TYPES = { 'one_time' => 1, 'monthly' => 2, 'monthly' => 3 }
end
You can access these values by accessing the constant as
Transaction::TRANSACTION_TYPES['one_time'] # => 1
Transaction::TRANSACTION_TYPES['monthly'] # => 2
Transaction::TRANSACTION_TYPES['monthly'] # => 3
To add a default value to transaction_type column just create a new migration with :
def up
change_column :transactions, :transaction_type, :default => Transaction::TRANSACTION_TYPES['one_time']
end
With this, every time you create a Transaction object without passing transaction_type, the default value 1 with be stored in it.
Maybe you could try something like this? Ruby doesn't really support c-style enums..
module TransactionType
ONCE = 1
MONTHLY = 2
ANUALLY = 3
end
then you could access their values like so:
#transaction = Transaction.new(TransactionType::ONCE)

Rails STI subclasses validation on update_attributes

I would like to know if there's a way to when doing STI the update_attributes, validate the attributes based on the new class type?
For e.g. suppose i have:
class A < ActiveRecord::Base
end
class B < A
validates :attribute_z, :presence => true
end
class C < A
validates :attribute_x, :presence => true
validates :attribute_y, :presence => true
end
If i run (the way rails is implemented):
b = A.find('b-id')
b.update_attributes({ 'type' => 'C', :attribute_x => 'present', :attribute_y => 'present', :attribute_z => nil }) # will return false with errors on 'attribute_z must be present'
I've tried with #becomes:
b = A.find('b-id')
b = b.becomes(C)
b.update_attributes({ 'type' => 'C', :attribute_x => 'present', :attribute_y => 'present', :attribute_z => nil })
# this works partially, because the validations are ok but when i look to console i get something like:
UPDATE "as" SET "type" = 'c', "attribute_z" = NULL, "attribute_y' = 'present', 'attribute_x' = 'present' WHERE "as"."type" IN ('C') AND "as"."id" = 'b-id'
# which is terrible because it's looking for a record of B type on the C types.
Allied to this topic Callback for changed ActiveRecord attributes?, you can catch any assignments done to type attribute and make "self" to be of different class (A, B or C) by using the becomes method. So whenever you use find method, it'll populate a fresh new model instance with the data that comes from the DB (identified by 'b-id') and it'll automatically forced-cast the model instance to another type if necessary.
Does that help you?
i've made a solution: https://gist.github.com/4532583, since the condition is added internally by rails (https://github.com/rails/rails/blob/master/activerecord/lib/active_record/inheritance.rb#L15) i've created a new "update_attributes" method that changes the type if the type is given. :)

2 level deep grouping in ruby

So I have a array of records and I would like to group by 2 levels
Essentially I would like to group_by{|x| x.field1 } and then each value in hash to be further grouped in by field2. Effectively leading to a tree that I can dump out.
def treemaker(array = [])
tree = ledgers.group_by{|x|x.master_group}
tree.each{|x,z| tree[x] = z.group_by{|y| y.account_group}}
tree
end
I would then render tree in a way that i can be put into a "tree" javascript plugin.
Is there a more efficient way?
Sample Input: An Array of ActiveRecord objects, where the model contains, fields master_group, account_group and name
Class MyModel < ActiveRecord::Base
validates :master_group, :account_group, :name, :presence => true
end
Sample Ouput:
{"master_group1" => {"account_group1" => ["name1","name2",...],
"account_groupx" => ["name3", "name4",...],
....},
"master_group2" => {"account_group2" => ["namex", "namey"]},
...
}
I'm not specifically looking for an "SQL grouping" solution (but that would be nice too). Just a solution using enumerables on a any given list of ruby objects.
#xaxxon sent me thinking in the right way basically with the "default value of hash" path.
I think i can now add a method to my model where i can use all sorts of scopes and tack on tree at the end to get my models in tree mode.
class MyModel < ActiveRecord::Base
validates :master_group, :account_group, :name, :presence => true
def self.tree(field1 = 'master_group', field2 = 'account_group')
tree = Hash.new{|hash,key| hash[key] = Hash.new{|h,k| h[k] = []}}
all.each do |item|
tree[item.send('field1')][item.send('field2')].push(item)
end
tree # bob's your uncle!
end
end
MyModel.recent.tree => Hash of Hash of arrays
set up some fake data
foo=[{:a=>1,:b=>2},{:a=>3,:b=>4}]
set up the output data structure
tree={}
populate the output data structure - this is weird looking because you have to populate the hashes that don't exist when they don't exist, hence the ||={} stuff.
foo.each{|thing| (tree[thing[:a]]||={})[thing[:b]]=thing}
looks good.. :a is your master group and :b is your account_group
pp tree
{1=>{2=>{:a=>1, :b=>2}}, 3=>{4=>{:a=>3, :b=>4}}}

Why are associated objects through a belongs_to not equal to themselves?

I have two classes with a has_many and belongs_to association:
class Employee < ActiveRecord::Base
has_many :contracts
end
class Contract < ActiveRecord::Base
belongs_to :employee
end
I expect that the employee returned by the #employee method of the Contract class would be equal to itself, which means that the following unit test would pass.
class EmployeeTest < ActiveSupport::TestCase
test "an object retrieved via a belongs_to association should be equal to itself" do
e = Employee.new
e.contracts << Contract.new
assert e.save
a = e.contracts[0].employee
assert a.equal? a
end
end
However, it fails. I do not understand. Is this a bug in ActiveRecord?
Thanks for helping out.
This has to do with object equality. consider this IRB session
irb(main):010:0> Foo = Class.new
=> Foo
irb(main):011:0> f = Foo.new
=> #<Foo:0x16c128>
irb(main):012:0> b = Foo.new
=> #<Foo:0x1866a8>
irb(main):013:0> f == b
=> false
By default, == will test that the two objects have the same type, and same object_id. In activerecord, it is hitting up the database for the first employee, and hitting it up again for the employee through the referencial method, but those are two different objects. Since the object_ids are different, it doesn't matter if they have all the same values, == will return false. To change this behavior, consider this second IRB session
irb(main):050:0> class Bar
irb(main):051:1> attr_accessor :id
irb(main):052:1> def ==(compare)
irb(main):053:2> compare.respond_to?(:id) && #id == compare.id
irb(main):054:2> end
irb(main):055:1> end
=> nil
irb(main):056:0> a = Bar.new
=> #<Bar:0x45c8b50>
irb(main):057:0> b = Bar.new
=> #<Bar:0x45c2430>
irb(main):058:0> a.id = 1
=> 1
irb(main):059:0> b.id = 1
=> 1
irb(main):060:0> a == b
=> true
irb(main):061:0> a.id = 2
=> 2
irb(main):062:0> a == b
=> false
Here I defined the == operator to compare the .id methods on the two objects (or to just return false if the object we are comparing doesn't have an id method). If you want to compare Employees by value like this, you will have to define your own == method to implement it.
That is probably because the rails internal cached differently in two association calls. try to do
a.reload.equal? a.reload
this will get rid of the caching and should return true

Resources