complicated search design - mysql

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.

Related

Rails joining tables having has_many association

I have Student and Record table with a simple has_many :through association:
class Student < ActiveRecord::Base
has_many :records
end
I need to fetch all students details of MBA branch. I need to assign result to a variable #students and need to access record of a student using
#students.each do |student|
students.records.each do |record|
///
end
end
That is I need to access has_many related class datas also into an object. How to write query for that. I am newbee in rails. Please help .Thanks in advance
try this,
#student = Student.where("student.course = ?", "MBA").includes(:records)
Then do eaching on instance variable. This will save you from N+1 issue
It looks like you have a has_many relation instead of has_many :through. Have you looked through the rails guides? There they describe how to fetch data from the database. http://guides.rubyonrails.org/active_record_basics.html#read
Here is a more detailed reference on the has_many relation: http://guides.rubyonrails.org/association_basics.html#has-many-association-reference
Make sure you access student and not students in the loop.
#students.each do |student|
student.records.each do |record|
///
end
end
Where is the MBA branch coming from? Is that another table?
You probably will end up with something like #students = Student.where(branch_id: 1).
The ID 1 is just fictional but I'm guessing you have your branches in another table.

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

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.

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!