Function parameter and different use as a object and SQL query - ruby-on-rails

I am trying to build a function with a parameter and then use this parameter all over my function but I have some issue, I don't know how to convert it for this two different cases
def html_append(attribute)
[...].order(attribute: :asc) [...]
[...]current_object.attribute[...]
Do you have any lead?
Have a great day.
Jonathan

So what you could do here is send the attribute in as a symbol and handle it as follows:
def html_append(attribute)
Post.order(attribute => :asc)
current_object.public_send(attribute)
end
example_attribute = :name
html_append(example_attribute)
This should work just fine, the only thing with public_send is its gives whoever is sending in the attribute access to invoke any of your public methods (send gives then access to invoke all of them) so I would be careful where you are getting this attribute from and have a well thought out public and private interface for this object.

Great question!
For your second example, you can simply do something like this.
def html_append(attribute)
current_object[attribute]
end
Using the order method is a bit more complicated. Be careful because it might seem like a good idea to do something like this.
def unsafe_method(attribute)
[...].order("#{attribute} ASC") [...]
end
The above example would be vulnerable to SQL injection. In an ideal world, you’d be able to do something like this.
Model.all.order("? ASC", attribute)
But unfortunately that’s not part of the Rails API. As things are, you need to make sure that anything going into order is safe to execute against your database. There's a great answer about this already.
One simple option would be to have an array of safe attributes which you can validate the argument against. But be careful using this because if the if statement is removed, the method is open to SQL injection once again.
def safe_method(attribute)
safe_order_options = ["name", "email", "phone"]
if attribute.in? safe_order_options
[...].order("#{attribute} ASC") [...]
end
end
And even better option would be this.
def safer_method(attribute)
safe_order_options = ["name", "email", "phone"]
order_index = safe_order_options.index(attribute)
[...].order("#{safe_order_options[order_index]} ASC") [...]
end
Hope that helps. Sorry if there are any typos. I haven't tested this code, but it should work in principle.

What ever you do with attribute It is better to check for column names for security reasons.
def html_append(attribute)
return unless Post.class.column_names.include?(attribute.to_s)
# Above guard condition will make sure the security of below code
# your code do public_send(attribute) or send(). You are safe.
end

Related

Rails .where any field contains specific text

Is there a short-hand way of querying a Rails database for any record that has a field containing a specific piece of text? I know I could code every field with a .where("field_name LIKE ?", "my text"), but I have several fields and am wondering if there is a shorter way of doing this.
Thanks in advance.
I do not know of a framework-way to do so. You could code something using
my_attributes = YourModel.attributes
# delete attributes you do not need, like `id` etc.
# or just create an array with your desired attributes,
# whichever way is faster
queries = my_attributes.map { |attr| "#{attr} LIKE %insert_your_text_here%" }
# Do not use this if the text your looking for is provided by user input.
built_query = queries.join(" OR ")
YourModel.where(built_query)
This could bring you closer to your goal. Let me know if this makes sense to you.
edit: The answer https://stackoverflow.com/a/49458059/299781 mentions Ransack. That's a nice gem and takes the load off of you. Makes it easier, nicer and performs better :D
Glad you like this, but pay attention that you make your app open for sql injection, if you take user-input as the text you are looking for. (with this solution) Ransack would alleviate that.
class MyModel
scope :search_like, -> (field_name, search_string) {where("#{field_name} LIKE ?", "%#{search_string}%")}
end
then you can call it like:
MyModal.search_like('name', 'foobar')
UPDATE based on #holgar answer but beware if not indexed these searches can be slow on large data sets:
class MyModel
def self.multi_like(search_string)
my_attributes = [:first_name, :last_name] # probalby only use string fields here
queries = my_attributes.map { |attr| "#{attr} LIKE '%#{search_string}%'" }
where(queries.join(" OR "))
end
end
If you want full fledge text search based on params then you can use ransack gem

Rails common method for updating a database field

I am new to rails and I have a task to write a common method that will update a specific database field with a given value. And I should be able to invoke the method from anywhere in the app.(I understand about the security flaw and so on.. But I was asked to do it anyway) In my application controller I tried
def update_my_model_status(model,id,field, value)
#model = model.find(id)
#model.update(field: value)
end
Of course this doesn't work.. How to achieve this? What is the right way to do this? And if it is possible how to pass a model as an argument to a method?
If you're using Rails, why not use Rails?
Compare update_all:
MyModel.where(id: 1).update_all(banned: true)
or maybe update_attribute:
my_model.update_attribute(:banned, true)
to:
update_my_model_status(MyModel, 1, :banned, true)
Notice how, despite being shorter, the first two approaches are significantly more expressive than the last - it is much more obvious what is happening. Not only that, but they are immediately more familiar to any Rails developer off the street, while the custom one has a learning curve. This, combined with the added code from the unnecessary method adds to the maintenance cost of the application. Additionally, the Rails methods are well tested and documented - are you planning to write that, too? Finally, the Rails methods are better thought out - for example, your prototype naively uses attribute validations, but does not check them (which could result in unexpected behavior) and makes more SQL queries than it needs to. It's fine to write custom methods, but let's not write arbitrary wrappers around perfectly fine Rails methods...
Try this:
def update_my_model_status(model,id,field, value)
#model_var = model.capitalize.constantize.find(id)
#model_var.update_attributes(field: value)
end
Instead of just using update you should use update_attributes:
def update_my_model_status(model,id,field, value)
#model_var = model.find(id)
#model.update_attributes(field: value)
end
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update

Rails 4: Return all instances from a where call

I have a status integer column in a invoices table
def change
create_table :invoices do |t|
t.integer :status
end
end
I do a find like this
def find_status(status)
Invoice.where(status: status)
end
This is great when I want to find all invoices with, say status 1.
But, sometimes I want to the find_status method return all invoices?
I could solve this with a if statement, but my question is;
Can I pass something into the find_status method that will return all invoices?
PS: After reviewing this question I understand if someone get the temptation to suggest other solutions to the problem. Please just look at this question as a "prof of concept kind of question"
You could put an array or a range into the method call assuming you've not got hundreds of statuses, but unable to resist the temptation - if you want them all I'd avoid the method call and just do Invoice.all
You can change your find_status method like below if you insist on using the same method for both.
def find_status(status = nil)
status.nil? ? Invoice.all.to_a : Invoice.where(status: status)
end
It is absolutely impossible ! And hopefully, that would be a real security issue if we could do that.
Nevertheless, if you don't mind a lack of security, you can still write something like
def find_status(status)
Invoice.where("status = #{status}")
end
And then
find_status(1) #=> All invoices with the status 1
find_status('status') #=> All invoices :)
But again, what I did was exploiting a lack of security !
And as you said, you could easily use an if or a ? condition statement
Actually ! You can do that:
find_status Invoice.pluck(:status)
It works without changing your method :)
It is nearly impossible to do that as where doesn't support wildcards in this scenario and options like huge range result in very unclean code. But you might use Invoice.all call instead of calling this method.

Is the Active Record Base update method deprecated?

I'm trying to update many active records at the same time using the :update method and they don't seem to update fine.
#drop_ship_order_line_items = DropShipOrderLineItem.update(params[:drop_ship_order_line_items].keys, params[:drop_ship_order_line_items].values).reject { |dsoli| dsoli.errors.empty? }
params[:drop_ship_order_line_items] returns the following hash:
{"11"=>{"available"=>"1"}, "2"=>{"available"=>"1"}}
But the models don't seem to update correctly...anyone with insides?
AFAIK you can't update models like this on rails, you would have to do it like this:
params[:drop_ship_order_line_items].each do |key,value|
DropShipOrderLineItem.find( key ).update_attributes( value )
end
EDIT
There's probably an attr_protected call somewhere in your code, you should check which attributes are protected or not in there.
If you think you can safely ignore the protection on this specific call, you can use some sending do work out the magic (disclaimer: this is on your own, i'm just showing a possibility):
params[:drop_ship_order_line_items].each do |key,value|
ship = DropShipOrderLineItem.find( key )
value.each do |property,value|
ship.send( "#{property}=", value )
end
ship.save
end
This is going to overcome the attribute protection, but you should make sure this is a safe call and you're not going to burn yourself by doing this.

Use find to initialize a constant?

Something like this:
class Category
SOME_CATEGORY = find_by_name("some category")
end
Category::SOME_CATEGORY
tried without a problem, but want to know if it is a bad idea, and the reasons if any..
thanks
If you don't want to hit the database each time you'll have to cache the model. There are several ways to do this, but one quick way is using Memoization. This was introduced in Rails 2.2.
class Category < ActiveRecord::Base
class << self
extend ActiveSupport::Memoizable
def named(name)
find_by_name(name)
end
memoize :named
end
end
Use it like this.
Category.named("some category") # hits the database
Category.named("some category") # doesn't hit the database
The cache should stay persistent across requests. You can reset the cache by passing true as the last parameter.
Category.named("some category", true) # force hitting the database
What do you want to do?
Maybe:
class Category
def self.some_category
Category.find_by_name("some category")
end
end
So you can call:
Category.some_category
=> <Category#2....>
It's not a terrible idea, but it's not really a good one either. It doesn't really fall in line with the way Rails does things. For one thing, you'll end up with a lot of ugly constant code. Too many ALL_CAPS_WORDS and your Ruby starts to look like C++. Bleah.
For another, it's inflexible. Are you going to make one of these constants for every category? If you add a new category two months from now, will you remember to update your Rails code, add a new constant, redeploy it and restart your server?
If it's important to you to be able to access categories very easily, and not repeat DB queries, here's a bit of metaprogramming that'll automatically look them up and create static methods like Lichtamberg's for you on first access:
def self.method_missing(category, *args) # The 'self' makes this a class method
#categories ||= {}
if (#categories[category] = find_by_name(category.to_s))
class_eval "def self.#{category.to_s}; #categories[#{category}]; end"
return #categories[category]
end
super
end
With this method in place, whenever you first call Category.ham, it'll create a class method that returns the value of find_by_name("ham") -- so that neither the query nor method_missing() runs again the next time you call it. This is pretty much the way the OpenStruct class works, BTW; look it up in the Pickaxe book if you want to learn more.
(Of course you'll still have the risk that, because these are all memoized, your Rails app won't reflect any changes you make to your category objects. This makes the assumption that changes won't happen or don't really matter. It's up to you to determine whether that assumption is valid for your app. You could always put an after_update callback in your code that resets ##categories if that's a problem; but at that point this starts to get complicated.)

Resources