inserting data through HABTM - ruby-on-rails

I have a HABTM association between Users and Workspaces, which also has groups. The problem is I can't insert data intro groups from users.
class User < ActiveRecord::Base
has_and_belongs_to_many :workspaces #, dependent: :destroy
has_many :groups, through: :workspaces
end
class Workspace < ActiveRecord::Base
has_and_belongs_to_many :users, dependent: :destroy
has_many :groups, dependent: :destroy
end
This is the join table migration:
class CreateUsersAndWorkspaces < ActiveRecord::Migration
def change
create_table :users_workspaces, id: false do |t|
t.belongs_to :user, index: true
t.belongs_to :workspace, index: true
end
end
end
In rails console, when I try to create new group:
u.groups.create!(name: "txt", workspace_id: 1 )
(0.1ms) begin transaction
(0.2ms) rollback transaction
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly:
Cannot modify association 'User#groups' because it goes
through more than one other association.
Is there a way to create groups from the user?
EDIT:
With help from #evedovelli I could make it work. but since user.workspace was a ActiveRecord_Associations_CollectionProxy, it was not a workspace class, but a collection. appending 'first', solved the problem, so the final code is:
u.workspaces(id: 1).first.groups.create!(name: "txt")
Now suppose I have more associations:
u.workspaces(id: 1).first.groups(id: 2).first.modelN(id:3).first.modelNplus1.create!(name: "txt")
my final question is, is this the correct way?

The problem is that Rails cannot find out to what user's workspace the group should be added to (even if you to specify the worspace_id on creation as you did).
As mentioned in the error message, the nested associations for HasManyThrough are read only. So you can read groups directly from an user, but you can't create them.
Since you known the workspace_id when creating the group, you can simply create the group with:
u.workspaces.find_by_id(1).groups.create!(name: "txt")
This way it will create the group for the right user's workspace.
And you should be able to keep going with that through other associations like this:
u.workspaces.find_by_id(1).groups.find_by_id(2).modelN.find_by_id(3).modelNplus1.create!(name: "txt")

Related

convert "has many, through" association to simply a "belongs to" association

I wrote a rails program for a non-profit to help track encounters. Originally the thought was that a single encounter might deliver multiple services, hence the setup:
class Encounter < ApplicationRecord
has_many :encounters_services, dependent: :destroy, inverse_of: :encounter
has_many :services, through: :encounters_services
accepts_nested_attributes_for :encounters_services
class Service < ApplicationRecord
has_many :encounters, :through => :encounters_services
has_many :encounters_services, dependent: :destroy, inverse_of: :service
The end user has now figured out that they only want to associate a single service with an encounter. But there's already a lot of data in the database under the original structure. Is there a clean way to convert it to a scenario where a Service "has many" Encounters, and an Encounter "belongs to" a Service, without messing up the data that's already stored in the database in the "EncounterServices" table?
Thanks! I'm still a newbie so I appreciate the help!
I guess you could add a new migration to add a new column to the services and add a little script to set the value from EncountersServices.
Something like
def change
add_column :services, :encounter_id, :integer, index: true
Service.each do |s|
s.update_column :encounter_id, s.encounters.first.id
end
end
You can leave the previous data untouched. Since the old association's data has it's own table, your models' tables won't have garbage.
EDIT: I understood the relationship the wrong way, if an encounter should belong yo a service, the migration would look like this:
def change
add_column :encounters, :service_id, :integer, index: true
Encounter.each do |e|
e.update_column :service_id, e.services.first.id
end
end

HABTM relationship, How to input multiple values for id?

I have a User model which has_many Portfolios, which has_many Assets which has_and_belongs_to_many AssetHistories.
Basically User 1 might have Google in their portfolio and User 2 might also have Google in their portfolio. Why populate the database with duplicate lines of stock price history for Google when I can have a many-to-many (HABTM) relationship. However what throws me off is what to put for asset_id in the AssetHistory model when it will be multiple values. I.e. it needs to reference both user 1 and user 2. User 1's Google might be asset.id 1 and User 2's Google might be asset.id 2. Therefore how do the entries in the AssetHistory model reference both the ids?
It seems pretty clear that asset_id can't be 2 values simultaneously but I can't wrap my head around this. Am I supposed to use a foreign_key and make Google the key? If so, I still have issues in my Asset model for what entry to put for Asset_History_id, because the asset Google, will have maybe 30 lines of stock price history. Each stock price history would be a different Asset_History_id.
Can someone help explain what I'm doing wrong?
Note that I am using after_save in my asset model to populate the asset price histories. I.e. when someone adds an Asset, it populates the asset_history, but it doesn't populate the asset_history_id field in the Asset model and it doesn't populate the asset_id in the AssetHistory model because I'm at a lost on what to do there.
My asset model has:
class Asset < ActiveRecord::Base
attr_accessible :asset_symbol, :shares, :cost, :date_purchased, :asset_history_id
belongs_to :portfolio
has_and_belongs_to_many :asset_histories
after_save populatepricehistory
private
def populatepricehistory
#uses an api to download price data as an array and imports it to AssetHistory...
#I expect something should go here to fill out the asset_history_id field in the Asset Model
#while simultaneously filling out the asset_id in the AssetHistory model
end
end
Asset History model
class AssetHistory < ActiveRecord::Base
attr_accessible :close, :date, :asset_id, :asset_symbol
has_and_belongs_to_many :assets
end
Migration for AssetHistoryAsset join table
class AssetHistoryAssetJoinTable < ActiveRecord::Migration
def up
create_table :asset_histories_assets, :id=> false do |t|
t.integer :asset_id
t.integer :asset_history_id
end
end
def down
drop_table :asset_histories_assets
end
end
My suggestion would be this:
class User < ActiveRecord::Base
has_many :assets, :through => :porfolios
has_many :porfolios
end
class Porfolio < ActiveRecord::Base
has_many :assets
has_many :users
end
class Asset < ActiveRecord::Base
has_many :users, :through => :portfolios
has_many :portfolios
has_and_belongs_to_many :asset_histories
end
By the way, do you really need a many-to-many relationship between Asset and AssetHistory? I would imagine each instance of AssetHistory to refer to only one Asset, probably by means of belongs_to :asset / has_one :asset_history.

accept_nested_attributes_for in a many-to-many relationship

I have tried to find a solution for this but most of the literature around involves how to create the form rather than how to save the stuff in the DB. The problem I am having is that the accepts_nested_attributes_for seems to work ok when saving modifications to existing DB entities, but fails when trying to create a new object tree.
Some background. My classes are as follows:
class UserGroup < ActiveRecord::Base
has_many :permissions
has_many :users
accepts_nested_attributes_for :users
accepts_nested_attributes_for :permissions
end
class User < ActiveRecord::Base
has_many :user_permissions
has_many :permissions, :through => :user_permissions
belongs_to :user_group
accepts_nested_attributes_for :user_permissions
end
class Permission < ActiveRecord::Base
has_many :user_permissions
has_many :users, :through => :user_permissions
belongs_to :user_group
end
class UserPermission < ActiveRecord::Base
belongs_to :user
belongs_to :permission
validates_associated :user
validates_associated :permission
validates_numericality_of :threshold_value
validates_presence_of :threshold_value
default_scope order("permission_id ASC")
end
The permission seem strange but each of them has a threshold_value which is different for each user, that's why it is needed like this.
Anyway, as I said, when I PUT an update, for example to the threshold values, everything works ok. This is the controller code (UserGroupController, I am posting whole user groups rather than one user at a time):
def update
#ug = UserGroup.find(params[:id])
#ug.update_attributes!(params[:user_group])
respond_with #ug
end
A typical data coming in would be:
{"user_group":
{"id":3,
"permissions":[
{"id":14,"name":"Perm1"},
{"id":15,"name":"Perm2"}],
"users":[
{"id":7,"name":"Tallmaris",
"user_permissions":[
{"id":1,"permission_id":14,"threshold_value":"0.1"},
{"id":2,"permission_id":15,"threshold_value":0.3}]
},
{"name":"New User",
"user_permissions":[
{"permission_id":14,"threshold_value":0.4},
{"permission_id":15,"threshold_value":0.2}]
}]
}
}
As you can see, the "New User" has no ID and his permission records have no ID either, because I want everything to be created. The "Tallmaris" user works ok and the changed values are updated no problem (I can see the UPDATE sql getting run by the server); on the contrary, the new user gives me this nasty log:
[...]
User Exists (0.4ms) SELECT 1 AS one FROM "users" WHERE "users"."name" = 'New User' LIMIT 1
ModelSector Load (8.7ms) SELECT "user_permissions".* FROM "user_permissions" WHERE (user_id = ) ORDER BY permission_id ASC
PG::Error: ERROR: syntax error at or near ")"
The error is obviously the (user_id = ) with nothing, since of course the user does not exists, there are no user_permissions set already and I wanted them to be created on the spot.
Thanks to looking around to this other question I realised it was a problem with the validation on the user.
Basically I was validating that the threshold_values summed up within certain constraints but to do that I was probably doing something wrong and Rails was loading data from the DB, which was ok for existing values but of course there was nothing for new values.
I fixed that and now it's working. I'll leave this here just as a reminder that often a problem in one spot has solutions coming from other places. :)

Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations

I have 3 models: Question, Option, Rule
Question has_many options;
Option needs a foreign key for question_id
Rule table consists of 3 foreign_keys:
2 columns/references to question_ids -> foreign keys named as 'assumption_question_id' and 'consequent_question_id'
1 column/reference to option_id -> foreign key named as option_id or condition_id
Associations for Rule:
Question has_many rules; and
Option has_one rule
I want to understand how to write up migrations for this, and how that associates to the 'has_many'/'belongs_to' statements I write up in my model, and the ':foreign_key' option I can include in my model.
I had this for my Option migration, but I'm not sure how the "add_index" statement works in terms of foreign keys, and how I can use it for my Rule migration: (my Question and Options models have appropriate has_many and belongs_to statements - and work fine)
class CreateOptions < ActiveRecord::Migration
def change
create_table :options do |t|
t.integer :question_id
t.string :name
t.integer :order
t.timestamps
end
add_index :options, :question_id
end
end
Thank you for the help!
Note: I have found this way to solve the problem.Kindness from China.
If you have RailsAdmin with you,you may notice that you can see all rules of one question as long as one field of both question fields(assumption_question_id,consequent_question_id) equals to id of the question.
I have done detailed test on this and found out that Rails always generates a condition "question_id = [current_id]" which make to_sql outputs
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170
And the reason that the following model
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
makes Question.take.rules.to_sql be like this
SELECT `rules`.* FROM `rules` WHERE `rules`.`question_id` = 170 AND (assumption_question_id = 170 OR consequent_question_id = 170)
Is that we have not yet get ride of the annoy question_id so no matter how we describe or condition properly, our condition follows that "AND".
Then,we need to get ride of it.How?
Click here and you will know how,Find sector 8.1,and you can see
Article.where(id: 10, trashed: false).unscope(where: :id)
# SELECT "articles".* FROM "articles" WHERE trashed = 0
Then lets do it:
class Question < ActiveRecord::Base
has_many :options
# Notice ↓
has_many :rules, ->(question) { unscope(where: :question_id).where("assumption_question_id = ? OR consequent_question_id = ?", question.id, question.id) }, class_name: 'Rule'
# Notice ↑
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
All done.
Finally
This is my first answer here at stackoverflow,and this method is never found anywhere else.
Thanks for reading.
add_index adds an index to column specified, nothing more.
Rails does not provide native support in migrations for managing foreign keys. Such functionality is included in gems like foreigner. Read the documentation that gem to learn how it's used.
As for the associations, just add the columns you mentioned in your Question to each table (the migration you provided looks fine; maybe it's missing a :rule_id?)
Then specify the associations in your models. To get you started
class Question < ActiveRecord::Base
has_many :options
has_many :assumption_rules, class_name: "Rule"
has_many :consequent_rules, class_name: "Rule"
end
class Rule < ActiveRecord::Base
belongs_to :option
belongs_to :assumption_question, class_name: "Question", foreign_key: :assumption_question_id, inverse_of: :assumption_rules
belongs_to :consequent_question, class_name: "Question", foreign_key: :consequent_question_id, inverse_of: :consequent_rules
end
class Option < ActiveRecord::Base
belongs_to :question
has_one :rule
end
Note This is just a (untested) start; options may be missing.
I strongly recommend you read
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://guides.rubyonrails.org/association_basics.html
Edit: To answer the question in your comment
class Option < ActiveRecord::Base
belongs_to :question
# ...
The belongs_to tells rails that the question_id column in your options table stores an id value for a record in your questions table. Rails guesses the name of the column is question_id based on the :question symbol. You could instruct rails to look at a different column in the options table by specifying an option like foreign_key: :question_reference_identifier if that was the name of the column. (Note your Rule class in my code above uses the foreign_key option in this way).
Your migrations are nothing more than instructions which Rails will read and perform commands on your database based from. Your models' associations (has_many, belongs_to, etc...) inform Rails as to how you would like Active Record to work with your data, providing you with a clear and simple way to interact with your data. Models and migrations never interact with one another; they both independently interact with your database.
You can set a foreign key in your model like this:
class Leaf < ActiveRecord::Base
belongs_to :tree, :foreign_key => "leaf_code"
end
You do not need to specify this in a migration, rails will pull the foreign key from the model class definition.

Rails updating a many to many record

I'm getting stuck at trying to update an existing many to many record.
Project model:
class Project < ActiveRecord::Base
belongs_to :assignment
belongs_to :programmer
end
Programmer model:
class Programmer < ActiveRecord::Base
has_many :projects
has_many :assignments, :through => :projects
end
Assignment model:
class Assignment < ActiveRecord::Base
has_many :projects
has_many :programmers, :through => :projects
end
so I have data linked up like so:
p = Programmer.create(:name => "Mike")
p.assignments.create(:name => "homework4")
p.assignments[0] = Assignment.find_or_create_by_name("homework1")
p.save
so as you can see, I'm trying to update the association of Mike's first hw to "homework1". All the homework assignments are already in the assignments table so it shoud just find "homework1" and assign it to mike. unfortunately, when I type the third line there are no errors, but it doesn't update it. In memory, p.assignments == homework1, but in the DB it's still the same(even after p.save). The project's join table isn't changed at all.
the logs of mysql show this command being generated whenever I enter the 3rd line.
SELECT "assignments".* FROM "assignments" WHERE "assignments"."name" = 'homework1' LIMIT 1
there's no Update anywhere.... what am I doing wrong?
UPDATE
So I found out that I could just reference the join table directly to edit the links. Something along the lines of:
proj = p.projects.first
proj.assignment_id = 12
proj.save!
If you just want a reference to the object, then you need to edit your migration scripts (db/migrate). An example:
def self.up
create_table :configurations do |t|
t.string :name
t.references :project # This store just the id of the object.
t.timestamps
end
end
Don't forget to type:
rake db:migrate

Resources