How does ransack search works on an array list? - ruby-on-rails-4

I am using Rails 4.2.5, Ruby 2.2, ransack.
I am trying to implement search functionality using Ransack. I have something like:
emails = ["abc#abc.com", "a#a.com", "b#b.com"]
users_list = emails.map{|a| User.where(email: a).first}
checked_in_users = Kaminari.paginate_array(users_list)
This gives me proper list of users in the page. But if I want to search by email, what should I do ?
#q = checked_in_users.ransack params[:q]
This gives me:
"NoMethodError (undefined method `ransack' for #<Array"
HAML code:
= search_form_for [#q], url: users_path(some_id: id) do |form|
= form.text_field :user_name_or_user_email_cont, placeholder: 'Name or email', class: 'form-control'
What would be the correct way to do it with ransack ?

If you want to search the entire list of users, you could do something like this:
#q = User.ransack(params[:q])
#users = #q.result.page(params[:page])
You want to call ransack on your model prior to adding pagination. Once you have the ransack result, you can add pagination. This section of the documentation shows you how to handle pagination with ransack. In its most basic form, this is how you can use ransack.
Also, it looks like you have some odd code in your example. Apologies if I'm misunderstanding what you're trying to do.
When you call emails.map{|a| User.where(email: a).first}, you're making a where query 3 times in this case and returning an array of 3 models. Ransack won't operate on that array.
I would change it to something like this in your case:
emails = ["abc#abc.com", "a#a.com", "b#b.com"]
#q = User.where(email: emails).ransack(params[:q])
#checked_in_users = #q.result.page(params[:page])
Just know that you'd be searching from an array of users with emails of "abc#abc.com", "a#a.com", and "b#b.com". If you search with your ransack form for "bob#example.com", you wouldn't get any search results. This may not be what you want.

Related

Ajax Datatables Rails - Query not working for show-method

Let's start with the database-model:
# => ProductSelection.rb
has_many :products, -> { uniq }, through: :product_variants
# => Product.rb
has_many :product_informations
belongs_to :product_configuration
belongs_to :product_class
Using plain Ruby on Rails, we collected the products to show inside the product_selection#show-method like so:
#products = ProductSelection.find(params[:id]).products.includes(:product_informations, :product_class, :product_configuration)
and generated a table like so:
= table(#products).as(:products).default do |product|
= product.name
= product.product_configuration.name
= product.product_class.name
= product.state
= link_to product_product_selection_path(#product_selection, product_id: product.id), method: :delete
Now we want to use Datatables instead of plain Ruby on Rails.
We are using the Ajax-Datatables-Rails-Gem. We would like all columns to be sortable and searchable in the end.
Unfortunately we do not come past the query to retrieve the Products belonging to the ProductSelection in question.
This is the query we tried so far:
def get_raw_records
ProductSelection.find(params[:id]).products.includes(:product_informations, :product_class, :product_configuration).references(:product_information, :product_class, :product_configuration).distinct
end
def data
records.map do |record|
[
record.name,
record.product_configuration.name,
record.product_class.name,
record.state,
record.id
]
end
end
The error that pops up:
> undefined method `each_with_index' for nil:NilClass
When adding in sortable/searchable columns, the error is the following instead:
PG::UndefinedColumn at /product_selections/fetch_table_data_show.json
=====================================================================
> ERROR: column products.name does not exist
LINE 1: SELECT DISTINCT "products"."id", products.name
with the configuration like so:
def sortable_columns
# Declare strings in this format: ModelName.column_name
#sortable_columns ||= %w(Product.name ProductConfiguration.name ProductClass.name Product.state)
end
def searchable_columns
# Declare strings in this format: ModelName.column_name
#searchable_columns ||= %w(Product.name ProductConfiguration.name ProductClass.name Product.state)
end
I think the problem is with the different models right here. I assume Datatables-Rails expects a model of ProductSelection but instead is prompted with a Product. Any help would be highly appreciated!
If anything is missing, let me know!
After having another look at this, we figured the problem was that Product.name wasn't a valid ActiveRecord-construct. Instead it was a helper method defined somewhere down the road.
Since the gem tries to find records through the column-names, this error occurred.
We solved it by removing the column in question. Other approaches would be to:
Store the data in a separate column, instead of using a helper.
Implementing the helpers behavior in javascript so that we can pass
it as a callback to datatables.

How does one go about partial updates?

How does one go about partial updates (i.e. via PATCH)? rake routes indicates that def update handles PUT and PATCH. This is how my Rails API is setup:
#user.first_name = user_params[:attributes][:'first-name']
#user.last_name = user_params[:attributes][:'last-name']
In user model. Both first_name and last_name have validates … presence: true. However, client, is trying to hit the endpoint with just attributes[first-name]. Note, attributes[last-name] is not being passed in the request. Rails thinks that #user.first_name has a value, but #user.last_name is nil. So a validation error is thrown
One way I thought of going about this was something like:
#user.first_name = user_params[:attributes][:'first-name'].present? ? user_params[:attributes][:'first-name'] : #user.first_name
#user.last_name = user_params[:attributes][:'last-name'].present? ? user_params[:attributes][:'last-name'] : #user.last_name
Is this a viable approach? Or is there something better I can consider?
EDIT. A more sophisticated problem is when I need to pre-calculate before actually saving the object. Take for example a product trying to update its price against a discount value, if present
def update
product = Product.find(params[:id])
product.amount_in_cents = product_params[:attributes][:'amount-in-cents']
product.discount_in_percentage = product_params[:attributes][:'discount-in-percentage'].present? ? product_params[:attributes][:'discount-in-percentage'].to_f : nil # Can be 0.10
if product.discount_in_percentage.present?
product.amount_in_cents = product.amount_in_cents + (product.amount_in_cents * product.discount_in_percentage)
else
product.amount_in_cents = product.amount_in_cents
end
if product.save
# ...
end
end
In Rails, we have a convention that the attributes in Model should be fetched to the Rails App like user[first_name] , user[last_name] and in controller we build a private method like users_params which will represent the data to be fed to the User model. like
# in controller
def update
user = User.find(params[:id])
user.update(users_params)
end
private
# This will prepare a whitelisted params data
def users_params
params.require(:user).permit(:first_name, :last_name, ...)
end
No need of this
#user.first_name = user_params[:attributes][:'first-name'].present? ? user_params[:attributes][:'first-name'] : #user.first_name
#user.last_name = user_params[:attributes][:'last-name'].present? ? user_params[:attributes][:'last-name'] : #user.last_name
In your case, you need to reformat the params keys to first_name instead of first-name and so forth. This will help you do your stuff with ease.
Tip: Try to keep it simpler as possible

Rails 4.2 - How to properly use rails sanitize to protect against cross scripting vulnerabilites

I am on rails 4.2.1 (ruby 2.2.1p85) and I want to sanitize user input and also have that text cleared out of the post/get params. I don't want to solely depend on native rails 4 auto escaping.
Being a bit new to rails I was not aware of the sanitize options.
http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html
I was originally going to create a helper for such purposes so I could do something as shown below. I'm trying to sanitize form input to secure against Cross-site scripting (XSS) vulnerabilities.
basic helper example This works but its probably not the best rails way to do it? Also the text is still in the post/get request.
<%= text_field_tag :input_text, Safetxt(params[:input_text]) %>
helper method
def Safetxt(input_value)
txtOut = input_value
if txtOut.blank?
txtOut = ""
else
txtOut = txtOut.gsub("<script>", "")
txtOut = txtOut.gsub("</script>", "")
end
return txtOut
end
on submit the input_text is cleaned on output but the get/post values still contain the stripped values.
input_text=<script>alert%28"blah"%29<%2Fscript>
How can I utilize a custom scrubber in a helper method to properly sanitize input (remove dangerous text,tags)? I'm a bit confused how to implement this properly with custom rules.
For example something as shown below (I know this is wrong). Also what is the best way to exclude that text in the post/get request as well? I know I should sanitize the text in the controller side for input, but if there is a way, I'd like that text to be cleared on the submit request if that's possible.
def Safetxt(input_value)
scrubber = Rails::Html::TargetScrubber.new
scrubber.tags = ['script']
txtOut = input_value
if txtOut.blank?
txtOut = ""
else
txtOut.scrub!(scrubber)
end
return txtOut
end
You can use the Rails 4 sanitize method to strip out tags that are not "whitelisted" by default by rails.
So in your controller code you can have:
ActionView::Base.new.sanitize("<script>alert('hello')</script>")
which would strip out the script tag. You can whitelist your own attributes or elements rather than the default or if you want more custom behavior you can define your own scrubber in the sanitize method.

Filter sunspot search by class

I've set up a sunspot/solr search with rails that works like a charme:
# run the search
search_results = Sunspot.search Project, Question, User, Requests do
fulltext params[:search]
paginate page: params[:page], per_page: 10
end
It could be cool to filter that search results by a given class. E.g.there is a param in the URL like params[:class], it would be awesome if the search would only consist of results from that specific class (Project, Question, User, Requests...).
I just can't figure out where to start at.
Hope someone could help me out!
Got it. All you need to do is adding the class as a string to the searchable block:
# solr search
searchable do
text :firstname, :lastname, :email, :position
integer :account_id
string :klass
end
def klass
return self.class.to_s.downcase
end
After that you're able to filter by the class (klass)
# run the search
search_results = Sunspot.search Project, Question, User do
fulltext params[:search]
with :account_id, params[:account_id]
with :klass, "question"
paginate page: params[:page], per_page: 10
end

Using django haystack search with global search bar in template

I have a django project that needs to search 2 different models and one of the models has 3 types that I need to filter based on. I have haystack installed and working in a basic sense (using the default url conf and SearchView for my model and the template from the getting started documentation is returning results fine).
The problem is that I'm only able to get results by using the search form in the basic search.html template and I'm trying to make a global search bar work with haystack but I can't seem to get it right and I'm not having a lot of luck with the haystack documentation. I found another question on here that led me to the following method in my search app.
my urls.py directs "/search" to this view in my search.views:
def search_posts(request):
post_type = str(request.GET.get('type')).lower()
sqs = SearchQuerySet().all().filter(type=post_type)
view = search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=sqs,
form_class=HighlightedSearchForm
)
return view(request)
The url string that comes in looks something like:
http://example.com/search/?q=test&type=blog
This will get the query string from my global search bar but returns no results, however if I remove the .filter(type=post_type) part from the sqs line I will get search results again (albeit not filtered by post type). Any ideas? I think I'm missing something fairly obvious but I can't seem to figure this out.
Thanks,
-Sean
EDIT:
It turns out that I am just an idiot. The reason why my filtering on the SQS by type was returning no results was because I didn't have the type field included in my PostIndex class. I changed my PostIndex to:
class PostIndex(indexes.SearchIndex, indexes.Indexable):
...
type = indexes.CharField(model_attr='type')
and rebuilt and it all works now.
Thanks for the response though!
def search_posts(request):
post_type = str(request.GET.get('type')).lower()
sqs = SearchQuerySet().filter(type=post_type)
clean_query = sqs.query.clean(post_type)
result = sqs.filter(content=clean_query)
view = search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=result,
form_class=HighlightedSearchForm
)
return view(request)