Optimize big array search ruby - ruby-on-rails

I am creating a friendship feature.
But I have an issue. When a user is sending a request to a specific user, it is iterating through more than 3k users in order to find the right person.
I'd like to know if there is a ruby method which permits to increase the speed of the search.
I'm using the has_friendship gem
Here is my code :
View friends : index.html.erb
<div class="info d-flex flex-column">
<header class="ui basic segment">
<h3 class="typo text-center mt-2">Ajoutes tes amis 👇</h3>
</header>
<div class="ui basic segment">
<%= form_tag ({controller: 'friends', action: 'search'}) do %>
<div class="text-center mt-2">
<input type="align-items-center mt-2" name="search" id="search">
</div>
<input class="btn btn-light mt-3" type="submit" name="commit" value="Chercher" data-disable-with="Search">
<% end %>
</div>
</div>
Here is my controller of friends with the method search :
#search = params[:search]
#results = User.find{|x| x.pseudo == #search}
Here is my search view : search.html.erb
<% if #results.name != current_user.name %>
<div class="ui vertical clearing segment">
<span class="ui header">
<p class="typopo"> Pseudo: <%= #results.pseudo %></p>
<p class="typopo"> Championnat: <%= #results.player_seasons[0].championship.name %></p>
<p class="typopo"> Nombre de points: <%= #results.player_seasons[0].number_of_points %></p>
</span>
<% if current_user.not_friends.include?(#results) %>
<div class="ui basic segment">
<%= form_tag ({controller: 'friends', action: 'create', method: 'post', id: #results.id}) do %>
<input class="btn btn-light mt-3" type=submit value='Ajouter'>
<% end %>
</div>
<% end %>
<% end %>
Here is my schema for the user model and the friendship model :
create_table "friendships", id: :serial, force: :cascade do |t|
t.string "friendable_type"
t.integer "friendable_id"
t.integer "friend_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "blocker_id"
t.integer "status"
t.index ["friendable_id", "friend_id"], name: "index_friendships_on_friendable_id_and_friend_id", unique: true
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "name"
t.string "pseudo"
t.string "last_name"
t.boolean "admin", default: false, null: false
t.string "blason"
t.string "provider"
t.string "uid"
t.text "image"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Thank you by advance

As you're filtering records by their pseudo column, in its entirety, you could use find_by, which implements the WHERE clause plus a LIMIT of 1:
User.find_by(pseudo: #search)

I think you can try save user.pseudo to db and search by that field. You can improve speed with db index.

Related

Error Updating Spree User Custom Attributes

I'm attempting to add custom fields like first name, last name, avatar_url, and bio to my Spree user.
I added the fields to the table successfully:
class AddFieldsToSpreeUser < ActiveRecord::Migration[6.0]
def change
add_column :spree_users, :first_name, :string
add_column :spree_users, :last_name, :string
add_column :spree_users, :bio, :text
add_column :spree_users, :avatar_url, :string
end
end
I checked and the schema was updated according to plan:
create_table "spree_users", id: :serial, force: :cascade do |t|
t.string "encrypted_password", limit: 128
t.string "password_salt", limit: 128
t.string "email"
t.string "remember_token"
t.string "persistence_token"
t.string "reset_password_token"
t.string "perishable_token"
t.integer "sign_in_count", default: 0, null: false
t.integer "failed_attempts", default: 0, null: false
t.datetime "last_request_at"
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "login"
t.integer "ship_address_id"
t.integer "bill_address_id"
t.string "authentication_token"
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "reset_password_sent_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "spree_api_key", limit: 48
t.datetime "remember_created_at"
t.datetime "deleted_at"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "first_name"
t.string "last_name"
t.text "bio"
t.string "avatar_url"
t.index ["bill_address_id"], name: "index_spree_users_on_bill_address_id"
t.index ["deleted_at"], name: "index_spree_users_on_deleted_at"
t.index ["email"], name: "email_idx_unique", unique: true
t.index ["ship_address_id"], name: "index_spree_users_on_ship_address_id"
t.index ["spree_api_key"], name: "index_spree_users_on_spree_api_key"
end
I added the new attributes to the Spree initializer:
Spree::PermittedAttributes.user_attributes << [:first_name]
Spree::PermittedAttributes.user_attributes << [:last_name]
Spree::PermittedAttributes.user_attributes << [:bio]
Spree::PermittedAttributes.user_attributes << [:avatar_url]
When I try to create a new user in rails c the new attributes come up as part of the model.
I have the following form to attempt to update the user:
<%= simple_form_for current_spree_user, url: spree.spree_user_registration_path(current_spree_user) do |f| %>
<%= f.object.errors.full_messages.join(", ") if f.object.errors.any? %>
<div class="row text-left">
<div class="col-sm-6 py-3">
<%= f.label "First Name" %>
<%= f.text_field :first_name, class: "form-control", required: true, autofocus: true, input_html: { autocomplete: "fname" } %>
</div> <!-- col -->
<div class="col-sm-6 py-3">
<%= f.label "Last Name" %>
<%= f.text_field :last_name, class: "form-control", input_html: { autocomplete: "lname" } %>
</div> <!-- col -->
<div class="col-12 py-3">
<%= f.label "Author Bio" %>
<%= f.text_area :bio, class: "form-control" %>
</div> <!-- col -->
<div class="col-12 py-3">
<%= f.label "Profile Image URL" %>
<%= f.text_field :avatar_url, class: "form-control", input_html: { autocomplete: "lname" } %>
</div> <!-- col -->
</div> <!-- row -->
<div class="form-actions text-center">
<%= f.button :submit, "Let's Go", class: "btn blue" %>
</div>
<% end %>
When I hit submit, I get a routing error:
ActionController::UnknownFormat
I have tried specifying method: :patch but it gets the same result. Can anyone see where I'm going wrong here?

How to use collection select for hidden value

I have a table called sublet post which has these columns and there is a reference column to the property table.
create_table "sublet_posts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "property_id"
t.index ["property_id"], name: "index_sublet_posts_on_property_id"
Property table
create_table "properties", force: :cascade do |t|
t.string "address"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
In the sublet post form view
<div class="field">
<%= collection_select(:sublet_post, :property_id, #properties, :id, :address, prompt: true) %>
<%= form.hidden_field :property_id, value: params[:sublet_post][:property_id] %>
</div>
In the form view, I am using the address as the selections and if a user clicks on an address, I would like the id to be stored in the for the value of the hidden value :property_id. I would like to do something similar to the code above.
You can add an id to both elements and catch the change event.
The html will be so:
<div class="field">
<%= collection_select(:sublet_post, :property_id, #properties, :id, :address, prompt: true, id: 'the_collection') %>
<%= form.hidden_field :property_id, value: params[:sublet_post][:property_id], id: 'the_field'%>
</div>
And the javascript, if you are using jQuery :
$(document).on('change', '#the_collection', function(event) {
var element = $(event.target);
element.siblings('#the_field').val(element.val());
});
https://api.jquery.com/siblings/

custom query in active record with multiple joins

I am very new to rails and active record so I'm probably doing something wrong, but... using the schema below, I want to create a list of courses in the outer do loop, then list each student enrolled in the course with the inner do loop. I m not sure if this approach will work, but this error keeps popping up:
ActionView::Template::Error (undefined method `enrollments' for #):
it seems that the association is made. what am i doing wrong?
Professor show page:
<div class="col-md-8">
<h2 class="card-title"><%= #professor.name %></h2>
<% #courses_taught.each do |course| %>
<div class="card mb-4 card-header">
<img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
<h3 class="card-text"><%= course.title %></h3>
</div>
<div class="card-body">
<% course.sections.enrollments.students.each do |student| %>
<p><% student.name %></p>
<% end %>
</div>
<% end %>
</div>
models:
enrollment
class Enrollment < ApplicationRecord
belongs_to :section
belongs_to :student
end
Student:
class Student < ApplicationRecord
has_many :enrollments
end
Professor:
class Section < ApplicationRecord
has_many :enrollments
belongs_to :professor
belongs_to :course
validates_uniqueness_of :professor_id, scope: :course_id
scope :by_professor_id, ->(prof_id) { where('professor_id = ?', prof_id) }
end
Course:
class Course < ApplicationRecord
enum status: { planning: 0, offered: 1 }
scope :offered, -> { where(status: 1) }
scope :planning, -> { where(status: 0) }
belongs_to :department
has_many :sections
has_many :professors, through: :sections
validates :title, :number, :status, :description, presence: true
validates :description, length: { in: 10..500 }
validates :title, :number, uniqueness: { case_sensitive: false }
def self.search(term)
if term
where('title LIKE ?', "%#{term}%").order('title DESC')
else
order('title ASC')
end
end
def self.taught_by(professor_id)
Course
.joins(:sections)
.joins(:professors)
.where(sections: { professor_id: professor_id })
.select('distinct courses.*')
end
end
Schema:
ActiveRecord::Schema.define(version: 20171013201907) do
create_table "courses", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "number"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "status", default: 0
t.integer "department_id"
t.index ["department_id"], name: "index_courses_on_department_id"
end
create_table "departments", force: :cascade do |t|
t.string "name"
t.text "description"
t.text "main_image"
t.text "thumb_image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "enrollments", force: :cascade do |t|
t.integer "section_id"
t.integer "student_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["section_id"], name: "index_enrollments_on_section_id"
t.index ["student_id"], name: "index_enrollments_on_student_id"
end
create_table "professors", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "status", default: 0
t.integer "department_id"
t.text "bio"
t.index ["department_id"], name: "index_professors_on_department_id"
end
create_table "sections", force: :cascade do |t|
t.integer "number"
t.integer "max_enrollment"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "professor_id"
t.integer "course_id"
t.string "room"
t.index ["course_id"], name: "index_sections_on_course_id"
t.index ["professor_id", "course_id"], name: "index_sections_on_professor_id_and_course_id", unique: true
t.index ["professor_id"], name: "index_sections_on_professor_id"
end
create_table "students", force: :cascade do |t|
t.string "name"
t.decimal "gpa"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "name"
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "roles"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
enrollment belongs_to user, there is no need to use each on single record rather it will throw error. You can use below code.
<% course.sections.each do |section| %>
<% section.enrollments.each do |enrollment| %>
<p><% enrollment.student.name %></p>
<% end %>
<% end %>
Sean's answer is almost correct but since enrollments belongs_to student
you would use the singular form enrollment.student. but more simply you can just call student from inside enrollments block.
<div class="col-md-8">
<h2 class="card-title"><%= #professor.name %></h2>
<% #courses_taught.each do |course| %>
<div class="card mb-4 card-header">
<img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
<h3 class="card-text"><%= course.title %></h3>
</div>
<div class="card-body">
<% course.sections.each do |section| %>
<% section.enrollments.each do |enrollment| %>
<p><% enrollment.student.name %></p>
<% end %>
<% end %>
</div>
<% end %>
</div>
<div class="col-md-8">
<h2 class="card-title"><%= #professor.name %></h2>
<% #courses_taught.each do |course| %>
<div class="card mb-4 card-header">
<img class="card-img-top" src="http://placehold.it/750x300" alt="Card image cap">
<h3 class="card-text"><%= course.title %></h3>
</div>
<div class="card-body">
<% course.sections.each do |section| %>
<% section.enrollments.each do |enrollment| %>
<p><% enrollment.student.name %></p>
<% end %>
<% end %>
</div>
<% end %>
</div>

Rails 4 - Many to many categories won't save to database

I have 3 tables called pins (posts), genres (categories) and genre_pin (many to many between pins and genres because pins can have many genres).
I am trying to build the create form, which will allow the user to select as many genres as they want on a pin and save / create. The create works just fine without error, but it is not saving any relationship records to the genre_pin table. I'm pretty sure I may be missing something very obvious here.
I am using the terms 'pins' and 'genres', but you can think of these as the same as 'posts' and 'categories'.
Pin Model
class Pin < ActiveRecord::Base
belongs_to :user
belongs_to :type
has_many :replies
has_many :genre_pins
has_many :genres, :through => :genre_pins
accepts_nested_attributes_for :genres, allow_destroy: true, reject_if: proc { |genre| genre[:id].blank? }
validates :user_id, presence: true
validates :type_id, presence: true
validates :title, presence: true
validates :description, presence: true
end
Genre Model
class Genre < ActiveRecord::Base
has_many :genre_pins
has_many :pins, :through => :genre_pins
validates :title, presence: true
end
GenrePin Model
class GenrePin < ActiveRecord::Base
belongs_to :pin
belongs_to :genre
validates_uniqueness_of :pin_id, :scope => :genre_id
end
Next, in my pin#new view, I have the following form:
Pin New View
<%= form_for :pin, url: pins_path do |f| %>
<div class="small-12 columns">
<%= f.label :title %>
<%= f.text_field :title %>
</div>
<div class="small-12 columns">
<%= f.label :description %>
<%= f.text_area :description, :rows => 10 %>
</div>
<div class="small-12 columns">
<%= f.label :type %>
<%= f.collection_select :type_id, Type.order(:title), :id, :title, include_blank: false %>
</div>
<div class="small-12 columns">
<%= f.label :genres %>
<%= f.collection_select :genre_ids, Genre.order(:title), :id, :title, {}, { :multiple => true } %>
</div>
<div class="small-12 columns">
<%= f.submit "Create Pin", :class => "button" %>
</div>
<% end %>
The view works fine and gets me the available genres from the genres table. However, when I go to save the form, the record (pin) is created, but the relationship with the genres is not. What am I missing here? No errors are thrown, it's just that nothing happens. I am assuming Rails should be creating these PinGenre records through a pins association with genre.
Pin Controller (create)
def create
#pin = Pin.new(pin_params)
#pin.user_id = current_user.id
#pin.save
if #pin.save
flash[:success] = "Pin successfully created"
redirect_to #pin
else
flash[:warning] = #pin.errors.full_messages.to_sentence
redirect_to :action => "new"
end
end
private
def pin_params
params.require(:pin).permit(:title, :description, :type_id, :genre_ids)
end
Are there further steps I need to make here? Do the records need to be explicitly created by myself?
Thanks for your help and patience. I hope I've made this read clearly.
Michael.
Schema.rb
ActiveRecord::Schema.define(version: 0) do
create_table "genre_pins", force: true do |t|
t.integer "pin_id"
t.integer "genre_id"
end
create_table "genres", force: true do |t|
t.string "title", null: false
end
create_table "pins", force: true do |t|
t.integer "user_id", null: false
t.string "title", limit: 160, null: false
t.text "description", null: false
t.decimal "latitude", precision: 10, scale: 8
t.decimal "longitude", precision: 11, scale: 8
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "deleted_at"
t.integer "type_id", null: false
end
create_table "profiles", force: true do |t|
t.integer "user_id", null: false
t.string "first_name", null: false
t.string "last_name", null: false
t.string "gender"
t.string "email"
t.text "bio"
t.decimal "latitude", precision: 10, scale: 8
t.decimal "longitude", precision: 11, scale: 8
t.string "image", limit: 2000
t.datetime "updated_at"
t.datetime "created_at"
t.datetime "deleted_at"
end
create_table "replies", force: true do |t|
t.integer "pin_id", null: false
t.integer "user_id", null: false
t.text "reply", null: false
t.datetime "updated_at"
t.datetime "created_at"
t.datetime "deleted_at"
end
create_table "saves", force: true do |t|
t.integer "pin_id", null: false
t.integer "user_id", null: false
t.datetime "updated_at"
t.datetime "created_at"
t.datetime "deleted_at"
end
create_table "settings", force: true do |t|
t.integer "user_id", null: false
t.boolean "show_email", null: false
t.boolean "show_telephone", null: false
t.integer "timezone", null: false
t.datetime "updated_at"
t.datetime "created_at"
t.datetime "deleted_at"
end
create_table "types", force: true do |t|
t.string "title", null: false
end
create_table "users", force: true do |t|
t.string "provider", limit: 45, null: false
t.string "provider_id", limit: 45, null: false
t.string "oauth_token", limit: 1000
t.datetime "oauth_expires_at"
t.datetime "updated_at"
t.datetime "created_at"
t.datetime "deleted_at"
end
end
Generated View Source
<form accept-charset="UTF-8" action="/pins" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /><input name="authenticity_token" type="hidden" value="3VO3kVR/62JFf6dJs+yyPTPvYJb3fsUSLRFljSE0Jdk=" /></div>
<div class="small-12 columns">
<label for="pin_title">Title</label>
<input id="pin_title" name="pin[title]" type="text" />
</div>
<div class="small-12 columns">
<label for="pin_description">Description</label>
<textarea id="pin_description" name="pin[description]" rows="10">
</textarea>
</div>
<div class="small-12 columns">
<label for="pin_type">Type</label>
<select id="pin_type_id" name="pin[type_id]"><option value="2">Available</option>
<option value="1">Wanted</option></select>
</div>
<div class="small-12 columns">
<label for="pin_genres">Genres</label>
<input name="pin[genre_ids][]" type="hidden" value="" /><select id="pin_genre_ids" multiple="multiple" name="pin[genre_ids][]"><option value="5">Ambient</option>
<option value="4">Electronic</option>
<option value="2">Indie</option>
<option value="3">Jazz</option>
<option value="6">Metal</option>
<option value="1">Rock</option></select>
</div>
<div class="small-12 columns">
<input class="button" name="commit" type="submit" value="Create Pin" />
</div>
</form>
You need to tell your controller that 'genre_ids` is expected to be an array:
params.require(:pin).permit(:title, :description, :type_id, genre_ids: [])
However I expect you will get another error (unknown attribute gener_ids). Let me know if that happen.

Ruby on Rails Devise user

I am new to ruby on rails. I am originally a PHP/mysql programmer. I can not seem to understand how you connect any posts to the user and display them (e.g. a post is successfully created with the user ID and then the users ID is queried to get his or her name.)
I am using devise for authentication.
Controller
def create
poll = current_user.polls.build(params[:poll])
poll.onefourty = poll.onefourty[0..140]
poll.created_at = Time.now
poll.save!
if #poll.save
redirect_to root_path
else
redirect_to root_path
end
end
Form
<%= form_for Poll.new, :html => { :multipart => true } do |f| %>
<div id="form">
<div class="text_input" style="height: 65px;"><%= f.text_area :onefourty %><div class="label" style="height: 63px; line-height: 63px;">Question</div></div>
<div class="text_input"><%= f.text_field :option1 %><div class="label">Option One</div></div>
<div class="text_input"><%= f.text_field :option2 %><div class="label">Option Two</div></div>
<div class="text_input"><%= f.text_field :option3 %><div class="label">Option Three</div></div>
<div class="text_input"><%= f.text_field :option4 %><div class="label">Option Four</div></div>
<div id="submit">
<div id="left">
</div>
<%= f.submit :id => "next", :value => "" %>
</div>
</div>
<% end %>
Migrations:
create_table "polls", :force => true do |t|
t.text "onefourty"
t.string "option1"
t.string "option2"
t.string "option3"
t.string "option4"
t.string "option5"
t.string "option6"
t.datetime "created_at"
t.datetime "updated_at"
t.string "photo1_file_name"
t.string "photo1_content_type"
t.integer "photo1_file_size"
t.datetime "photo1_updated_at"
t.string "photo2_file_name"
t.string "photo2_content_type"
t.integer "photo2_file_size"
t.datetime "photo2_updated_at"
t.string "photo3_file_name"
t.string "photo3_content_type"
t.integer "photo3_file_size"
t.datetime "photo3_updated_at"
t.string "photo4_file_name"
t.string "photo4_content_type"
t.integer "photo4_file_size"
t.datetime "photo4_updated_at"
end
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :limit => 128, :default => "", :null => false
t.string "password_salt", :default => "", :null => false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "reset_password_token"
t.string "remember_token"
t.datetime "remember_created_at"
t.integer "sign_in_count", :default => 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "username"
t.string "firstname"
t.string "lastname"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
end
Assuming you have the following structure:
# app/model/user.rb
class User < ActiveRecord::Base
has_many :polls
#...
end
# app/model/poll.rb
class Poll < ActiveRecord::Base
belongs_to :user
#...
end
And if you're successfully creating polls, which it appears you probably are, then in any view - you could query and display the current user's polls with:
# app/views/polls/index.html.erb
<%- if current_user -%>
<%- current_user.polls.each do |poll| -%>
<h2><%= poll.onefourty %></h2>
<ul>
<li><%= poll.option1 %></li>
<li><%= poll.option2 %></li>
<li><%= poll.option3 %></li>
<li><%= poll.option4 %></li>
<li><%= poll.option5 %></li>
<li><%= poll.option6 %></li>
</ul>
<%- end -%>
<%- else -%>
<em>Not logged in</em>
<%- end -%>

Resources