Additional Foreign Key on a Join Table - mysql

I have a join table joining a Client and a Setor (plural: setores) models. The relation is that a Client has_and_belongs_to_many Setores (there are three Setores, a Client can have from one to all three of them. Setores have lots of Clients).
My problem is:
In this join table I added a reference to the User model. Setores have many Users, but a relation between one client and one setor have only one user. But I don't know how to read and write this association on the clients_setores table.
My models are as follows:
class Client < ActiveRecord::Base
has_and_belongs_to_many :documents
has_and_belongs_to_many :setores
has_many :screenings
has_many :contacts
has_many :interactions, through: :contacts
validates :cnpj, uniqueness: true
class Setor < ActiveRecord::Base
self.table_name = 'setores'
has_many :documents
has_many :screenings
has_many :users
has_and_belongs_to_many :clients
attr_acessor :user_id
class User < ActiveRecord::Base
has_one :setores
has_many :clients
And the current join tables parameters that have been working appear like this on the end of Clients controller:
private
# Use callbacks to share common setup or constraints between actions.
def set_client
#client = Client.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def client_params
params.require(:client).permit(:cnpj, :pacote, :razsoc, :vencimento, user_ids:[], document_ids:[], setor_ids:[])
end
Note that "user_ids:[]" was my attempt to get it to work, which has failed so far.
In the views I use the current join tables like this (taken from /client/_form.html.erb):
<%= f.collection_check_boxes :setor_ids, Setor.where(pacote: true), :id, :setor do |b| %>
So with these checkboxes I can create an entry in the clients_setores table.
What I want to do is to be able to pick a User that belongs to a given setor_id from a drop down menu and store this relation in the join table. I did manage to make such menu appear with the following code, in the same _form.html.erb:
<%= f.collection_select :user_ids, User.where(:setor_id => 1), :id, :email %>
But when I submit the form, the values are not saved. I don't know if my problem is just that I didn't get the right way to record this association on my view or if my problem is further down in the controller (probably) and model (maybe).
Closest problem I found in SO was Rails 4 Accessing Join Table Attributes, but the association type is different (has_many/through) and there isn't a third relation involved, so I couldn't figure out how to make it work for me.

Example of many-to-many associations between three models, done with a single join table:
I start out by generating some models:
rails g model User; rails g model Setor; rails g model Client;
rails g model Joiner user_id:integer setor_id:integer client_id:integer
By the way, references is a way to add a foreign key that refers to an existing model. I.e. user:references will create a user_id column. It also adds an "index" to the foreign key, which improves the performance.
Then I add some associations to the classes
class Joiner
# columns: user_id, setor_id, client_id
belongs_to :user
belongs_to :setor
belongs_to :client
end
class User
has_many :joiners
has_many :setors, through: :joiners, source: :setor
has_many :clients, through: :joiners, source: :client
end
class Setor
has_many :joiners
has_many :users, through: :joiners, source: :user
has_many :clients, through: :joiners, source: :client
end
class Client
has_many :joiners
has_many :users, through: :joiners, source: :user
has_many :setors, through: :joiners, source: :setor
end
With this code written, you have many-to-many associations for the three models.
You can then write:
User.create; Setor.create;
Joiner.create(user_id: User.first.id, setor_id: Setor.first.id);
User.first.setors.length # => 1
This won't work for self-joins (i.e. a nested comment system), by the way, but that's not part of the question.

What I ended up doing to achieve the desired effect was to add three columns to the Client model:
userset1
userset2
userset3
This way a Client has up to three (since the field can be blank) relations with users, according to the Setores (departments) he has at his service.
This is the easiest solution in this case since I only have 3 Setores. It would not be for larger numbers.
If I had more than 3 Setores, making it impractical to keep adding columns in the Client table, I would cerate a new model just to store the relations, in this case, a table that would have, besides its unique ID, client_id, setor_id and user_id.

Related

How to query the inverse of a self-referential "has_many :through" relationship in Rails?

Our Rails application uses a self-referential has_many relationship on our User class to track followings. This works to find followed_users:
class User < ApplicationRecord
has_many :followings
has_many :followed_users, through: :followings
end
class Following < ApplicationRecord
belongs_to :user
belongs_to :followed_user, class_name: 'User'
end
I was specifically asked to create a has_many :follower_users. I cannot seem to be able to generate the correct query to get the inverse. The closest I have come is an instance method which works
def followers
User.includes(:followings).where followings: { followed_user_id: id }
end
but I was told to query the inverse through a has_many, not an instance method.
Is this possible?
I solved it after I read this post:
has_many :followers, foreign_key: :followed_user_id, class_name: 'Following'
has_many :follower_users, through: :followers, source: :user
I had a misunderstanding in how I first had to define the join relationship, then (in effect) follow that join through to the User class. It was my faulty assumption that I could directly describe the join and through in a single statement.

Rails: Creating a survey data structure with variable type answers

I'm creating a survey-answer data structure in my Rails app to collect user information. I'll be asking a mixture of multiple choice, number field and open ended questions so my (MySQL) data structure will need to cope with a variable data-type. How can I achieve this? My current plan is based on a previous answer here:
User model (the particpant)
Survey model (the survey)
Question model (the question asked)
Choice model (possible choice for question stored in 'text' column)
Answer model (the answer that links the choice to the participant user)
This works great for just multiple choice text answers with checkboxes, but what if I want an answer to be an integer field (e.g. "What is your age?") or an open ended text field (e.g. "What could be improved?")?
Different type fields
I imagine that a Choice or Answer model with multiple columns for each possible type (e.g. text, integer, datetime, etc.) would be bad as it would be incredibly sparse.
Would multiple Choice tables for each type be better? (e.g. Choice_Text, Choice_Integer etc)
But then how would the Answer model link to the right table?
Open ended unique answers
Should I store unique text answers in the Answer model as another data column, or in the Choice model as a new entry each time?
Any help would be much appreciated. Cheers!
So I ended up using a polymorphic association to link the different types of input, and dealt with open ended text answers by adding them to the Choice_Text table.
If anyone comes across this in the future, the data structure was as follows:
class Survey < ActiveRecord::Base
has_many :survey_questions
has_many :survey_attempts
end
class SurveyAttempt < ActiveRecord::Base
has_many :survey_answers
belongs_to :survey
belongs_to :user
end
class SurveyQuestion < ActiveRecord::Base
has_many :survey_choices
belongs_to :survey
end
class SurveyChoice < ActiveRecord::Base
has_many :survey_answers
belongs_to :survey_question
belongs_to :survey_choice_value, polymorphic: true
end
class SurveyChoiceString < ActiveRecord::Base
has_many :survey_choices, as: :survey_choice_value
has_many :survey_question, through: :survey_choices
end
class SurveyChoiceText < ActiveRecord::Base
has_many :survey_choices, as: :survey_choice_value
has_many :survey_question, through: :survey_choices
end
class SurveyChoiceInteger < ActiveRecord::Base
has_many :survey_choices, as: :survey_choice_value
has_many :survey_question, through: :survey_choices
end
class SurveyAnswer < ActiveRecord::Base
belongs_to :survey_choice
belongs_to :survey_attempt
end

Rails query on different models in a multi-level association chain

I have a multi-level model structure, and I am trying to create a query where I can search for either an attribute on the main parent model, or the last model in the associatioon change. My structure looks like the following:
class Parent
attr_accessible :pname
has_one :child
end
class Child
has_many :infos
end
class Info
has_one :setting
end
class Setting
has_many :subsettings
end
class Subsetting
attr_accessible :sname
end
What I am trying to do is create a where query, where i can pull all parents where either the "pname = X" or "sname = X". However, I am unsure of how to create the associations that deep: Is there a way I can do it cleanly using active record, or is it better to create a mysql query directly?
I'm typing this freehand, so it's possible this won't be 100%, but you should be able to do something like the following...
class Parent
has_one :child
has_many :infos, through: :child
has_many :settings, through: infos
has_many :subsettings, through: :settings
...
end
Then you should be able to call...
Parent.joins(:subsettings).where("parent.pname = ? OR subsetting.sname =?", x)
Two things to note about the .where() call:
Because you are querying against multiple joined tables, you need to preface the column with the table name.
The table name is singular, not plural. That's why subsetting and not subsettings.

Unable to make sense of database data in Rails console

I'm trying to gather a set of data for current_user so in my controller code I have:
#user_workouts = current_user.planned_workouts
with planned_workouts being the join table b/w the user and the workouts. When I test that in console:
#user_workouts.count
I get 4.
When I do:
PlannedWorkout.count
I get 4.
But when I try using a .each loop for my #user_workouts:
#user_workouts.each do |user_workout|
puts user_workout
end
I get far more than 4, I get something like ~25, how is that possible when I tested above how many planned_workouts there are in the table and for the user themselves? I also tried deleting all of the planned_workouts so that the table is empty and created 4 new ones, and still the same outcome. Can it have anything to do with the fact that I'm using add_index in the schema?
EDIT:
My model associations:
User.rb
has_many :planned_workouts
has_many :workouts, through: :planned_workouts
Workout.rb
has_many :planned_workouts
has_many :users, through: :planned_workouts
PlannedWorkout.rb
belongs_to :workout
belongs_to :user

Database and model setup in Ruby On Rails

I'm pretty new to database setup in Ruby and need help in setting it up correctly along with the models. Can anyone help?
Basically I have an Organisation, which will have at least 2 types of Users - Member and Admin User. Both the Organisations and the Users have an Address.
I was thinking this was basically three tables - Organisation, User and Address, but then got really confused when trying think about models and foreign keys.
Can anyone suggest the best way to organise this?
I'm running Rails 3 with a mySql database.
Thanks for your time
Sniffer
I like a lot of Adam Tanner's answer, but I would set it up a little differently. First, the way an Organization associates with admins doesn't work as described - you'd have to have a different foreign key in your user table, and specify that in the has_one :admin association. But I don't think that's a good path anyway, because it limits you to one admin per organization, and limits a user belonging to one organization.
My version is slightly more complicated, but I think it gets the job done well. First, admin should be a role that a user has or doesn't have with an organization. I'll address the user/org issue first, and save the address issue for later.
Here are the migrations, which you can enhance with whatever other fields they need:
create_table :organizations do |t|
# your fields go here
end
create_table :users do |t|
# your fields go here
end
create_table :memberships do |t|
t.integer :user_id
t.integer :organization_id
t.boolean :is_admin
end
add_index :memberships, [:user_id, :organization_id]
As you can see, we're adding a memberships table, which is going to connect users and organizations. We also add an index to speed up the association a little. Now for the models:
class Organization < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class User < ActiveRecord::Base
has_many :memberships
has_many :organizations, :through => :memberships
def membership_in organization
self.memberships.detect{|m| m.organization = organization}
end
def is_admin_for? organization
self.membership_in(organization).is_admin?
end
def set_admin_for organization, value
self.membership_in(organization).update_attribute(:is_admin, value)
end
end
class Membership < ActiveRecord::Base
belongs_to :organization
belongs_to :user
end
Here, we're connecting our users and organizations through memberships. A user can be an admin for any of the organizations they belong to. I've created a few methods to set and get the admin status of a user in an organization, in the user model.
Next the addresses: I've already tackled this one in a blog post of mine:
http://kconrails.com/2010/10/19/common-addresses-using-polymorphism-and-nested-attributes-in-rails/
If you have any questions, please ask. Good luck!
UPDATE
Edward M. Smith pointed out in the comments that my admin methods aren't very fault-tolerant. I was trying to keep the code as clean as possible for the example, but he has a point. So here's the beefier version that accounts for trying to use a membership in an organization the user isn't part of:
def is_admin_for? organization
membership = self.membership_in(organization)
return false if membership.nil?
membership.is_admin?
end
def set_admin_for organization, value
membership = self.membership_in(organization)
return false if membership.nil?
membership.update_attribute(:is_admin, value)
end
As always, test-driven development is best, but I usually don't have the time to do that for stackoverflow questions :)
class Organization < ActiveRecord::Base
has_one :address, :as => :addressable
has_many :members, :class_name => "User"
end
class User < ActiveRecord::Base
has_one :address, :as => :addressable
belongs_to :organization
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
I have removed the admin association, partly because it didn't work anyway, mostly because as Jaime said it is the wrong way to go about it (most developers use some sort of roles system). See Jaime's post for a good way about creating an extendable role system.
Hope this helps you out!