Referencing Model with String input - ruby-on-rails

Lets say I wish to make a page that can query the desired object with type(string) and id(int).
/query?type=people&id=1
would fetch me
Person.find(1)
whereas
/query?type=cities&id=123
would fetch me
City.find(123)
However, I have problems as to how to translate strings into the desired model class.
The only way I can think of is
case params[:type]
when 'people'
#object = Person.find(params[:id])
when 'cities'
#object = City.find(params[:id])
end
However, this method will be quite problematic if I have more types of models.
Is there a better way?
Thank you in advance,

Try:
klass = params[:type]
klass.singularize.classify.constantize.find(params[:id])
Edit:
#object = params[:type].singularize.classify.constantize.find(params[:id])

Related

DRY way of assigning new object's values from an existing object's values

I created a class method that is called when a new object is created and copied from an old existing object. However, I only want to copy some of the values. Is there some sort of Ruby shorthand I can use to clean this up? It's not entirely necessary, just curious to know if something like this exists?
Here is the method I want to DRY up:
def set_message_settings_from_existing existing
self.can_message = existing.can_message
self.current_format = existing.current_format
self.send_initial_message = existing.send_initial_message
self.send_alert = existing.send_alert
self.location = existing.location
end
Obviously this works perfectly fine, but to me looks a little ugly. Is there any way to clean this up? If I wanted to copy over every value that would be easy enough, but because I only want to copy these 5 (out of 20 something) values, I decided to do it this way.
def set_message_settings_from_existing(existing)
[:can_message, :current_format, :send_initial_message, :send_alert, :location].each do |attribute|
self.send("#{attribute}=", existing.send(attribute))
end
end
Or
def set_message_settings_from_existing(existing)
self.attributes = existing.attributes.slice('can_message', 'current_format', 'send_initial_message', 'send_alert', 'location')
end
a hash might be cleaner:
def set_message_settings_from_existing existing
fields = {
can_message: existing.can_message,
current_format: existing.current_format,
send_initial_message: existing.send_initial_message,
send_alert: existing.send_alert,
location: existing.location
}
self.attributes = fields
end
you can take this further by only selecting the attributes you want:
def set_message_settings_from_existing existing
fields = existing.attributes.slice(
:can_message,
:current_format,
:send_initial_message,
:send_alert,
:location
)
self.attributes = fields
end
at this point you could also have these fields defined somewhere, eg:
SUB_SET_OF_FIELDS = [:can_message, :current_format, :send_initial_message, :send_alert, :location]
and use that for your filter instance.attributes.slice(SUB_SET_OF_FIELDS)

Problem with selecting elements with the same params

What i do wrong? I want to return every products which pass condition:
def show
products = Product.select{|x| x[:category_id] == params[:id]}
render json: products
end
When i write
def show
products = Product.select{|x| x[:category_id] == 1}
render json: products
end
it works why the first example doesn't work?
I am pretty sure that there is mismatch in data type.
1=='1' #will be always false
1==1 #will be true
'1'=='1' #will be true as well
And also check for nil value from params[:id]
Please make sure to change as follows
def show
products = Product.select{|x| x.category_id == params[:id].to_i}
render json: products
end
OR
The best solution as suggested by #Eyeslandic is to use .where as it will not check for mismatch in data type. And also you don't have to take care of nil value from params[:id].
You should really be using a where to stop sql from loading all your products.
#products = Product.where('category_is = ?', params[:id])
The being said, if you are sticking to rails restful conventions, the fact you have a param called :id that is the category_id suggests you are on the category controller. So maybe consider changing your logic to:
#category = Category.includes(:products).find(params[:id])
you can then access products via
#category.products
or if your not interested in the category too much maybe
#products = Category.includes(:products).find(params[:id])&.products

Rails how to use where method for search or return all?

I am trying to do a search with multiple attributes for Address at my Rails API.
I want to search by state, city and/or street. But user doesn't need to send all attributes, he can search only by city if he wants.
So I need something like this: if the condition exists search by condition or return all results of this condition.
Example:
search request: street = 'some street', city = '', state = ''
How can I use rails where method to return all if some condition is nil?
I was trying something like this, but I know that ||:all doesn't work, it's just to illustrate what I have in mind.:
def get_address
address = Adress.where(
state: params[:state] || :all,
city: params[:city] || :all,
street: params[:street] || :all)
end
It's possible to do something like that? Or maybe there is a better way to do it?
This is a more elegant solution using some simple hash manipulation:
def filter_addesses(scope = Adress.all)
# slice takes only the keys we want
# compact removes nil values
filters = params.permit(:state, :city, :street).to_h.compact
scope = scope.where(filters) if filters.any?
scope
end
Once you're passing a column to where, there isn't an option that means "on second thought don't filter by this". Instead, you can construct the relation progressively:
def get_address
addresses = Address.all
addresses = addresses.where(state: params[:state]) if params[:state]
addresses = addresses.where(city: params[:city]) if params[:city]
addresses = addresses.where(street: params[:street]) if params[:street]
addresses
end
I highly recommend using the Searchlight gem. It solves precisely the problem you're describing. Instead of cluttering up your controllers, pass your search params to a Searchlight class. This will DRY up your code and keep your controllers skinny too. You'll not only solve your problem, but you'll have more maintainable code too. Win-win!
So in your case, you'd make an AddressSearch class:
class AddressSearch < Searchlight::Search
# This is the starting point for any chaining we do, and it's what
# will be returned if no search options are passed.
# In this case, it's an ActiveRecord model.
def base_query
Address.all # or `.scoped` for ActiveRecord 3
end
# A search method.
def search_state
query.where(state: options[:state])
end
# Another search method.
def search_city
query.where(city: options[:city])
end
# Another search method.
def search_street
query.where(street: options[:street])
end
end
Then in your controller you just need to search by passing in your search params into the class above:
AddressSearch.new(params).results
One nice thing about this gem is that any extraneous parameters will be scrubbed automatically by Searchlight. Only the State, City, and Street params will be used.

For array of ActiveRecord objects, return array of their attributes

#matched = [1, 2, 3]
Where each integer represents the id of an ActiveRecord object in the Inventory class. As a next step, I want to look at each of those objects and obtain the email of the parent User, but I'm not sure how to do it. Ideally I'd write something like:
Inventory.where(id: #matched).user.email
Because certainly, this statement would work if I only had a single id to look up. Since that doesn't work, I'm instead doing this
#email = []
#matched.each do |i|
#email << Inventory.find_by_id(i).user.email
end
Just wondering if there's an easier way.
Thanks!
If you only need the email addresses then you can use the pluck method:
Inventory.where(id: #matched).joins(:user).pluck("users.email")
class Inventory
def self.with_ids(ids)
sql = #matched.map{|id| "id = #{id}"}.join(" OR ")
where(sql)
end
def parent_email
user.email
end
end
Inventory.with_ids(#matched).map(&:parent_email)

How to delete a single instance from a collection

I'm trying to delete a single instance from a database query. "l.remove" represents what i want to do but i know its wrong. I have tried delete and destroy. destroy didn't work and delete actually removed the data from the database. I just want the data removed from the variable. Can anyone help me?
<%
#owner = User.find(params[:id])
#job_list = ShoppingList.where(:user_id=>#user.user_id)
#job_list.each do |l|
#temp = FlaggedCandidate.where(:flagged_user_id=>#owner.user_id, :list_id=>l.list_id)
if !#temp.nil?
l.remove
end
end
#candidate = FlaggedCandidate.new
%>
based on the code i assume that User has many ShoppingList.
You can do something like:
#job_list = #owner.shopping_lists.where( list_id: FlaggedCandidate.where( flagged_user_id: #owner.user_id ).pluck(:list_id) )
That could save the trouble of looping around.
You are trying to remove record from db. In order to just modify collection #job_list you need reject some unsatisfied elements. You can do it with select method (to select job_lists that flagged), or reject in opposite. This is how you code should looks like:
#owner = User.find(params[:id])
#job_list = ShoppingList.where(:user_id=>#user.user_id)
#job_list.select! do |job_list|
FlaggedCandidate.where(
:flagged_user_id => #owner.user_id,
:list_id => job_list.list_id
).any?
end
#candidate = FlaggedCandidate.new
select! simply change the original collection, instead of doing #job_list = #job_list.select { ... }

Resources