Unable to make sense of database data in Rails console - mysql

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

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.

Additional Foreign Key on a Join Table

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.

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.

Access data from join-table in has_many :through, preventing additional data loading

Rails 4.1.7
I have 3 models.
# Report
class Report < ActiveRecord::Base
has_many :computed_values, dependent: :destroy
has_many :settlements, through: :computed_values
end
# ComputedValue
class ComputedValue < ActiveRecord::Base
belongs_to :report
belongs_to :settlement
end
# Settlement
class Settlement < ActiveRecord::Base
has_many :computed_values
has_many :reports, through: :computed_values
end
ComputedValue has an attribute distance.
I want to get such construction works: Report.first.settlements.first.distance which is ComputedValue.find(report_id: Report.first.id, settlement_id: Report.first.settlements.first.id).distance
Is there any elegant and fast way to get that worked?
When I call Report.first.settlements.first, Rails has already loaded records for first report, first settlement and record from join-table computed_values.
How to prevent second loading from computed_values for finding values and use data from already loaded records?
Ok, I found a solution.
# Report
has_many :settlements, -> {select("settlements.*, computed_values.distance AS distance")},
through: :computed_values
After that, Report.first.settlements.first.distance works just perfectly!

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!