Association with proc for mongoid - ruby-on-rails

I saw in sources of Mongoid that no proc can be send to association method, what is best practice to achieve below AR association with Mongoid:
class Task
...
belongs_to :creator, ->{where(type: :manager)}, class_name: "User"
belongs_to :acceptor, ->{where(type: :acceptor)}, class_name: "User"
end

It seems I'm find answer. Relations methods in Mongoid accepts block as third argument.
belongs_to :creator, class_name: "User", inverse_of: :created_tasks do
->{ where(type: :manager)}
end
belongs_to :executor, class_name: "User", inverse_of: :accepted_tasks do
->{where(type: :acceptor)}
end

At least for has_many relations I've been having trouble getting this working with a default filter in mongo, so I wonder if your answer is actually working correctly for belongs_to. This has been working correctly on the has_many side for me so I figured it might be a useful answer for someone else as well:
belongs_to :user do
def creator
where(type: :manager)}
end
def executor
where(type: :acceptor)
end
end
They can then be accessed by saying task.user.accepted, task.user.created, etc, however I have been unable to figure out how to actually set the default filtering for the overall relation.

Related

how can I do inverse of self-reference with rails

I have the user model like :
has_many :users, class_name: 'User'
belongs_to :master_user, class_name: 'User', optional: true, inverse_of: :users
I Would like to find :
User.first.master_user it's ok
MasterUser.users but get an error : "NameError: uninitialized constant MasterUser"
You are getting that error because you have not defined a MasterUser model. I am guessing you only have a User model as described in your question. If you want to find the users belonging to a "master_user" then you need to find a "master_user" first, then request its users. It would look something like this:
user_with_a_master = User.where.not(master_user_id: nil).first
master = user_with_a_master.master_user
master_users = master.users
Here is an example of how to properly setup a self-referential association with a bit less confusing naming:
class User < ApplicationRecord
belongs_to :manager
class_name: 'User', # requied here since it cannot be derided from the name
optional: true,
inverse_of: :subordinates
has_many :subordinates,
class_name: 'User', # requied here since it cannot be derided from the name
foreign_key: :manager_id, # what column on the users table should we join
inverse_of: :manager
end
"Managers" here are not a separate class. While you could use single table inheritance to that purpose you should probally get the basics figured out first. Even if you did have a MasterUser class you would get NoMethodError since you're calling .users on the class and not an instance of the class - that will never work and is a very common beginner misstake.
Note that this strictly speaking would actually work without the inverse_of: option which is really just used to explicity set the two way binding in memory.
So in your case it should look like:
class User < ApplicationRecord
# class_name isn't required since it can be derided from the name
has_many :users,
foreign_key: :master_user_id,
inverse_of: :master_user
belongs_to :master_user,
class_name: 'User', # requied here since it cannot be derided from the name
optional: true,
inverse_of: :users
end
Note that the users table must have a master_user_id column.

i18n for a has_many through self-referential

I don't pretend to begin understanding i18n and have not had to use it often but here is the concept.
My User class:
class User < ActiveRecord::Base
has_many :parent_relationships, class_name: 'UserRelationship', foreign_key: 'parent_id'
has_many :user_relationships, class_name: 'UserRelationship', foreign_key: 'user_id'
has_many :dealers, through: :parent_relationships
has_one :agent, through: :user_relationships
...
end
My UserRelationships class:
class UserRelationship < ActiveRecord::Base
attr_accessible :end_date, :parent_id, :start_date, :user_id
belongs_to :agent, class_name:'User',conditions:["user_type='Agent' AND id=?",:parent_id]
belongs_to :dealer, class_name:'User',conditions:["user_type='Dealer' AND id=?",:user_id]
end
I want User.model_name.human to return "User" but when the agent association is referenced I would like it to return "Agent".
I have tried a few things none of which have any success and would be less than useful posting here as they were just stabs in the dark. So my question is 2 fold. Is this possible and if so can someone explain the implementation so I have a better understanding of i18n.
My reason for doing this is I have a ransack form which searches Users but I also would like to have it search Agent as a optgroup and right now it outputs 2 User opt groups This github issue seems to suggest it is possible but the poster did not explain the implementation.
I have figured it out ransack uses it's own namespace for associations in translate.rb line 53
defaults = key.blank? ? [:"#{context.klass.i18n_scope}.models.#{context.klass.model_name.singular}"] : [:"ransack.associations.#{context.klass.model_name.singular}.#{key}"]
So the i18n translation would be:
ransack:
associations:
user:
agent: "Agent"
In case anyone else comes across this issue.

Rails: How can this be done with a has_one association?

My model has two columns, one named foo_id and the other bar_id.
I'm wondering if it's possible to turn these two simple methods into has_one associations:
class SomeModel < ActiveRecord::Base
def foobar_foo
Foobar.find( self.foo_id )
end
def foobar_bar
Foobar.find( self.bar_id )
end
end
Perhaps I've been staring at the documentation for too long, but I can't seem to find a way to tell Rails to use self.foo_id as the foreign key for the other model.
I'm aware that in most cases this should instead be a has_many :through or maybe a belongs_to, but for the sake of argument I'm interested to learn if this is possible with a has_one
Does this help?
has_one :foo, class_name: "Foobar", foreign_key: :foo_id
has_one :bar, class_name: "Foobar", foreign_key: :bar_id
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
Finally had a chance to revisit this problem and incidentally, stumbled across a solution within the first few minutes of trying..
has_one :bar, primary_key: :bar_id, foreign_key: :id, class_name: 'Foobar'
has_one :foo, primary_key: :foo_id, foreign_key: :id, class_name: 'Foobar'
Works as intended.

Retrieve default belongs_to record from a built has_many association in a before_create hook?

I have 2 models, User and Stash.
A User has many stashes, and a default_stash. A Stash has one owner (User).
Before a User is created I want it to build a Stash AND assign that Stash to self.default_stash. As per the instructions here Rails - Best-Practice: How to create dependent has_one relations I created the Stash relation, however I cannot 'create' the belongs_to relation at the same time.
User.rb
has_many :stashes, foreign_key: :owner_id
belongs_to :default_stash, class_name: 'Stash', foreign_key: :owner_id
before_create :build_default_stash
def build_default_stash
self.default_stash = stashes.build
true
end
Stash.rb
belongs_to :owner, class_name: 'User'
At this point the Stash can be found in user.stashes but user.default_stash remains nil, as stashes.build does not return an id in the before_create block.
What I need can be achieved by adding the following to User.rb
after_create :force_assign_default_stash
def force_assign_default_stash
update_attribute(:default_stash, stashes.first)
end
But I'd much prefer to keep everything within the before_create block if possible, for validations etc.
I agree with you that what you describe should work, but if you're building associated records in memory and expecting them to save together properly -- with everything hooked up -- then ActiveRecord is particularly finicky about how you define them.
But you can make it work without changing a thing in your before_create.
The usual problem is that you need to give AR hints about which relationships are inverses of each other. The has_many and belongs_to methods take an :inverse_of option. The problem in your case is that you have one side of a relationship (Stash#owner) that is actually the inverse of two on the other (User#stashes and User#default_stash), so what would you set as the inverse_of for Stash#owner?
The solution is to add a has_one to Stash (call it something like owner_as_default), to balance things out. Then you can add inverse_of to all of the definitions, each identifying its inverse on the other side. The end result is this:
class User < ActiveRecord::Base
has_many :stashes, foreign_key: :owner_id, inverse_of: :owner
belongs_to :default_stash, class_name: "Stash", inverse_of: :owner_as_default
...
end
class Stash < ActiveRecord::Base
belongs_to :owner, class_name: "User", inverse_of: :stashes
has_one :owner_as_default, class_name: "User",
foreign_key: :default_stash_id, inverse_of: :default_stash
end
(Also, you don't need a foreign key on your belongs_to.)
From there, your before_create should work as you've written it.
Yes, it seems that this is a lot of redundant defining. Can't ActiveRecord figure it all out from the foreign keys and whatnot? I always feel like I'm breaking some walls by making one side so aware of what it's the inverse of. Maybe in some future version of Rails this will be ironed out.

Rails has_many with alias name

In my User model I could have:
has_many :tasks
and in my Task model:
belongs_to :user
Then, supposing the foreign key 'user_id' was stored in the tasks table, I could use:
#user.tasks
My question is, how do I declare the has_many relationship such that I can refer to a User's Tasks as:
#user.jobs
... or ...
#user.foobars
Thanks a heap.
Give this a shot:
has_many :jobs, foreign_key: 'user_id', class_name: 'Task'
Note, that :as is used for polymorphic associations.
Also, foreign_key option for has_many.
You could also use alias_attribute if you still want to be able to refer to them as tasks as well:
class User < ActiveRecord::Base
alias_attribute :jobs, :tasks
has_many :tasks
end
If you use has_many through, and want to alias:
has_many :alias_name, through: :model_name, source: :initial_name
(thanks for the correction Sami Birnbaum)
To complete #SamSaffron's answer :
You can use class_name with either foreign_key or inverse_of. I personally prefer the more abstract declarative, but it's really just a matter of taste :
class BlogPost
has_many :images, class_name: "BlogPostImage", inverse_of: :blog_post
end
and you need to make sure you have the belongs_to attribute on the child model:
class BlogPostImage
belongs_to :blog_post
end

Resources