Rails Conditional Select Contents - ruby-on-rails

I'm populating a select tag dynamically within my form. The select tag is populated by a query to Payee. I have a couple fields I'd like to display as the text value of the element. However it must be done conditionally. For example.
fields:
:company
:first_name
:last_name
I can concatenate those three in the text box like so:
f.select :payee_id, Payee.all.collect { |p| [p.company.to_s + " " + p.first_name.to_s + " " + p.last_name.to_s, p.id] }
The above doesn't work the way I desire. If company exists, I would like to leave out first_name and last_name.
if company is blank, I'd like to display only first_name and last_name
currently if company and first_name exist it concatenates the two together, which I do not desire.
I know how to query the database for each case separately, but I don't know how to combine the results so the select tag will display correctly.
Here's what I have of my helper so far. I know it's currently a mess, but I need help understanding the method to get this done.
def expense_payee_id_field(f)
#payee_with_company = Payee.where("company IS NOT NULL and company !='' ")
#payee_without_company = Payee.where("(first_name != '' or last_name != '') and company = '' ")
f.select :payee_id, Payee.all.collect { |p| [p.company.to_s + " " + p.first_name.to_s + " " + p.last_name.to_s, p.id] }
end

You should just create a method in your Payee model that does this logic for you:
# Payee.rb
# I've extracted this into its own method
# because you very well may need it later
def full_name
[first_name, last_name].join(" ")
end
# This can be written more concisely, but I've
# kept it as-is to allow for better readability
def display_name
if company_name.blank?
full_name
else
company_name
end
end
Now, you can simply call payee.display_name and it will print the desired result.

Related

PaperTrial versions handling changeset error

here are my models looks like so I can address properly my issue
class Product
has_paper_trail
belongs_to :category
end
On my HTML side, I need to load all the logs using the PaperTrial versions. like this
#product.versions.each |version|
version.changeset.each do |k, v|
- if k.to_s == "category_id"
- old_record = v[0].blank? ? " No Record " : Category.find(v[0].to_i).name
- new_record = v[1].blank? ? " No Record " : Category.find(v[1].to_i).name
= "Category" + " From: " + "#{old_record} " + " To: " + "#{new_record}"
in my HTML looks fine but. there is a scenario. that category will be deleted. and I will get an error in this view. because of my "Category.find". the find method cant find the Category that already deleted.
is there a way to store the name of the category and not the ID. so I can get rid of using "find"
or there is a better way to implement these things?
I'd recommend adding category_name attribute to your product model for caching.
# migration
rails g migration AddCategoryNameToProducts category_name
# Product model
before_validation :cache_category_name
def cache_category_name
self.category_name = category&.name if category_name != category&.name
end
But if you had already gone production, you must fill in the data first. And you are not able to fill data, that is already deleted.
# console or migration
Category.find_each(&:save!)
This scenario preserves the deleted category name, additionally it will be much more efficient than a lot of Category.find.
Or you can do some kind of soft-delete as others suggested.

Search every field or attribute in model Rails

I have a simple model called Company with atributes such as Name, contact email, address, etc. I've created a search form where a user can find the company by searching for any attribute i.e., city, name of business, etc.
I'm new to Rails but I've figured out how to get it working like so in my controller.
if(params[:searchstring].present?)
logger.debug "*** Running search --> "
term = params[:searchstring]
#companies = Company.where('name LIKE ? OR name LIKE ? OR contactemail LIKE ? OR city LIKE ?' , "%#{term}%", "%#{term}%", "%#{term}%","%#{term}%" ).paginate(page: params[:page]).order('id DESC')
else
logger.debug "*** Running search --> get em all "
#companies = Company.all.paginate(page: params[:page], per_page: 5).order('id DESC')
end
end
This works fine but I'm guessing there is a much simplier way. I'd prefer not to modify the query every time I add an attribute. Any ideas on how to improve this?
You could go with something like this (It is just an example, might be a better way, but it is fonctionnal)
# company.rb
def self.search_all_properties term
query = ''
properties = Company.column_names
properties.each_with_index do |prop, index|
if index < properties.count - 1
query = query + prop + ' LIKE :term OR '
else
query = query + prop + ' LIKE :term'
end
end
term = "%" + term + "%"
Company.where(query, :term => term)
end
and use it like this
Company.search_all_properties term

Rails - where with multiple like options

I am trying pull information from a contacts table based on multiple like conditions. So far I have come up with the following
conditions = ""
conditions << "email_address LIKE '%#{params[:email_address]}%'" unless params[:email_address].blank?
conditions << " AND first_name LIKE '%#{params[:first_name]}%'" unless params[:first_name].blank?
conditions << " AND last_name LIKE '%#{params[:last_name]}%'" unless params[:last_name].blank?
conditions.sub!(/^AND/, '')
if !conditions.blank?
#contacts = Contact.where(conditions).page(params[:page]).per(10)
else
#contacts = Contact.all.page(params[:page]).per(10)
end
What I was wondering is ... is this the best way to do this? I would have thought there would be a nice way to add multiple conditions in the form of a hash and somehow specify that I want to use OR/AND and like.
I am fairly new to rails and google is not really helping much.
Thanks.
Just append the where calls directly to a scope:
#contacts = Contact.scoped
#contacts = #contacts.where("email_address LIKE '%?%'", params[:email_address]) if params[:email_address].present?
#contacts = #contacts.where("first_name LIKE '%?%'", params[:first_Name]) if params[:first_name].present?
#contacts = #contacts.where("last_name LIKE '%?%'", params[:last_name]) if params[:last_name].present?
You can use a simple loop to make it less repetative:
%(email_address first_name last_name).each do |field|
#contacts = #contacts.where("#{field} like '%?%'", params[field]) if params[field].present?
end
And do not build queries by hand by directly substituting user input into your query string. Rails makes that hard to do on purpose: You're bypassing all of Rails' sanitization and opening yourself to SQL injection.
I would have thought there would be a nice way to add multiple conditions in the form of a hash and somehow specify that I want to use OR/AND and like.
There is, but it only works with AND and =:
#contacts.where(first_name: "bob", last_name: "smith")
# select ... where first_name = 'bob' and last_name = 'smith'

I need a search form with a large number of fields. How do I do this cleanly in Ruby on Rails?

I'm building a database application that tracks a lot of data for a Person, such as first_name, last_name, DOB, and 20+ more fields.
Users will need to be able to search for all of these fields. I'm having trouble writing clean code for this. The code below is what I have so far in my people_controller:
The data is submitted from a form_tag
def search
#people = Person.all
general_info_string = String.new
if(params[:first_name] != "") then general_info_string << 'people.first_name = "' + params[:first_name] + '" AND ' end
if(params[:last_name] != "") then general_info_string << "people.last_name = '" + params[:last_name] + "' AND " end
... Lots more of similar clauses
general_info_string = general_info_string[0, general_info_string.length - 5]
# ^This line removes the trailing " AND " from the string
#people = #people.where(general_info_string)
end
general_info_string is so called because there are more "where" clauses(not shown) and separate strings that I build to search for them.
The problem here is that the code looks like a mess and seems like a "hacky" way to do something that should be well supported by Rails. How could I perform this operation in a cleaner way?
That's not just hacky - it leaves you open to a string injection attack.
You need a :conditions using the ? template, like this example:
:conditions => ["key1 = ?", var]
but you need to make the template part into a string that grows once per parameter, and you need to make the var into an array that grows with each parameter's value. That gives you something like this:
template = []
values = []
if params[:first_name].present?
template.push 'people.first_name = ?'
values.push params[:first_name]
end
if params[:last_name].present?
template.push 'people.last_name = ?'
values.push params[:last_name]
end
template = template.join(' AND ')
:conditions => [template, *values]
From there, you should DRY up all those ifs, such as with a table of value keys. Then you'd loop through the table, check the key, and push its results into the arrays:
fields = [:first_name, :last_name, :shoe_size, ...]
fields.each do |field|
if params[field].present?
template.push "people.#{field} = ?"
values.push params[field]
end
end

Creating a FullName method in the Devise User Model?

right now I use devise and have two fields. fname and lname.
I would like the ability to put a full_name field to the user model. Then the user model would take the full_name field and extract the fname and lname by splitting with a space. if there is more than one space that would all go to the fname and the last item would be the last name, and then saved to the user model with the extracted fields for fname and lname.
Is this possible without hacking up devise?
Thanks
The problem with both current answers is that they don't handle three(+) word names such as Billy Bob Thornton.
'Billy Bob Thornton'.split(/(.+) (.+)$/) => ["", "Billy Bob", "Thornton"]
'Billy Bob Thornton'.split(' ', 2) => ["Billy", "Bob Thornton"]
The original posting requests all but the last name to go to first name. So I'd suggest:
def full_name
[first_name, last_name].join(' ')
end
def full_name=(name)
elements = name.split(' ')
self.last_name = elements.delete(elements.last)
self.first_name = elements.join(" ")
end
You don't need to hack up devise persay, just do it in your Users model (or whatever modal you are usuing as your devise_for
class User
def fullname
self.fname << " " << self.lname
end
def fullname=(fname)
names = fname.split(/(.+) (.+)$/)
self.fname = names[0]
self.lname = names[1]
end
end
untested and off the top my head, but it should be a start....
i wouldn't suggest storing the fullname, just use it like a helper function, but that is up to you.

Resources