I have Credits that belong to Accounts, but are traded back and forth between accounts. I need to be able to keep track of which account a credit currently belongs to, and which account originally created the credit. Currently I have an association like this:
class Credit < ActiveRecord::Base
belongs_to :original_owner, :foreign_key => "account_id", :class_name => "Account"
belongs_to :account, :class_name => Account
end
Now in the rails console, I can set Credit.account.id = 3, and Credit.original_owner.id = 4, but that just changes the "Account id" and doesn't actually change the association. If I set Credit.account = Account.find(3), that works, but I can't set Credit.original_owner to Account.find(4). It indicates that it saved correctly, but it doesn't. It only changes with Credit.account. Every time I change the account, it changes the original_owner along with it.
How can I set up my association so that my Credits belong to an account but still keep track of their original account?
You can't set the foreign key for the original_owner association to account_id because that's the foreign key for the account association. You need to have a distinctly named foreign key for each association. (i.e. original_owner_id)
#credit.account.id = 3 and #credit.original_owner.id = 4 are not correct either. You're attempting to change the id of the associated objects. I assume what you're trying to is modify the association, which is done with #credit.account_id = 3 (using the foreign key), or, as you pointed out #credit.account = Account.find(3) also works if you want to work with objects.
You need to add a new column to your model and map foreign_key to that column.
Related
Given two Rails models with one to one relationship. A WorkItem has one ChemicalSafetyFeature and a ChemicalSafetyFeature belongs to a WorkItem
class WorkItem < ActiveRecord::Base
has_one :chemical_safety_feature
end
class ChemicalSafetyFeature < ActiveRecord::Base
belongs_to :work_item
end
I go on to rails console and I create a WorkItem. I get a WorkItem object with id = 1
WorkItem.create()
Then I create one ChemicalSafetyFeature object like thisand test it out
ChemicalSafetyFeature.create(work_item: WorkItem.first)
ChemicalSafetyFeature.workItem == WorkItem.first
WorkItem.first.chemical_safety_feature == ChemicalSafetyFeature.first
But its surprising to me if I create another ChemicalSafetyFeature and link it to the first work item:
ChemicalSafetyFeature.create(work_item = WorkItem.first)
Even though ChemicalSafetyFeature.first.work_item and ChemicalSafetyFeature.find(2).work_item point to the first WorkItem, the first WorkItem points to only the first ChemicalSafetyFeature object.
My expectations are that when I try to create the second ChemicalSafetyFeature object which is associated with the first WorkItem, it should throw an error. It seems like I can still create two ChemicalSafetyFeature objects which both link to the first WorkItem which means that the first WorkItem has 2 ChenicalSafetyFeature items
A has_one relationship does not enforce that the model with the has_one relationship only belongs to one object with the belongs_to relationship. It does not enforce that there is only a single chemical_safety_features record with that work_item_id.
You can enforce this with a uniqueness validation:
class ChemicalSafetyFeature < ActiveRecord::Base
validates :work_item_id, uniqueness: true
belongs_to :work_item
end
The way this validation works is by querying the database before saving the ChemicalSafetyFeature instance to check if there is already a record with that work_item_id. If a record is found it fails the save. It is still possible to violate this constraint due to a race condition.
Imagine there is no conflicting record in the database and two processes query the database for a record with work_item_id = 5, and neither process finds any records. Then both processes will save a record with work_item_id = 5 violating the constraint.
The solution is to add a unique constraint in the database. This will ensure you cannot have two records with identical work_item_id values.
Create a migration with this add_index call:
add_index :chemical_safety_features, :work_item_id, unique: true
With this index the database will enforce the constraint, and the application code will not be able to violate the constraint.
You should also define work_item_id as a foreign key in the database using an add_foreign_key migration. This will prevent inserting garbage values to that field. It will also prevent orphaned records - you will not be able to delete a record from work_items if there is a reference to that record in the chemical_safety_features table.
Finally, you should tell rails what to do if you call #destroy on a WorkItem instance that belongs to a ChemicalSafetyFeature. Take a look at the dependent option for has_one and has_many relationships.
For example if you specify
class WorkItem < ActiveRecord::Base
has_one :chemical_safety_feature, dependent: :destroy
end
Then calling #destroy on a WorkItem will call destroy on the related ChemicalSafetyFeature. Other values for the dependent option are :delete, :nullify, :restrict_with_exception, and :restrict_with_error.
It is good practice to always specify the dependent option on all has_one and has_many relationships.
I am trying to learn how to write scopes in Rails.
I have models for user, organisation and organisation request. The associations are:
User
has_one :organisation
Organisation
belongs_to :owner, class_name: 'User'
has_many :organisation_requests
Organisation_request
belongs_to :organisation
In my organisation request model, I'm trying to write a scope to pick out all the organisation requests that belong to the organisation's owner.
scope :same_org, where(:organisation_id => owner.organisation_id)
Then in my organisation_requests controller, I have:
def index
#organisation_requests = OrganisationRequest.same_org
end
I tried the above. Owner is an alias for user. In each organisation, one user is nominated as that organisation's owner. I want that user to see an index of the organisation requests that come in for that owner's organisation.
Can anyone see what I've done wrong here? I'm not understanding something about how to write scopes.
In your model, try this:
scope :same_org, -> {where(:organisation_id => owner.organisation_id) }
The upvoted answer is wrong (nothing personal, #Hasmukh Rathod) - there is no organisation_id column in users table (it's actually vice versa - there is a user_id column in organisations table).
I would suggest the following solution:
scope :same_org, ->(owner_id) { joins(organisation: :user).where(users: { id: owner_id }) }
Having the above scope, what you'll need to do, is to pass the owner_id as an argument. I'm sure there is a way to get it working without passing an argument to scope, but I'm not yet sure how (I've just woke up :D).
So example:
owner_id = User.find(1).id
OrganisationRequest.same_org(owner_id) # would give you the expected collection of requests
So I have been trying to create a dummy application to try and learn Rails. The app I thought I could create is a coffee ordering app for a group of people in work.
So the website will have many users.
A user can create a coffee_order.
A coffee order contains orders for other individual users.
Each user can have one or more coffee_shop_items (e.g. latte,
cappuccino,danish, muffin, etc)
A coffee order also has an assignee, this is the person who is tasked
with going and getting the order.
So as a user, I create a coffee order, select an assignee, add users to the order, and add one or more coffee shop items to each user,
I am really struggling with how the database should be, and what the associations need to be, along with any join tables?
I am also trying to use nested attributes for the form entry.
Thanks in advance for help.
Update with some code I have tried to create a coffee order:
#coffee_order = CoffeeOrder.new(coffee_order_params)
params[:coffee_order][:user_coffee_orders_attributes].each do |user_order|
order = #coffee_order.user_coffee_orders.new(user_id: user_order[1][:user_id].to_i)
user_order[1][:coffee_shop_items].each do |item|
coffee_shop_item = CoffeeShopItems.find(item) if item != ""
# this line fails! see error below
#coffee_order.user_coffee_orders.coffee_shop_items << coffee_shop_item if coffee_shop_item != nil
end
end
error:
NoMethodError (undefined method `coffee_shop_items' for #<UserCoffeeOrder::ActiveRecord_Associations_CollectionProxy:0x42c6180>):
The coffee_shop_items belong to the order, not the user. After all, a user could probably create another order another day? You should probably also check out the rails documentation, which, IIRC actually contains a walk-through of a shopping cart application.
User has_many :coffes_orders
User has_many :coffee_orders_he_needs_to_get, class_name: "CoffeeOrder", foreign_key: "assignee_id"
CoffeeOrder belongs_to :user
CoffeeOrder belongs_to :assignee, class_name: "User"
CoffeeOrder has_and_belongs_to_many :coffee_shop_items
Coffee_shop_items has_and_belongs_to_many :coffee_orders
I'm trying to set up a notification system in Rails, along with mongoid (but I don't think this is mongoid specific).
The basic structure is like this - Every notification has a notifier (the one who is responsible for the notification) and a notifee (the one who receives the notification). When a user A comments on user B's post (in a blog system for example), the User A becomes the notifier and the User B is the notifiee.
User.rb
# nothing in here
Notification.rb
has_one :notifier, :class_name => "User"
belongs_to :notifiee, :class_name => "User"
However, when I do:
#notification = Notification.new
#notification.notifier = current_user
#notification.notifiee = User.first #Just for the sake of the example
#notification.save
I get this error:
Problem: When adding a(n) User to Notification#notifier, Mongoid could
not determine the inverse foreign key to set. The attempted key was
'notifiee_id'.Summary: When adding a document to a relation, Mongoid
attempts to link the newly added document to the base of the relation
in memory, as well as set the foreign key to link them on the database
side. In this case Mongoid could not determine what the inverse
foreign key was.Resolution: If an inverse is not required, like a
belongs_to or has_and_belongs_to_many, ensure that :inverse_of => nil
is set on the relation. If the inverse is needed, most likely the
inverse cannot be figured out from the names of the relations and you
will need to explicitly tell Mongoid on the relation what the inverse
is.
What could I be doing wrong? Or, is there a better way to model this??
Any help is much appreciated! Thank you.
You should probably go for the following associations:
User:
has_many :notifications_as_notifier, :class_name=>'Notification', :foreign_key=>'notifier_id'
has_many :notifications_as_notifiee, :class_name=>'Notification', :foreign_key=>'notifiee_id'
Notification:
belongs_to :notifier, :class_name=>'User', :foreign_key=>'notifier_id'
belongs_to :notifiee, :class_name=>'User', :foreign_key=>'notifiee_id'
Your notifications table should have notifier_id and notifiee_id.
Now you can do,
#notification = Notification.new
#notification.notifier = current_user
#notification.notifiee = User.first #Just for the sake of the example
#notification.save
What I find questionable in your setup:
You have,
has_one :notifier, :class_name => "User"
belongs_to :notifiee, :class_name => "User"
When you use has_on, then the other relation (table) must have a foreign key referencing the parent. Here users must have a column notification_id or something. This is impractical because a single user has many notifications (based on your explanations).
Secondly, you are associating Notification to User through two relationships but you are mentioning anything about the foreign key to use to enforce the association.
And why do you not have an inverse relation in the User model? Would it not help if you had access to something like: current_user.notifications_as_notifier ??
Right now I have a simple blog website setup with devise which allows users to edit posts. I also have activeadmin installed on the backend. What I want is when a user signs in and they edit a post I want that users email to be tied to that post. Then I could go into active admin and setup the column to view the user later. Trouble im having is that im not sure how to automatically tag a users email to a specific post when they edit it, also my user and post model are on different tables in the database.
Thanks for any help.
http://guides.rubyonrails.org/association_basics.html#the-has_and_belongs_to_many-association
Just set up a join table for a has_and_belongs_to_many association on each model. This is a standard active record association which should be well-documented; see the link above for a start.
Then in your update method for the PostsController you can add a line like:
#post.users << current_user
(obviously the specific code will vary depending on the names of your variables & associations -- i'd probably rename the association to "editors" or something like that)
I don't know anything about active admin, so I can't tell you how to make these associations viewable there. But it shouldn't be too hard once the association is set up properly.
Two approaches I use to create user_stamps.
paper_trail gem that records all modifications in Version table.
Works great with Active Admin.
Adding updated_by_id and created_by_id columns to all tables (paper_trail needed)
# In each Model.
belongs_to :updated_by, :class_name => "AdminUser", :foreign_key => "updated_by_id"
belongs_to :created_by, :class_name => "AdminUser", :foreign_key => "created_by_id"
after_create { |i| i.update_column(:created_by_id, PaperTrail.whodunnit) }
after_save { |i| i.update_column(:updated_by_id, PaperTrail.whodunnit) }
These columns will be redundant but a great compliment to Version table and is faster and better for many reports and scopes.