I'm writing a Conference listing website in Rails, and came across this requirement:
chain, in no particular order, a URL to find events, such as:
/in/:city/on/:tag/with/:speaker
or rearranged like
/in/:city/with/:speaker/on/:tag
i can handle these fine one by one. is there a way to dynamically handle these requests?
i achieved it with the following (excuse some app-specific code in there):
routes.rb:
map.connect '/:type/*facets', :controller => 'events', :action => 'facets'
events_controller.rb:
def facets
#events = find_by_facets(params[:facets], params[:type])
render :action => "index"
end
application_controller.rb:
def find_by_facets(facets, type = nil)
query = type.nil? ? "Event" : "Event.is('#{type.singularize.capitalize}')"
for i in (0..(facets.length - 1)).step(2)
query += ".#{facets[i]}('#{facets[i+1].to_word.capitalize_words}')"
end
#events = eval(query)
end
event.rb:
named_scope :in, lambda { |city| { :conditions => { :location => city } } }
named_scope :about, lambda {
|category| {
:joins => :category,
:conditions => ["categories.name = ?", category]
}
}
named_scope :with, lambda {
|speaker| {
:joins => :speakers,
:conditions => ["speakers.name = ?", speaker]
}
}
named_scope :on, lambda {
|tag| {
:joins => :tags,
:conditions => ["tags.name = ?", tag]
}
}
named_scope :is, lambda {
|type| {
:joins => :type,
:conditions => ["types.name = ?", type]
}
}
this gives me URLs like /conferences/in/salt_lake_city/with/joe_shmoe or /lectures/about/programming/with/joe_splo/, in no particular order. win!
Related
this function works. However, instead of including all the rounds. I just need to include the last one. How would I do it? I looked into using :scope but haven't found anything useful. Please help, it's driving me nuts!
def get_games_list
Rails.logger.info("get_games_list".colorize(:color => :white, :background => :blue))
player_1_games = Game.includes(:rounds, :player_2).where(:player_1_id => params[:player_id], :is_valid => true)
player_2_games = Game.includes(:rounds, :player_1).where(:player_2_id => params[:player_id], :is_valid => true)
render json: {
error_code: ERROR_NOERROR,
status: "ok",
player_1_games: player_1_games.as_json(
:include => {
:player_2 => {
:only => [:id, :user_name]
},
:rounds => {
:only => [:id, :playing_player_id]
}
}
),
player_2_games: player_2_games.as_json(
:include => {
:player_1 => {
:only => [:id, :user_name]
},
:rounds => {
:only => [:id, :playing_player_id]
}
}
)
}
return
end
You could create a :last_round has_one association in the Game model.
class Game < ApplicationModel
# ...
has_one :last_round, -> { order(round_date: :desc) }, class_name: 'Round'
# ...
end
On rails 4 with the acts as taggable gem. My search is currently not returning exact matches first. It seems to be like the tags aren't being weighted properly. When I get rid of the :associated_against => { :tags => {:name => 'D'}} exact matches are returned first. Has anyone ran into this issue before? Any suggestions?
Here is my search scope:
pg_search_scope :search, :against => { :specific => 'A', :title => 'B', :aka => 'B'},
:associated_against => { :tags => {:name => 'D'}},
:using => { dmetaphone: {}, tsearch: { dictionary: 'english' },
trigram: {:threshold => 0.3} },
ignoring: :accents
Can you post the rest of your code in the controller, etc. I have the following in my app:
# tools.rb
include PgSearch
pg_search_scope :search_including_tags,
:against => [:description, :barcode],
:associated_against => {:tags => [:name] }
Then in my controller to search through I have:
#tools_controller.rb
def index
if params[:search]
#tools = Tool.where("(barcode) LIKE (?)", "%#{params[:search]}")
elsif params[:tag]
#tools = Tool.tagged_with(params[:tag])
elsif params[:id]
#tool = Tool.find(params[:id])
else
#tools = Tool.all
#tool = Tool.first
end
end
and finally for my search controller
def new
#tools = Tool.search_including_tags(params[:query])
end
Hope this helps. Can't really say much without seeing all of the code. But I ended up using this which worked: :associated_against => {:tags => [:name] }
I am fairly still new to ruby on rails and don't fully understand why I am getting the following error:
undefined local variable or method `user' for #<StatisticsController:0xb9a20d0>
The code:
class StatisticsController < ApplicationController
before_filter :authenticate, :only => [:index]
def index
#title = "Statistics"
#projects = Project.all
#data = []
Project.all.each do |project|
projdata = { 'name' => project.project_name.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values' ].push( record )
end
#data.push( projdata )
end
end
end
Update
class StatisticsController < ApplicationController
before_filter :authenticate, :only => [:index]
def index
#title = "Statistics"
#projects = Project.all
#data = []
User.all.each do |user|
projdata = { 'name' => user.user_id.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
user = User.all
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values'].push( record )
end
#data.push( projdata )
end
end
end
In string :conditions => { "project_tasks.efforts.user_id" => user.id, you call id for user object, but it is not instantiated in code above.
Your update doesn't loop over the users at all; user is now a collection of all the users. You need to iterate over the users if you want to get individual statistics for individual users.
Are you using devise? Use current_user instead of user.
Fix of your code:
User.all.each do |user|
projdata = { 'name' => user.user_id.to_s,
'values' => [] }
['Pre-Sales','Project','Fault Fixing','Support' ].each do |taskname|
record = Effort.sum( :hours,
:joins => {:project_task => {:efforts => :user}},
:conditions => { "project_tasks.efforts.user_id" => user.id,
"project_tasks.project_id" => project.id,
"project_tasks.task_name" => taskname } )
projdata[ 'values'].push( record )
end
#data.push( projdata )
end
So: removed the rogue user=User.all :)
Question: in 1 place you write user.user_id and in the other you write user.id. Is that correct?
I know how to access foreign key attributes in a scaffold index view. I can simply refer to the attributes using dot notation such as property.que.name. Given the following models:
class Priority < ActiveRecord::Base
belongs_to :que
...
end
class Que < ActiveRecord::Base
has_many :priorities
...
end
In the index view, I can do something like this to get the name value:
<td><%=h priority.que ? priority.que.name : "" %></td>
How do I do this in the jqgrid?
I tried this but the jqgrid comes back empty:
Priorities Controller:
#priorities = Priority.find(:all, :order => "position", :conditions => "multitenant_team_id = " + current_user.team.id.to_s ) do
if params[:_search] == "true"
id =~ "%#{params[:id]}%" if params[:id].present?
issue_id =~ "%#{params[:issue_id]}%" if params[:issue_id].present?
que =~ "%#{params[:que]}%" if params[:que].present?
customer =~ "%#{params[:customer]}%" if params[:customer].present?
title =~ "%#{params[:title]}%" if params[:title].present?
reporting_source =~ "%#{params[:reporting_source]}%" if params[:reporting_source].present?
priority =~ "%#{params[:priority]}%" if params[:priority].present?
product =~ "%#{params[:product]}%" if params[:product].present?
current_owner =~ "%#{params[:current_owner]}%" if params[:current_owner].present?
end
paginate :page => params[:page], :per_page => params[:rows]
order_by "#{params[:sidx]} #{params[:sord]}"
end
if request.xhr?
end
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #priorities.to_jqgrid_json(
[:id, :issue_id, :que.name, :customer, :title, :reporting_source,
:priority, :product, :current_owner],
params[:page], params[:rows], #priorities.total_entries)}
format.xml { render :xml => #priorities }
end
end
Index View:
<%= jqgrid("Priorities", "priorities", "/priorities",
[
{:field => "id", :label => "ID", :width => 35, :resizable => false},
{:field => "issue_id", :label => "Issue Id"},
{:field => "que", :label => "Queue"},
{:field => "customer", :label => "Customer"},
{:field => "title", :label => "Title"},
{:field => "reporting_source", :label => "Reporting Source"},
{:field => "priority", :label => "Priority"},
{:field => "product", :label => "Product"},
{:field => "current_owner", :label => "Current Owner"}
],
{ :rows_per_page => 12, :height => 450 }
)%>
If I specify que instead of que.name, I get the data back in the grid but the Queue field shows a "#" symbol so I suspect the .to_jqgrid_json call doesn't like my syntax.
Has anyone tried this before? I hope so.
I fixed my problem. I ended up changing my find to a find_by_sql so I could do a left outer join on the ques table. I think there were a couple of issues. I think the *to_jqgrid_json* had problems with null foreign key values and I couldn't figure out how to get at the Que.name any other way. I'm using SQLServer so I had to use isnull(ques.name, '') to convert the null to empty space.
So I replaced my find as follows:
#priorities = Priority.find_by_sql ["select priorities.id id, issue_id, isnull(ques.name,' ') queue_name, customer, title, reporting_source, priority, product, current_owner from priorities left outer join ques on priorities.que_id = ques.id where priorities.multitenant_team_id = ? order by issue_id", current_user.team.id.to_s]
This introduced another problem in that find_by_sql returns an array which breaks the #priorities.total_entries call. So I had to replace it with array.count.
format.json { render :json => #priorities.to_jqgrid_json(
[:id, :issue_id, :queue_name, :customer, :title, :reporting_source, :priority, :product, :current_owner],
params[:page], params[:rows], #priorities.count)}
My grid looks great!
Edit
My grid LOOKS great but it doesn't paginate or sort. Back to the drawing board. :(
Okay, I think I fixed it for real this time.
#priorities = Priority.find(:all,
:select => "priorities.id, priorities.issue_id,
priorities.customer, priorities.title,
priorities.reporting_source, priorities.priority,
priorities.product, priorities.current_owner,
priorities.position,
isnull(ques.name,' ') queue_name",
:joins => "LEFT OUTER JOIN ques ON ques.id = priorities.que_id",
:order => "priorities.position",
:conditions => "priorities.multitenant_team_id = " + current_user.team.id.to_s ) do
I had know idea I could specify joins like this. This keeps the resultset in a format the 2dc_jqgrid plugin likes. Sorting, pagination and searching all work now. Now my grid looks good and actually works.
I have a bunch of named scopes and have a method within one of them that I would like to share between the other named scopes. I've sort of accomplished this by using define_method and a lambda. However, there is still some repeated code and I'm wondering is there a better approach?
Here's a simplified example of what I've got. Assume I have a table of projects and each project has many users.
Within the User model I have...
filter_by_name = lambda { |name| detect {|user| user.name == name} }
named_scope :active, :conditions => {:active => true} do
define_method :filter_by_name, filter_by_name
end
named_scope :inactive, :conditions => {:active => false} do
define_method :filter_by_name, filter_by_name
end
named_scope :have_logged_in, :conditions => {:logged_in => true} do
define_method :filter_by_name, filter_by_name
end
Then I would use it like...
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.logged_in
more_users = logged_in_users.filter_by_name "John"
Here's an entirely different solution that is probably more in spirit with what the question was asking for.
named_scope takes a block, which could be any Proc. So if you create a lambda/Proc which defines the filter_by_name method, you can pass it as the last argument to a named_scope.
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
named_scope(:active, :conditions => {:active => true}, &add_filter_by_name)
named_scope(:inactive, :conditions => {:active => false}, &add_filter_by_name)
named_scope(:have_logged_in, :conditions => {:logged_in => true}, &add_filter_by_name)
This will do what you're looking for. If you still think it's too repetitive, you can combine it with the techniques in mrjake2's solution to define many named scopes at once. Something like this:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
filter_by_name = lambda { |name| detect {|user| user.name == name} }
add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }
method_params.keys.each do |method_name|
send(:named_scope method_name, :conditions => method_params[method_name],
&add_filter_by_name)
end
Named scopes can be chained, so you're making this harder on your self than you need to.
The following when defined in the user model will get you what you want:
class User < ActiveRecord::Base
...
named_scope :filter_by_name, lambda { |name|
{:conditions => { :name => name} }
}
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
end
Then the following snippets will work:
active_users = Project.find(1).users.active
some_users = active_users.filter_by_name( ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"
logged_in_users = Project.find(1).users.have_logged_in
more_users = logged_in_users.filter_by_name "John"
I see that you're using detect, probably to avoid excess hits to the DB. But your examples don't use it properly. Detect only returns the first element in a list that the block returns true for. In the above example, some_users will only be a single record, the first user that is named either "Pete" or "Alan". If you're looking to get all users named "Pete" or "Alan" then you want select. And if you want select then you're better off using a named scope.
Named scopes when evaluated return a special object that contains the components necessary to build the SQL statement to generate the results, chaining other named scopes still doesn't execute the statement. Not until you try to access methods on the result set, such as calling each or map.
I would probably use a bit of metaprogramming:
method_params = {
:active => { :active => true },
:inactive => { :active => false },
:have_logged_in => { :logged_in => true }
}
method_params.keys.each do |method_name|
send :named_scope method_name, :conditions => method_params[method_name] do
define_method :filter_by_name, filter_by_name
end
end
This way if you wanted to add more finders in the future, you could just add the method name and conditions to the methods_param hash.
You can also do this with a second named scope.
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true}
named_scope :filter_by_name, lambda {|name| :conditions => ["first_name = ? OR last_name = ?", name, name]}
Then you can do #project.users.active.filter_by_name('Francis').
If you really need to do this with Enumerable#detect, I would define the filter_by_name method in a module which can then extend the named scopes:
with_options(:extend => FilterUsersByName) do |fubn|
fubn.named_scope :active, :conditions => {:active => true}
fubn.named_scope :inactive, :conditions => {:active => false}
fubn.named_scope :have_logged_in, :conditions => {:logged_in => true}
end
module FilterUsersByName
def filter_by_name(name)
detect {|user| user.name == name}
end
end
This adds the filter_by_name method to the class returned by all three named scopes.