Scope is not recognizing both columns? - ruby-on-rails

On my Tokeninput autocomplete field I am trying to make the returned columns be both my :address and :website when it goes by the defined :store method.
class BusinessStore < ActiveRecord::Base
scope :search_by_store, lambda { |q|
(q ? where(["address LIKE ? or website LIKE ? like ?", '%'+ q + '%', '%'+ q + '%','%'+ q + '%' ]) : {})}
def store
if self.online_store
"#{business_name} - #{website}"
else
"#{business_name} - #{address}"
end
end
end
class BusinessStoresController < ApplicationController
def index
#business_stores = BusinessStore.all
#business_stores = BusinessStore.search_by_store(params[:q])
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #business_stores }
format.json { render :json => #business_stores.collect{|b|{:id => b.id, :name => b.store } } }
end
end
end
My json page : http://localhost:3000/business_stores.json shows all the results correctly but the Token field only shows :address results and not website ones. How do I fix this?

Try this:
(q ? where(["address LIKE ? OR website LIKE ?", "%#{q}%", "%#{q}%" ]) : {})}

Related

How to render a dependency(project.tasks) in a two model (project + task) json result?

Given that each project has_many :tasks, I hope to render the project.task within the json result.
However, the json output also include a list of individual tasks as part of the result. See below:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
#all = #tasks + #projects
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
per_page: 25 }
end
This means that if I simply include:
respond_to do |format|
format.html
format.json { paginate json: #all.sort_by(&:created_at).reverse,
:include => [:tasks => {:only => :id}],
per_page: 25 }
end
Rails will throw an error of undefined method tasks for Task:0x007fa0ad8d3858 since tasks does not have a task method.
How can I have the project.tasks appear in a json result which also include individual tasks result? Thank you.
Consider using active_model_serializers gem. After installing you can define a serializer for Project model like so:
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :created_at, :tasks
def tasks
object.tasks.map(&:id)
end
end
Note: There might be any attributes you need. It's just an example.
Then you can do:
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
It will return you an array:
[{:id => 1, :created_at => "2017-07-13 08:13:20", tasks => [1, 2, 3, ...]}, ...]
Then for json response you can concat #tasks and serialized_projects:
all_for_json = #tasks + serialized_projects
And finally you can sort it like this:
all_for_json.sort_by { |record| record[:created_at] }.reverse
Note that you should do exactly record[:created_at], because projects are hashes, not active record models.
But I don't think this is a good idea to mix hashes and active record models in one array. So there is another solution.
You can also define a serializer for Task model:
class TaskSerializer < ActiveModel::Serializer
attributes :id, :created_at
end
Note: There might be any attributes you need. It's just an example.
And override code like this:
#tasks = Task.all.reject do |i|
i.project.inbox == false || i.completion_status == 100
end
#projects = Project.all.reverse.reject do |i|
i.inbox == true || i.completion_status == 100
end
respond_to do |format|
format.html do
#all = #tasks + #projects
end
format.json do
serialized_tasks = ActiveModelSerializers::SerializableResource.new(#tasks, each_serializer: TaskSerializer).as_json
serialized_projects = ActiveModelSerializers::SerializableResource.new(#projects, each_serializer: ProjectSerializer).as_json
all_serialized = serialized_tasks + serialized_projects
paginate json: all_serialized.sort_by { |record| record[:created_at] }.reverse, per_page: 25
end
end
To DRY your code, you can put
ActiveModelSerializers::SerializableResource.new(...).as_json
to separate method. For example:
def serialize_collection(collection, each_serializer)
ActiveModelSerializers::SerializableResource.new(collection, each_serializer: each_serializer).as_json
end
And do serializations like this:
serialized_tasks = serialize_collection(#tasks, TaskSerializer)
serialized_projects = serialize_collection(#projects, ProjectSerializer)
Profits of this solution:
You don't mix active record models and hashes in one array.
You can easily define via serializers which attributes and associations to include and set custom names for them.

How to refactor ActiveRecord query?

I have a code in controller:
def latest
#latest_articles = user_signed_in? ? Article.limit(10).order(id: :desc).pluck(:id, :title) : Article.where("status = ?", Article.statuses[:public_article]).limit(10).order(id: :desc).pluck(:id, :title)
render json: #latest_articles
end
How to refactor it to looks elegant?
I tried using lambda:
extract = lambda {|a| a.order(id: :desc).pluck(:id, :title)}
Article.limit(10) {|a| a.extract}
but it returns only Article.limit(10)
UPD: I need to get last 10 of all articles if user is signed in, and last 10 of only public ones if not.
I would create an initial scope, and modify it based on some conditions:
def latest
scope = Article.order(id: :desc)
scope = scope.where(status: Article.statuses[:public_article]) if user_signed_in?
render json: scope.limit(10).pluck(:id, :title)
end
You could refactor as
#lates_articles = Article.all
#lates_articles = #latest_articles.where("status = ?", Article.statuses[:public_article]) unless user_signed_in?
render json: #latest_articles.limit(10).order(id: :desc).pluck(:id, :title)
But it would be better to create model method
class Article < ActiveRecord::Base
...
scope :latest, -> {last(10).order(id: :desc)}
def self.public user_signed
if user_signed
all
else
where("status = ?", statuses[:public_article])
end
end
...
end
Then you would use it like
def latest
render json: Article.public(user_signed_in?).latest.pluck(:id, :title)
end
final version:
def latest
scope = Article.order(id: :desc)
scope = scope.shared unless user_signed_in?
render json: scope.limit(10), except: [:body, :created_at, :updated_at]
end

Previous/Next links based on model's function

How do I display previous/next links according to vehicle.vehicle_status
In my show view:
- if #vehicle.previous
= link_to "< Previous", #vehicle.previous
- if #vehicle.next
= link_to "Next >", #vehicle.next
In my model:
def previous
?
end
def next
?
end
vehicles/index.html.haml view:
- #vehicles.each do |vehicle|
%tr
%td.dashbox{:class => vehicle.vehicle_status, :style =>'width:18px;', :onclick=>"top.location=#{vehicle_url(vehicle)}"}
vehicle_status in model:
def vehicle_status
if self.maintenance_status=='c1' or self.fuel_efficiency_status=='c1' or self.system_status=='c1'
'c1'
elsif self.maintenance_status=='c2' or self.fuel_efficiency_status=='c2' or self.system_status=='c2'
'c2'
elsif self.maintenance_status=='c4' or self.fuel_efficiency_status=='c4' or self.system_status=='c4'
'c4'
else
'c3'
end
end
scope :previous, lambda { |vehicle|
where("vehicles.vehicle_status < ?", vehicle.vehicle_status).
order(:vehicle_status).reverse
}
scope :next, lambda { |vehicle|
where("vehicles.vehicle_status > ?", vehicle.vehicle_status).
order(:vehicle_status)
}
def previous
#previous ||= Vehicle.previous(self).first
end
def next
#next ||= Vehicle.next(self).first
end
In light of the fact that vehicle_status is a calculation and not a static field, and that there are other factors involved in the sorting, you can modify the scopes to look like this (using a helper method for the CASE string):
def self.vehicle_status_sql
<<-SQL
CASE
WHEN 'c1' IN (maintentance_status, fuel_efficiency_status, system_status) THEN 'c1'
WHEN 'c2' IN (maintentance_status, fuel_efficiency_status, system_status) THEN 'c2'
WHEN 'c4' IN (maintentance_status, fuel_efficiency_status, system_status) THEN 'c4'
ELSE 'c3'
END
SQL
end
scope :previous, lambda { |vehicle|
where("#{vehicle_status_sql} < :status OR (#{vehicle_status_sql} = :status AND (vehicles.odometer < :odometer OR (vehicles.odometer = :odometer AND vehicles.id < :id)))",
status: vehicle.vehicle_status, odometer: vehicle.odometer, id: vehicle.id).
order("#{vehicle_status_sql} DESC, vehicles.odometer DESC")
}
scope :next, lambda { |vehicle|
where("#{vehicle_status_sql} > :status OR ((#{vehicle_status_sql} = :status AND (vehicles.odometer > :odometer OR (vehicles.odometer = :odometer AND vehicles.id > :id)))",
status: vehicle.vehicle_status, odometer: vehicle.odometer, id: vehicle.id).
order("#{vehicle_status_sql}, vehicles.odometer)
}

Push name to results unless the name already exists?

I have the following in my tags controller (params[:q] comes from this plugin: http://loopj.com/jquery-tokeninput/). This is basically a slightly modified product of this screencast: http://railscasts.com/episodes/258-token-fields.
tags_controller.rb:
class TagsController < ApplicationController
def index
#tags = Tag.where("name like ?", "%#{params[:q]}%")
results = #tags.map(&:attributes)
results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}
respond_to do |format|
format.html
format.json { render :json => results }
end
end
I want to only do results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"} only if the name doesn't exist already in #tags. Because right now, it looks like this:
programming #input field
programming #drop-down menu
Add: progamming #drop-down menu
I want it to just display like
programming #input field
Add: progamming #drop-down menu
How to accomplish that?
EDIT:
Here is the model and JavaScript just in case:
application.js
$(function() {
$("#post_tag_tokens").tokenInput("/tags.json", {
crossDomain: false,
prePopulate: $("#post_tag_tokens").data("pre"),
preventDuplicates: true,
theme: "facebook"
});
});
post.rb:
def tag_tokens=(ids)
ids.gsub!(/CREATE_(.+?)_END/) do
Tag.find_or_create_by_name(:name => $1).id
end
self.tag_ids = ids.split(",")
end
You can do this:
#tag = Tag.find_by_name(params[:q])
or
#tag = Tag.name_like(params[:q]) #For this you need to install gem [searchlogic][1]
if #tag.blank?
# Do you things
end

Rails DRY up similar controller actions

I have two separate controllers that inherit from Admin::UserBaseController, display a searchable, sortable table of users, and use the same partial views.
Admin::UsersController - Display users within the context of a given organization.
Admin::OrganizationsController - Displays all users for the system.
Here is the index method of Admin::UsersController:
def index
q = "%#{params[:search]}%"
#users = User.where("first_name like ? or last_name like ? or username like ?", q, q, q).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #users }
end
end
Here is the edit method of Admin::OrganizationsController:
def edit
#organization = Organization.find(params[:id])
q = "%#{params[:search]}%"
#users = #organization.users.where("first_name like ? or last_name like ? or username like ?", q, q, q).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])
end
There is a lot of similarity between the two methods in the way that the #users variable is assigned. It's a difference of User and #organization.users and that's it. How do I DRY this up?
So what this screams is scopes. This removes the duplicate queries into a single place in the model and enables you to chain scopes onto the class and associations.
class User < ActiveRecord::Base
scope :search_identity, lambda { |identity| where("first_name like ? or last_name like ? or username like ?", identity, identity, identity) }
scope :user_order, lambda { |column,direction| order("#{column} #{direction}") }
end
Then in Admin::UsersController
q = "%#{params[:search]}%"
#users = User.search_identity( q ).user_order( sort_column, sort_direction).paginate(:page => params[:page])
In Admin::OrganizationsController:
q = "%#{params[:search]}%"
#users = #organization.users.search_identity( q ).user_order( sort_column, sort_direction).paginate(:page => params[:page])
Making everything nice and succinct.
Move
where("first_name like ? or last_name like ? or username like ?", q, q, q).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])
to a method in User
such as:
def self.method_name(q,params)
where("first_name like ? or last_name like ? or username like ?", q, q, q).order(sort_column + ' ' + sort_direction).paginate(:page => params[:page])
end
then just use that method in place of the where

Resources