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
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.
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
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)
}
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
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