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

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.

Related

How to control the order of joined records in Rails 4

Rails 4. Sqlite3 DB (for development).
My model looks like this:
class Group < ActiveRecord::Base
has_many :memberships
has_many :people, through: :memberships
has_many :notes, as: :notable
scope :with_active_members, -> { joins(people: [:role]).where(people: {active: true}).uniq }
end
And I use it like this:
#groups = Group.with_active_members.order("groups.position")
The join works perfectly well. But I want to order the people records alphabetically. To be very clear, I don't want to order the groups by their associated people. I want the groups to be ordered by their position field but I want the associated collections of people to be ordered alphabetically. Can this be done with the ORM?
It appears that you can do this in the model:
has_many :people, ->{ order('first_name') }, through: :memberships
This works in some situations, but I'm not accepting it as the answer because what I really need is a way to set this in the controller, so that the order can be controlled dynamically.

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.

Using form to update has_many through join table

I am short of implementation ideas for my rails project. at this point it seems impossible to implement in rails.
I am cloning a sort of accounting software into a web app using rails framework for small manufacturing companies to keep track of their products stock in their different distribution branches.
I have 3 different models: "Product", "Branch" & "Accumulator"
class Branch < ActiveRecord::Base
has_many :accumulators
has_many :products, :through => :accumulators
end
class Product < ActiveRecord::Base
has_many :accumulators
has_many :branches, :through => :accumulators
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
end
end
class Accumulator < ActiveRecord::Base
belongs_to :product
belongs_to :branch
end
I am new to rails and after reading up on many to many associations I came across using collections to add products to a branch "#branch.products << Product.all"
Is there a possible way to use a form "number_field_tag" in the branch show view to add multiple of specific products into the join table?
eg
I want to add 10 of (Product) called "garden eggs" to a (Branch) called "lagos branch" to the (Accumulator) join table using a form in Branches show view?
Congratulations for choosing :has_many, through: you will not regret it.
Is the relationship between a product and a branch static?
Or does it change a lot?
In your Accumulator model, add an integer field called amount (count can have conflicts). Then you either create a form for your Accumulators or you add a nested form with for example Cocoon.
This way you can add Accumulators to your Branch with a certain Product and a certain amount.
Off topic:
Here is an article about why has_many through has some advantages:
http://blog.flatironschool.com/why-you-dont-need-has-and-belongs-to-many/
If you have problems with forms I can really recommend SimpleForm and for nice Javascript-assisted fields I recommend Select2.
If the table accumulators needs to save only two things: product_id, and branch_id, you can easily use has_and_belongs_to_many associations.
class Branch < ActiveRecord::Base
has_and_belongs_to_many :products, join_table: 'accumulators'
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :branches, join_table: 'accumulators'
end
And now, there is no need for the third model.
As far as it goes, how to add a relationship, it's pretty easy in this case:
branch = Branch.last
branch.products << Product.create # you don't need to touch the middle table.
Instead of using number_field_tag to ask for plain ids, you can use something fancy like jQuery Chosen Plugin. This plugin will allow you to use tag like input, and will send the ids to the server separated by ,'s.

Combining data from two tables in rails

I have two models, one belongs to the other. They look like this:
class LittleClass < ActiveRecord::Base
has_many :little_class_sessions
end
and
class LittleClassSession < ActiveRecord::Base
belongs_to :little_class
end
LittleClassSession has a column called little_class_id. I want to get all LittleClassSession but also have the associated LittleClass returned to me in the same hash.
Is there some way to do this that's built into Rails? Or is there a clean way to do this?
And is this something that I build into the LittleClass or LittleClassSession model with scope?
When you query ActiveRecord you will get an array of ActiveRecord:Relation. It is a specific entity which starts your query. You can of course join dependent tables (as in your example with one-to-many relation). But you will still need to go over those dependent relations to build whatever object you need.
Here is a sketch of what I mean (assume we search for all little class sessions with specific little class id):
class_sessions = LittleClassSession.includes(:little_class).where(:little_classes => {:id => 1})
class_sessions.each do |relation|
test_hash = relation.attributes.merge!({:little_class => relation.little_class.attributes});
puts test_hash
end
test_hash will include all the attributes of the little class session as well as attributes of the little class under corresponding key.

get list of parent records from a list of child records with joins or includes

lets suppose I have to models
class Parent < ActiveRecord::Base
has_many :kids
end
class Kid < ActiveRecord::Base
belongs_to :parent
end
and I have an array of kids
[k1, k2, k3, k4]
I want a list of parents of these kids something like
[p1, p2]
I want to do this with use of includes or joins any one can help me.
If you want to do it via Active Record:
Parent.joins(:kids).where("kids.id" => array_of_kids).pluck(:id)