Include parent attributes when searching via query - mysql

I have a User Model that has_many Job Applications.
Everything works great, but when I try to search Job Applications by the User's first_name I get the error below.
Error
SQLite3::SQLException: no such column: first_name: SELECT "job_applications"
From my understanding, I need to include the User attributes in the Job Application query.
How can I accomplish this?
View (job application)
<%= form_for :search, :html => {:method => :get, :id => 'search'} do |f| %>
<%= text_field_tag :terms, params[:terms], :type => 'search' %>
<% end %>
Controller (job application)
def index
#job = Job.find(params[:job_id])
#job_applications = #job.job_applications.search(params[:terms])
end
Model (job application)
def self.search(terms)
terms ||= ""
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
### I am having an issue here...
### how do i include the user attributes in this query
"first_name like '%#{term}%'"
end
where(conditions.join " OR ")
end

You have to join job_applications table with users table.
# job_applications.rb
def self.search(terms)
terms ||= ""
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
"users.first_name like :term"
end
joins(:user).where(conditions.join " OR ")
end
Avoid passing raw user's inputs into your queries directly to avoid sql injection. Use rails' built-in filters or sanitize it yourself.
def self.search(terms)
terms ||= ""
term_args = []
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
term_args << "%#{term}%"
"users.first_name like ?"
end
joins(:user).where(conditions.join(' OR '), term_args)
end

Related

Delete an element from a collection

I'm facing to a stupid problem. I have created a collection select which is creating elements into a join table "staffs_task" to reference an association between the model staff and task.
And now I would like two things: (1) a button delete this association (2) and a little bit of code for my model staffs_task to avoid duplication, so with the task_id and staff_id. And last info, task is a model built by ranch
my code:
(the collection in new_task)
<%= select_tag "staffs_task", options_from_collection_for_select(#staffs, 'id', 'name') , :multiple => true %>
(task_controller)
skip_before_action :configure_sign_up_params
before_action :set_ranch
before_action :set_task, except: [:create]
def create
#task = #ranch.tasks.create(task_params)
#staffs = Staff.where(:id => params[:staffs_task])
#task.staffs << #staffs
if #task.save
#task.update(done: false)
#task.update(star: false)
flash[:success] = "The task was created "
else
flash[:success] = "The task was not created "
end
redirect_to #ranch
end
private
def task_params
params.require(:task).permit(:content, :deadline, :row_order, :date, :assigned_to)
end
def set_ranch
#ranch = Ranch.find(params[:ranch_id])
end
def set_task
#task = #ranch.tasks.find(params[:id])
end
So if you have any idea about one of this two things, your help would be welcome
Thanks in advance !!
Lets say you have the following many to many setup with a join model:
class Staff
has_many :assignments
has_many :tasks, through: :assignments
end
class Task
has_many :assignments
has_many :staff, through: :assignments
end
class Assignment
belongs_to :task
belongs_to :staff
end
Note that the plural of staff is staff - unless you are talking about the sticks carried by wizards.
ActiveRecord creates "magical" _ids setters for all has_many relationships. When used with a has_many through: relationship rails is smart enough to just remove the rows from the join table.
You can use this with the collection_select and collection_checkboxes methods:
<%= form_for([#task.ranch, #task]) do |f| %>
<%= f.collection_select(:staff_ids, Staff.all, :id, :name, multiple: true) %>
<% end %>
You would then set your controller up like so:
def create
#task = #ranch.tasks.new(task_params) do |t|
# this should really be done by setting default values
# for the DB columns
t.done = false
t.star = false
end
if #task.save
redirect_to #ranch, success: "The task was created"
else
render :new, error: "The task was not created"
end
end
private
def task_params
params.require(:task)
.permit(:content, :deadline, :row_order, :date, :assigned_to, staff_ids: [])
end
staff_ids: [] will allow an array of scalar values. Also not that .new and .create are not the same thing! You where saving the record 4 times if it was valid so the user has to wait for 4 expensive write queries when one will do.

How do I add checkbox filters to my Posts index page (Rails app)

I have a Rails app that has many Posts. Each Post has different parameters, two of these parameters are zagat_status and michelin_status. (it's a restaurant discovery website).
I'm trying to add 2 checkboxes that allow me to filter results for:
a) zagat_status .. so clicking the checkbox shows all Posts where zagat_status is "Yes"
b) michelin_status .. so clicking the checkbox shows all Posts where michelin_status = "1", "2", or "3"
Moreover, I want these two checkboxes to be able to work with each other. So if I click both, both filters apply simulatneously.
However, this is not working... how do I get the code below to work??
POST MODEL
class Post < ActiveRecord::Base
scope :zagat_status, -> (zagat_status) { where zagat_status: zagat_status }
scope :michelin_status, -> (michelin_status) { where michelin_status: michelin_status }
validates :name, presence: true
validates :city, presence: true
validates :address, presence: true
def self.search(query)
where("name like ? OR city like ? OR address like ?", "%#{query}%", "%#{query}%", "%#{query}%")
end
end
POST CONTROLLER
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
# GET /posts
# GET /posts.json
def index
#posts = Post.all
if params[:search]
#posts = Post.search(params[:search]).order("created_at DESC")
elsif params[:zagat_status].present?
#posts = Post.zagat_status(params[:zagat_status]).order("created_at DESC")
elsif params[:michelin_status].present?
#posts = Post.michelin_status(params[:michelin_status]).order("created_at DESC")
else
#posts = Post.all
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(
:name,
:michelin_status,
:zagat_status,
:address,
:city,
:cuisine,
:neighborhood,
:price_range,
:longitude,
:latitude)
end
end
INDEX.HTML.ERB FILE (for POSTS)
<div class="search-filter">
<form>
<span>Accolades</span>
<div class="accolades-panel">
<label>
Michelin Star(s) <dd>1,2,3 and bibs</dd>
<input type="checkbox" name="michelin_status" value="1">
</label>
<label>
Zagat<dd>rated</dd><input type="checkbox" name="zagat_status" value="Yes">
</label>
</div>
</form>
</div>
INDEX.JS.ERB FILE
Blank
INDEX.JSON.JBUILDER FILE
json.array!(#posts) do |post|
json.extract! post, :id, :name, :zagat_status, :michelin_status, :cuisine, :address, :city, :price_range, :longitude, :latitude
json.url post_url(post, format: :json)
end
Your code doesn't work because the results are returned immediately after one of the conditions in the controller are satisfied e.g. if params[:search] exists it will return:
#posts = Post.search(params[:search]).order("created_at DESC")
And the rest of the code won't be run, because it's inside elsif blocks.
Something like this should work:
#posts = Post.all
if params[:search]
#posts = #posts.search(params[:search]).order("created_at DESC")
end
if params[:zagat_status].present?
#posts = #posts.zagat_status(params[:zagat_status]).order("created_at DESC")
end
if params[:michelin_status].present?
#posts = #posts.michelin_status(params[:michelin_status]).order("created_at DESC")
end
This way we build the query based on the existence of parameters. However this is not the most elegant solution.
I would suggest you create a method in your model that would take in the parameters, and then create a query there based on the parameters.

Rails first_or_create! creating null values in database table

I'm using first_or_create to populate a table with a list of email subscribers (called members). The code is as follows:
def community_members=(members)
self.members = members.split(",").map do |member|
Member.where(email: member.strip, community_id: self.id).first_or_create! unless member.strip == nil
end
end
Everything works fine, except that when I add additional emails to the same community, the table turns the "community_id" column for all previous rows to NULL.
Here's the server log:
Member Load (0.2ms) SELECT "members".* FROM "members" WHERE "members"."email" = $1 AND "members"."community_id" = $2 ORDER BY "members"."id" ASC LIMIT 1 [["email", "lisa#holy.com"], ["community_id", 1]]
SQL (0.3ms) INSERT INTO "members" ("email", "community_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["email", "lisa#holy.com"], ["community_id", 1], ["created_at", "2015-04-30 16:14:25.930012"], ["updated_at", "2015-04-30 16:14:25.930012"]]
Member Load (0.2ms) SELECT "members".* FROM "members" WHERE "members"."community_id" = $1 [["community_id", 1]]
SQL (0.4ms) UPDATE "members" SET "community_id" = NULL WHERE "members"."community_id" = $1 AND "members"."id" = 30 [["community_id", 1]]
(0.3ms) COMMIT
The first "Member" load does exactly what it's supposed to do. But for some reason it always ends with the second Member load that goes in and sets all "community_id" fields to NULL.
Right now I call :community_member from a form on a community page:
<%= form_for(#community) do |f| %>
<%= f.text_area :community_members, class:"form-control input-lg", placeholder:"Please add your list of comma separated member email addresses here" %>
<%= f.submit "Save", class: "btn btn-lg btn-green btn-block pad-top" %>
<% end %>
Seems like I'm missing something obvious here. Any ideas? Thank you.
You're going to want to find by the unique attribute, email, and create by community name, I think.
If that's the case, you'll have to do something like:
Member.where(email: member.strip).first_or_create(community: self) unless...
If you have records with non-unique emails, you'll have to redesign your associations.
class Subscriber
#this should have the email attribute
has_many :community_subscribers
has_many :communities, through: :community_subscribers
end
class CommunitySubscriber
#this is a 'bridge' table
belongs_to :subscriber
belongs_to :community
end
class Community
has_many :community_subscribers
has_may :subscribers, through: :community_subscribers
#I suggest new method and arg names
#Using self will keep the query within the scope of the community you are working on
#This also allows for creation of Subscriber records if you insist placing that here
#are you sure you want to map instead of just iterating the split list of emails?
def new_subscribers(emails)
emails.split(",").map do |email|
clean_email = email.strip
subscriber = Subscriber.where(email: clean_email).find_or_create unless clean_email.blank?
self.community_subscribers.where(subscriber: subscriber).first_or_create unless subscriber.blank?
end
end
end
Docs:
http://apidock.com/rails/v3.2.1/ActiveRecord/Relation/first_or_create
http://guides.rubyonrails.org/v3.2.13/active_record_querying.html#first_or_create

Rails4: Formtastic3.0 , save multiple instances (for Answer models) at the same time

I have Question model and an answer model.
Each question can have one answer per user. I am trying to preset to a user a form to answer all questions, but couldnt really figure out how to do it with formtastic
Here is what I have so far
- #questions.each do |q|
= q.content
- ans = #user.answers.where(question_id:q.id).first.try(:content) || q.description
= semantic_form_for #answers do |f|
= f.label q.content
= f.inputs :content, placeholder: ans
= f.actions
I am trying to get some hint from How to loop through two alternating resources on a form? but I keep getting "undefined method `model_name' for Class:Class" for #questions if I try:
= semantic_form_for #questions do |q|
= q.input :content
= q.semantic_fields_for #answer do |a|
= a.inputs :content
= q.actions
Based on Railscast 198, but using formtastic here is my attempt that doesn't work either:
- semantic_form_for :Answer, :url => api_v1_answers_path, :method => :put do |f|
- #questions.each do |q|
- f.fields_for 'questions[]', q do |ff|
= q.content
= ff.input
= submit_tag "Submit"
Note:
1] I will like to have user press submit only once after he has added/edited all the answers
2] If there is an answer already present, it should be pre-poulated in the text box
3] I dont mind using simple_form gem if that makes life easier
Rather then making a form for #questions you need to pass a single object to your form helper (#questions is an array of questions). A good way to achieve this is though a form object.
# app/forms/questions_form.rb
class QuestionsForm
include ActiveModel::Model
def initialize(user)
#user = user
end
def questions
#questions ||= Question.all
end
def submit(params)
params.questions.each_pair do |question_id, answer|
answer = Answer.find_or_initialize_by(question_id: question_id, user: current_user)
answer.content = answer
answer.save
end
end
def answer_for(question)
answer = answers.select { |answer| answer.question_id == question.id }
answer.try(:content)
end
def answers
#answers ||= #user.answers.where(question: questions)
end
end
Then in your controller you'd have:
# app/controllers/submissions_controller.rb
Class SubmissionsController < ApplicationController
...
def new
#questions_form = QuestionsForm.new(current_user)
end
def create
#questions_form = QuestionsForm.new(current_user)
#questions_form.submit(params[:questions_form])
redirect_to # where-ever
end
...
end
In your view you'll want something along the lines of:
# app/views/submissions/new.html.haml
= form_for #questions_form, url: submissions_path do |f|
- #questions_form.questions.each do |question|
%p= question.content
= f.text_field "questions[#{question.id}]", value: #questions_form.answer_for(question)
= f.submit "Submit"
This doesn't use formtastic at all, it's just using the plain rails form helpers.
This code isn't tested so not sure if it even works but hopefully it helps get you on the right track.

Odd rendering of an html table in Ruby on Rails when using content_tag

I'm trying to build a table in Ruby on Rails with the help of the content_tag method.
When I run this:
def itemSemanticDifferential(method, options = {})
if options[:surveyQuestion].any? then
#template.content_tag(:tr) do
#template.content_tag(:td, options[:surveyQuestion], :colspan => 3)
end
end
#template.content_tag(:tr) do
#template.content_tag(:th, options[:argument0])
end
end
Only the second part gets rendered:
#template.content_tag(:tr) do
#template.content_tag(:th, options[:argument0])
end
Can anyone tell me why this is?
Ruby Rails returns the last variable it used if no return is explicitly called.
( example: )
def some_method(*args)
a = 12
b = "Testing a String"
# ...
3
end # This method will return the Integer 3 because it was the last "thing" used in the method
Use an Array to return all the content_tag (WARNING: this method will return an Array, not a content_tag as you expect, you'll need to loop on it):
def itemSemanticDifferential(method, options = {})
results = []
if options[:surveyQuestion].any? then
results << #template.content_tag(:tr) do
#template.content_tag(:td, options[:surveyQuestion], :colspan => 3)
end
end
results << #template.content_tag(:tr) do
#template.content_tag(:th, options[:argument0])
end
return results # you don't actually need the return word here, but it makes it more readable
end
As asked by author of the Question, You need to loop on the result because it is an array of content_tags. Also, you need to use .html_safe to output the content_tags as HTML (not strings).
<% f.itemSemanticDifferential(:method, options = {}).each do |row| %>
<%= row.html_safe %>
<% end %>