i18n for a has_many through self-referential - ruby-on-rails

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.

Related

Association with proc for mongoid

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.

has_many :through with class_name and foreign_key

I'm working with a fairly straightforward has_many through: situation where I can make the class_name/foreign_key parameters work in one direction but not the other. Perhaps you can help me out. (p.s. I'm using Rails 4 if that makes a diff):
English: A User manages many Listings through ListingManager, and a Listing is managed by many Users through ListingManager. Listing manager has some data fields, not germane to this question, so I edited them out in the below code
Here's the simple part which works:
class User < ActiveRecord::Base
has_many :listing_managers
has_many :listings, through: :listing_managers
end
class Listing < ActiveRecord::Base
has_many :listing_managers
has_many :managers, through: :listing_managers, class_name: "User", foreign_key: "manager_id"
end
class ListingManager < ActiveRecord::Base
belongs_to :listing
belongs_to :manager, class_name:"User"
attr_accessible :listing_id, :manager_id
end
as you can guess from above the ListingManager table looks like:
create_table "listing_managers", force: true do |t|
t.integer "listing_id"
t.integer "manager_id"
end
so the only non-simple here is that ListingManager uses manager_id rather than user_id
Anyway, the above works. I can call user.listings to get the Listings associated with the user, and I can call listing.managers to get the managers associated with the listing.
However (and here's the question), I decided it wasn't terribly meaningful to say user.listings since a user can also "own" rather than "manage" listings, what I really wanted was user.managed_listings so I tweaked user.rb to change
has_many :listings, through: :listing_managers
to
has_many :managed_listings, through: :listing_managers, class_name: "Listing", foreign_key: "listing_id"
This is an exact analogy to the code in listing.rb above, so I thought this should work right off. Instead my rspec test of this barfs by saying
ActiveRecord::HasManyThroughSourceAssociationNotFoundError:
Could not find the source association(s) :managed_listing or :managed_listings in model ListingManager. Try 'has_many :managed_listings, :through => :listing_managers, :source => <name>'. Is it one of :listing or :manager?
the test being:
it "manages many managed_listings" do
user = FactoryGirl.build(:user)
l1 = FactoryGirl.build(:listing)
l2 = FactoryGirl.build(:listing)
user.managed_listings << l1
user.managed_listings << l2
expect( #user.managed_listings.size ).to eq 2
end
Now, I'm convinced I know nothing. Yes, I guess I could do an alias, but I'm bothered that the same technique used in listing.rb doesn't seem to work in user.rb. Can you help explain?
UPDATE:
I updated the code to reflect #gregates suggestions, but I'm still running into a problem: I wrote an additional test which fails (and confirmed by "hand"-tesing in the Rails console). When one writes a test like this:
it "manages many managed_listings" do
l1 = FactoryGirl.create(:listing)
#user = User.last
ListingManager.destroy_all
#before_count = ListingManager.count
expect( #before_count ).to eq 0
lm = FactoryGirl.create(:listing_manager, manager_id: #user.id, listing_id: l1.id)
expect( #user.managed_listings.count ).to eq 1
end
The above fails. Rails generates the error PG::UndefinedColumn: ERROR: column listing_managers.user_id does not exist (It should be looking for 'listing_managers.manager_id'). So I think there's still an error on the User side of the association. In user.rb's has_many :managed_listings, through: :listing_managers, source: :listing, how does User know to use manager_id to get to its Listing(s) ?
The issue here is that in
has_many :managers, through: :listing_managers
ActiveRecord can infer that the name of the association on the join model (:listing_managers) because it has the same name as the has_many :through association you're defining. That is, both listings and listing_mangers have many managers.
But that's not the case in your other association. There, a listing_manager has_many :listings, but a user has_many :managed_listings. So ActiveRecord is unable to infer the name of the association on ListingManager that it should use.
This is what the :source option is for (see http://guides.rubyonrails.org/association_basics.html#has-many-association-reference). So the correct declaration would be:
has_many :managed_listings, through: :listing_managers, source: :listing
(p.s. you don't actually need the :foreign_key or :class_name options on the other has_many :through. You'd use those to define direct associations, and then all you need on a has_many :through is to point to the correct association on the :through model.)
I know this is an old question, but I just spent some time running into the same errors and finally figured it out. This is what I did:
class User < ActiveRecord::Base
has_many :listing_managers
has_many :managed_listings, through: :listing_managers, source: :listing
end
class Listing < ActiveRecord::Base
has_many :listing_managers
has_many :managers, through: :listing_managers, source: :user
end
class ListingManager < ActiveRecord::Base
belongs_to :listing
belongs_to :user
end
This is what the ListingManager join table looks like:
create_table :listing_managers do |t|
t.integer :listing_id
t.integer :user_id
end
Hope this helps future searchers.
i had several issues with my models, had to add the foreign key as well as the source and class name... this was the only workaround i found:
has_many :ticket_purchase_details, foreign_key: :ticket_purchase_id, source: :ticket_purchase_details, class_name: 'TicketPurchaseDetail'
has_many :details, through: :ticket_purchase_details, source: :ticket_purchase, class_name: 'TicketPurchaseDetail'

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.

belongs_to through associations

Given the following associations, I need to reference the Question that a Choice is attached through from the Choice model. I have been attempting to use belongs_to :question, through: :answer to perform this action.
class User
has_many :questions
has_many :choices
end
class Question
belongs_to :user
has_many :answers
has_one :choice, :through => :answer
end
class Answer
belongs_to :question
end
class Choice
belongs_to :user
belongs_to :answer
belongs_to :question, :through => :answer
validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
end
I am getting
NameError uninitialized constant User::Choice
when I try to do current_user.choices
It works fine, if I don't include the
belongs_to :question, :through => :answer
But I want to use that because I want to be able to do the validates_uniqueness_of
I am probably overlooking something simple. Any help would be appreciated.
You can also delegate:
class Company < ActiveRecord::Base
has_many :employees
has_many :dogs, :through => :employees
end
class Employee < ActiveRescord::Base
belongs_to :company
has_many :dogs
end
class Dog < ActiveRecord::Base
belongs_to :employee
delegate :company, :to => :employee, :allow_nil => true
end
Just use has_one instead of belongs_to in your :through, like this:
class Choice
belongs_to :user
belongs_to :answer
has_one :question, :through => :answer
end
Unrelated, but I'd be hesitant to use validates_uniqueness_of instead of using a proper unique constraint in your database. When you do this in ruby you have race conditions.
A belongs_to association cannot have a :through option. You're better off caching the question_id on Choice and adding a unique index to the table (especially because validates_uniqueness_of is prone to race conditions).
If you're paranoid, add a custom validation to Choice that confirms that the answer's question_id matches, but it sounds like the end user should never be given the opportunity to submit data that would create this kind of mismatch.
My approach was to make a virtual attribute instead of adding database columns.
class Choice
belongs_to :user
belongs_to :answer
# ------- Helpers -------
def question
answer.question
end
# extra sugar
def question_id
answer.question_id
end
end
This approach is pretty simple, but comes with tradeoffs. It requires Rails to load answer from the db, and then question. This can be optimized later by eager loading the associations you need (i.e. c = Choice.first(include: {answer: :question})), however, if this optimization is necessary, then stephencelis' answer is probably a better performance decision.
There's a time and place for certain choices, and I think this choice is better when prototyping. I wouldn't use it for production code unless I knew it was for an infrequent use case.
So you cant have the behavior that you want but you can do something that feels like it. You want to be able to do Choice.first.question
what I have done in the past is something like this
class Choice
belongs_to :user
belongs_to :answer
validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
...
def question
answer.question
end
end
this way the you can now call question on Choice
It sounds like what you want is a User who has many Questions.
The Question has many Answers, one of which is the User's Choice.
Is this what you are after?
I would model something like that along these lines:
class User
has_many :questions
end
class Question
belongs_to :user
has_many :answers
has_one :choice, :class_name => "Answer"
validates_inclusion_of :choice, :in => lambda { answers }
end
class Answer
belongs_to :question
end
The has_many :choices creates an association named choices, not choice. Try using current_user.choices instead.
See the ActiveRecord::Associations documentation for information about about the has_many magic.

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