Using form to update has_many through join table - mysql

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.

Related

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.

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.

Preventing duplicates via a custom foreign key in has_many :through

I'm trying to implement a two-way has_many :through association between a User model and a Location model using a UserLocations join table. This will enable setting user locations with built in ActiveRecord methods, ie. #user.locations = [<Location 1>, <Location 2>, ...]. My goal is to not associate locations to users individually, but rather for users to add and remove them, one or more at a time, via another field: :zip_code. This means that when a user adds a zip code, ActiveRecord should insert a single record into UserLocations (something like INSERT INTO user_locations (user_id, zip_code) VALUES (1, 12345)). Then, when #user.locations is called, ActiveRecord should join by :zip_code and get the matching location(s). My current implementation works, except that one INSERT into UserLocations is generated for each location associated with a zip code.
class User < ActiveRecord::Base
has_many :user_locations
has_many :locations, through: :user_locations
end
class UserLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location, primary_key: :zip_code, foreign_key: :zip_code
end
class Location < ActiveRecord::Base
has_many :user_locations, primary_key: :zip_code, foreign_key: :zip_code
has_many :users, through: :user_locations
end
Things I've tried:
validates_uniqueness_of :zip_code, scope: :user_id - just throws a validation error and prevents all record creation
has_many unique: true - doesn't prevent duplicate DB queries
add_index unique: true for (user_id, zip_code) - would at least prevent duplicate entries from being created, but I'm trying to prevent unnecessary queries entirely
Using questions like this one for guidance hasn't gotten me any closer. Is what I'm trying to do possible without using my own methods to get/set user locations?
First of all, I'm not very experienced in rails yet, but I'll still try to help :)
What I would do is not using zipcodes as a key. When a user inputs zip codes you look up the code in the Location:
#zip_code = Location.where(zipcode: user_input).first
#zip_code.user_locations.create!(user_id #some other stuff you want)
This way you store the id of the location into the user location and no duplicates are made. You can then generate user locations by joining the UserLocation and Location.
But as I said, there may be a better way of doing this as I'm beginner.
Stop me if I'm wrong :)
You have zipcodes in your locations table (i.e: 111, 222, 333) When a user selects a zipcode of '111' for him self, his record is associated with the existing locations record; but when a user selects a zipcode of '444' a new locations record is created and link to that user. Next use that selects '444' will be linked to this same record.
If my assumption if correct, you should have:
validates_uniqueness_of :zip_code (without scope) in your Location model
in your User model while creating/updating you could use Location.find_or_create_by(params[:zipcode])
This is pseudo-code (don't copy-paste it), I don't exactly know how your code is writen, but my point is for you to have a look at find_or_create, I believe it could be your solution
It looks like you have the association setup correctly.
When you have a has_many association in rails and want to do something like this:
#user.locations = [<Location 1>, <Location 2>, ...]
Rails will create individual INSERT statements for each location in the array, although it will do a bulk DELETE for you. If you want it to do bulk INSERT statements, you'll need to roll your own code or look into the activerecord-import gem to do this.
As for the duplicates, if you are only doing the above code, there shouldn't be duplicate record errors unless there are duplicates in that location array, in which case you should call uniq on it first.

complicated search design

I am implementing a recipe search on rails3 using mysql.
The idea of search is that user enters any number of ingredients and search outputs suggestions what to make, sorted in product deficiency order.
class Recipe < ActiveRecord::Base
has_many :ingredients
end
# these records will be entered by user
class IngredientType < ActiveRecord::Base
has_many :ingredients
end
# this table is join table
class Ingredient < ActiveRecord::Base
belongs_to :ingredient_type
belongs_to :recipe
end
What would be the most efficient way to implement this search?
What gems or techniques would you recommend?
Thank you for your answers
def self.from_ingredients ingredients
count_sql = Ingredient.
select('COUNT(*)').
joins(:recipes_ingredients).
where('`recipes_ingredients`.`recipe_id` = `recipes`.`id`').
where('`ingredients`.`id` in (?)', ingredients).to_sql
where("(#{count_sql}) > 0").
order("((`recipes`.`ingredients_count`) - (#{count_sql})) ASC")
end
I managed to find solution by creating such method in recipe model.