calling a method in a model from another controller in rails 3 - ruby-on-rails

In my rails application I need to call a method defined in one table from another controller. There are two tables named coordinates and tweets. The condition for the tweets table is decided by the coordinates table. In my final view I need to display the attributes of the tweets table for which the condition is decided by the coordinates table.
My Coordinates table code
class Coordinates<ActiveRecord::Base
def self.query()
a = Coordinates.where("city=?", params[:show])
b = a.first
if a.count == 1
latitude = b.latitude
longitude= b.longitude
if(latitude=0 && longitude=0) then
return sql="Select * from tweets where tweet_text LIKE '%text%' AND user_loc LIKE '%show%' order by id desc"
else if (latitude!=0 && longitude!=0)
min_lat = latitude - 1.0
max_lat = latitude + 1.0
min_lng = longitude - 1.0
max_lng = longitude + 1.0
return sql = "Select * from tweets where tweet_text LIKE '%text%' AND ( ((longitude BETWEEN min_lng and max_lng) AND (latitude BETWEEN min_lat and max_lat)) OR (user_loc LIKE '%show%') ) order by id desc"
else
return sql="Select * from tweets where tweet_text LIKE '%text%'"
end
end
end
My tweets table
class Tweets<ActiveRecord::Base
attr_accessible :id, :tweet_created_at, :tweet_id, :tweet_text, :tweet_source, :user_id, :user_name, :user_sc_name, :user_loc, :user_img, :longitude, :latitude, :place, :country
end
I need to call the query definition from my tweets_controller so that it decides what query to fetch from the tweets table and display in the final view . But the params function is not working in the model.rb file . I want something like this
My tweets_controller.rb
class TweetsController<ApplicationController
def index
Coordinates.query()
end
end
My final view code
<%= #tweets.each do |tweets| %>
<ul>
<li><%= tweets.id %></li>
<li><%= tweets.tweet_created_at %></li>
<li><%= tweets.tweet_source %></li>
<li><%= tweets.tweet_text %></li>
<li><%= tweets.user_id %></li>
<li><%= tweets.user_name %></li>
<li><%= tweets.user_sc_name %></li>
<li><%= tweets.user_loc %></li>
<li><%= tweets.user_img %></li>
<li><%= tweets.longitude %></li>
<li><%= tweets.latitude %></li>
<li><%= tweets.place %></li>
<li><%= tweets.country %></li>
</ul>
<% end %>
I am not able to call the query function defined in coordinates table from tweetscontroller. Also I tried using helper methods but that does not seem to help and sounds very complicated. Anybody kindly help me with this

Actually, I see a few issues with your code.
First off: use meaningful names. You are retrieving tweets pertaining to a certain city. Secondly, in your query method, most of what you are trying to do is retrieving tweets, which should be in the tweet model.
What your code does wrong:
it just builds the sql?
it searches for tweets containing the fixed text %text%, I am assuming that should be a given search-term
it searches for a given user-location %show%, I am assuming that should be the city-name (your params[:show]
What I would suggest:
finding tweets should be in Tweet model
use more smaller methods
for simplicity I assume your search-term is params[:text]
So I would write your code as follows:
class TweetsController < ApplicationController
def index
city = params[:show]
search_term = params[:text]
city_coordinates = Coordinates.where('city=?', city)
#tweets = if city_coordinates.count == 1 && city_coordinates.first.valid_location?
Tweet.for_coordinate(city_coordinates.first)
else
Tweet.for_user_location(city)
end
#tweets = #tweets.where("tweet_text like ?", "%#{search_term}%")
end
end
Do not build the sql yourself, let activerecord do that for you. Also: where is lazy, so you can easily chain them.
Then in your Coordinate model add
class Coordinate < ActiveRecord::Base
def valid_location?
self.latitude != 0 && self.longitude != 0
end
end
and in your Tweet model
class Tweet < ActiveRecord::Base
def self.for_coordinate(coordinate)
bbox = { min_lat: coordinate.latitude - 1.0, max_lat: coordinate.latitude + 1.0,
min_lng: coordinate.latitude - 1.0, max_lng: coordinate.latitude + 1.0
}
Tweet.where("(longitude BETWEEN ? and ?) AND (latitude BETWEEN ? and ?)) OR (user_loc LIKE ?)",
bbox[:min_lng], bbox[:max_lng], bbox[:min_lat], bbox[:max_lat], "%#{coordinate.city}%")
end
def self.for_user_location(city)
Tweet.where("user_loc like ?", "%#{city}%")
end
end

In your self.query method in model, set an argument like this :
class Coordinates<ActiveRecord::Base
def self.query(something)
a = Coordinates.where("city=?", something)
b = a.first
if a.count == 1
latitude = b.latitude
longitude= b.longitude
if(latitude=0 && longitude=0) then
return sql="Select * from tweets where tweet_text LIKE '%text%' AND user_loc LIKE '%show%' order by id desc"
else if (latitude!=0 && longitude!=0)
min_lat = latitude - 1.0
max_lat = latitude + 1.0
min_lng = longitude - 1.0
max_lng = longitude + 1.0
return sql = "Select * from tweets where tweet_text LIKE '%text%' AND ( ((longitude BETWEEN min_lng and max_lng) AND (latitude BETWEEN min_lat and max_lat)) OR (user_loc LIKE '%show%') ) order by id desc"
else
return sql="Select * from tweets where tweet_text LIKE '%text%'"
end
end
end
In your controller, pass your params[:show] as as parameter :
class TweetsController<ApplicationController
def index
#tweets = Coordinates.query(params[:show])
end
end
Now it should work. Thanks

You call Coordinates.query() but you don't actually store the return value.
Try this:
#tweets = Coordinates.query()

Related

best way to call a method in rails

I have some problems and I don't know wath's the best way to resolve it.
I have do a method, and I would like to help this in two views : poi.views and track.index
The method :
#distance_a_to_b = Track.find_by_sql(
["SELECT
ST_Distance(line::geography, pta::geography) +
ST_Distance(line::geography, ptb::geography) +
ST_Length(ST_LineSubstring(
line,
least(ST_Line_Locate_Point(line, pta), ST_Line_Locate_Point(line, ptb)),
greatest(ST_Line_Locate_Point(line, pta), ST_Line_Locate_Point(line, ptb)))::geography) AS dst_line
FROM (
SELECT
'SRID=4326;LINESTRING(1.457834243774414 43.597960902821576,1.462029218673706 43.59636807591895)'::geometry line,
'SRID=4326;POINT(1.457994 43.598124)'::geometry pta,
'SRID=4326;POINT(1.461628 43.596128)'::geometry ptb
) data"
])
I need to call this methdod in two views....
poi.show = distance bewtveen A to poi (point())
and
track.index = distance for each poi (point())
This method need 3 arguments :
a = start point ( query params) as a Point()
b = an end point as a Point()
and a linestring or merge linestring
How can I post this arguments to this method ?
As kind of :
#distance_a_to_b = Track.find_by_sql(
["SELECT
ST_Distance(line::geography, pta::geography) +
ST_Distance(line::geography, ptb::geography) +
ST_Length(ST_LineSubstring(
line,
least(ST_Line_Locate_Point(line, pta), ST_Line_Locate_Point(line, ptb)),
greatest(ST_Line_Locate_Point(line, pta), ST_Line_Locate_Point(line, ptb)))::geography) AS dst_line
FROM (
SELECT
'#track.path'::geometry line,
'#poi.lonlat'::geometry pta,
'query: params'::geometry ptb
) data"
])
How can I post the variables from each view ?
How can I get the result from this method, from each view ? by call method ?
Table pois :
t.geography "lonlat", limit: {:srid=>4326, :type=>"st_point", :geographic=>true}
Table tracks :
t.geometry "path", limit: {:srid=>4326, :type=>"line_string"}
Tracks has_many pois
Poi belongs_to track
Edit
Following the advice, here's what I did
In poi controller (Just to define the datas) :
def index
track = Track.friendly.find(params[:track_id])
#pois = Poi.where(track_id: track)
#track = Track.find_by id: 1
#poi = Poi.find_by id: 1
respond_to do |format|
format.html
end
In poi model :
def distance_along_track_to(poi2, track)
distance_sql = <<-SQL
SELECT
ST_Distance(tr.path::geography, pta.lonlat::geography) +
ST_Distance(tr.path::geography, ptb.lonlat::geography) +
ST_Length(ST_LineSubstring(
tr.path,
least(ST_Line_Locate_Point(tr.path, pta.lonlat::geometry), ST_Line_Locate_Point(tr.path, ptb.lonlat::geometry)),
greatest(ST_Line_Locate_Point(tr.path, pta.lonlat::geometry), ST_Line_Locate_Point(tr.path, ptb.lonlat::geometry)))::geography) AS dst_line
FROM tracks tr, pois pta, pois ptb
WHERE tr.id = #{track.id}
AND pta.id = #{self.id}
AND ptb.id = #{poi2.id}
SQL
Poi.find_by_sql(distance_sql).dst_line
end
In the index view :
<% #pois.each do |poi| %>
<div>
<%= poi.name %>
<%= poi.track_id %>
<%= #poi.distance_along_track_to(poi, #track) %> %>
</div>
<% end %>
And now I have this error message :
undefined method `dst_line' for [#<Poi id: nil>]:Array
I don't understand why #poi = nil ?
Suppose you have #poi1, #poi2 and #track, you can write your method as follows:
class Track
def distance_between_two_pois(poi1, poi2)
distance_sql = <<-SQL
SELECT
ST_Distance(tr.path, pta.lonlat) +
ST_Distance(tr.path, ptb.lonlat) +
ST_Length(ST_LineSubstring(
tr.path,
least(ST_Line_Locate_Point(tr.path, pta.lonlat), ST_Line_Locate_Point(tr.path, ptb.lonlat)),
greatest(ST_Line_Locate_Point(tr.path, pta.lonlat), ST_Line_Locate_Point(tr.path, ptb.lonlat)))::geography) AS dst_line
FROM tracks tr, pois pta, pois ptb
WHERE tr.id = #{self.id}
AND pta.id = #{poi1.id}
AND ptb.id = #{poi2.id}
SQL
Track.find_by_sql(distance_sql).first.dst_line
end
end
and then you could call it as follows
#track.distance_between_two_pois(#poi1, #poi2)
Not sure what the best api is? I can also imagine writing something like?
#poi1.distance_along_track_to(#poi2, #track)

How do I display database calls from Controller into view, to be viewed in HTML

I was handed a project from someone else, it's in Ruby On Rails, which I know VERY LITTLE. Basically, there is an EXPORT button, that the user clicks to send data to a CSV. I am tasked with sending this data to the view to be seen in HTML. (Thinking I could use dataTables). I have tried following examples, such as:
#example = StudentGroup.where(survey_id: #survey.id).order("groupNum")
and then using <%= #example %> in the view just to see the data and I get nothing. (Also extremely new to MySQL). I'll post the method, if ANYONE can help me, I'd very much appreciate it.
def download_results
if (user_signed_in?)
else
redirect_to new_user_session_path
end
#survey = Survey.find(params[:survey_to_view])
filename = #survey.name + " - " + Date.today.to_formatted_s(:short)
require "csv"
CSV.open(#survey.name+".csv", "wb") do |csv|
csv << [filename]
StudentGroup.where(survey_id: #survey.id).order("groupNum")
csv << []
csv << ["Summarized Results"]
csv << ["UCA","Group Number","Criteria 1","Criteria 2","Criteria 3","Criteria 4","Criteria 5","Criteria 6","Criteria 7","Criteria 8","Overall Team Contribution","Average(Would Work With Again)","Average(C1..C8)","Overall Team Contribution MINUS Average(C1..C9)"]
questions = #survey.questions
numQuestions = 0
questions.each do |q|
if(q.question_type != 2 && q.question_type != 4)
numQuestions = numQuestions+1
end
end
groups.each do |g|
answersCount = Answer.where(student_group_id: g.id).count
if(answersCount == numQuestions && answersCount != 0)
othersInGroup = StudentGroup.where(groupNum: g.groupNum, survey_id: #survey.id).order("groupNum")
size = othersInGroup.count-1
arr = []
criteria = SurveyQuestionDatum.where("number > 24 AND number < 35")
multiAvg = 0
teamCont = 0
criteria.each do |c|
avg = 0
othersInGroup.each do |o|
a = Answer.where(survey_question_datum_id: c.id, student_group_id: o.id).first
if(o.uca != g.uca)
if(a.nil?)
size = size-1
else
avg = avg + a.answer[g.uca].to_i
end
end
end
avg = avg.to_f/size
if(c.number == 33)
teamCont = avg
end
if(c.number < 33)
multiAvg = multiAvg+avg
end
arr << avg
end
multiAvg = multiAvg.to_f/8
arr << multiAvg
arr << teamCont-multiAvg
arr.insert(0,g.uca, g.groupNum)
csv << arr
end
end
csv << []
csv << []
csv << ["Raw Student Answers"]
groups = StudentGroup.where(survey_id: #survey.id).order("groupNum")
size = groups.count
csv << ["UCA", "F-Number", "Group Number"]
groups.each do |g|
answersCount = Answer.where(student_group_id: g.id).count
if(answersCount == numQuestions && answersCount != 0)
othersInGroup = StudentGroup.where(groupNum: g.groupNum, survey_id: #survey.id).order("groupNum")
csv << []
csv << [g.uca, g.FNum, g.groupNum]
answers = Answer.where(student_group_id: g.id)
csv << ["Question Number", "Question", "Answer"]
answers.each do |a|
datum = a.survey_question_datum
question = datum.question
#question_types = {"0" => "short", "1" => "paragraph",
#2" => "title", "3" => "fivept", "4" => "fixed",
#5" =>"ranking", "6"=>"tenpoints","7"=>"hundredpoints"}
ansText = ""
if(question.question_type == 0)
ansText = a.answer
elsif (question.question_type == 1)
if(question.rule == 'perMember')
othersInGroup.each do |o|
ansText = ansText+"#{o.uca},#{a.answer[o.uca]},"
end
elsif(question.rule == 'default')
ansText = a.answer
end
else (question.question_type == 3)
othersInGroup.each do |o|
ansText = ansText+"#{o.uca},#{a.answer[o.uca]},"
end
end
ansText = ansText.chomp(',')
ansText = ansText.split(',')
ansText.insert(0,datum.number,question.question_text)
csv << ansText
end
end
end
end
send_file(#survey.name+".csv", :filename => filename+".csv")
end
You need a new controller action. Take a look at http://guides.rubyonrails.org/layouts_and_rendering.html
Create an index (or show, or whatever you want to call it, maybe example) action. Make sure it is in your routes.
http://guides.rubyonrails.org/getting_started.html#adding-a-route-for-comments
do not use the download_results code.
set your #example variable the way you were trying to do.
create a view for your index action
add the data to your index view.
If you put code in your download_results method (action) it will never get rendered because of the send_file method call.
Did you create a brand new controller / action / view? Did you use generators? Have you really practiced doing this setup exactly the way the examples, videos, tutorials say to do it? If you have, you have seen how all the pieces (models, controllers, actions, views) come together. You should have seen how render statements come into play. Do that, exactly as the tutorials say to do it and you will get the idea.
If you want to use the same content that the download action uses, refactor the code to extract a method that is used both actions.
This is related to respond_to part, check the docs.
send_file(#survey.name+".csv", :filename => filename+".csv")
Your code above simply means you click the button, the controller will respond you with a csv file. So, if you want a html, the controller should be able to respond to html as well.

Display name in rails should contain first name and initial of last name

I have a students collection result set and I need following.
Display names should be resolved following these rules: If there are no other students in the collection with the same first name, their display
name should be just their first name.
If there are multiple students in the collection with the same first name, their display name should be their first name followed by a space and their last initial(e.g. “John Smith” would resolve to “John S”)..
Try this
#results.each do |object|
displayname = (#results.select { |filter_object| filter_object.first_name == object.first_name }.count > 0) ? object.first_name : object.first_name + " " + object.last_name.initial
end
here's an example, this might not specifically be what you need (this kinda sounds like homework), but hopefully it gives you an idea.
# in Student model
attr_accessor :display_name
# in controller
students = Student.all
students.each do |student|
if students.count { |s| s.first_name == student.first_name } > 1
student.display_name = s.first_name
else
student.display_name = "#{student.first_name} #{student.last_name[0].upcase}"
end
end
# in view
<% students.each do |student| %>
<%= student.display_name %>
<% end %>
First find out duplicate first names.
dup_first_names = Student.select(:first_name).group(:first_name).group(:first_name).having("COUNT(*) > 1").uniq.pluck(:first_name)
Then for each student check whether the first name is in the dup_first_names array.
Student.all.each do |s|
if dup_first_names.include?(s.first_name)
puts "#{s.first_name} #{s.last_name.first}"
else
puts "#{s.first_name}"
end
end

how to check object is exits or not in view ROR

I have implemented query on distant lat long and create virtual field in query like this
user = User.all
if longitude.present? && latitude.present? && fulladdress.present?
distantQuery = "(3959 * acos( cos( radians("+latitude+") ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians("+longitude+")) + sin( radians("+latitude +")) * sin(radians(latitude)))) < " + miles
user = User.select("*, (3959 * acos(cos(radians("+latitude+")) * cos( radians(latitude)) * cos(radians(longitude) - radians("+longitude+")) + sin(radians(" +latitude +")) * sin(radians(latitude)))) as distant").where(distantQuery)
end
when my if condition run then distant is coming other wise it will not coming
<% #result.each do |r| %>
<tr>
<td>
<%= r.distant.present %>
</td>
</tr>
now in view my distant is come form query then it will show result and when it will not come then it show me error
undefined method `distant' for #<User:0x000000092a6558>
I have implement "present" in view
<% if r.distant.present? %>
<%= r.distant.to_i %>
<% end %>
but still it show me error, What will i use to check if distant is coming then it will show and other wise not ?
this should work:
<% if r.respond_to?(:distant) %>
however this is too much logic in the view, i suggest you refactor some of your code to have very simple and safe methods in your view
Try this
<%= r.try(:distant) %>
I would use another approach:
user = if longitude.present? && latitude.present? && fulladdress.present?
distantQuery = QUERY_BODY_HERE
User.select(SELECT_QUERY_HERE).where(distantQuery)
else
User.all
end
unless user.respond_to(:distant)
class << user
def distant ; nil ; end
end
end
The advantage of this approach is that you have consistent User class, responding to #distant correctly regardless of whether it was calculated.

reusing a controller method from another controller method (rails)

I have a fairly complex method in my controller that basically outputs data to be used in a view to create a donut graph.
def courses_allocated
course_id = params[:course_id];
client_id = params[:client_id];
override_client_id = get_client_id_for_current_user
unless override_client_id.nil?
client_id = override_client_id
end
category_course_enrollments = CourseEnrollment.select("course_categories.title, COUNT(*) as count").
joins("INNER JOIN courses ON course_enrollments.course_id = courses.id").
joins("INNER JOIN course_categories ON courses.course_category_id = course_categories.id").
group("course_categories.id").
order("course_categories.title")
course_enrollments = CourseEnrollment.select("COUNT(*) as count, course_enrollments.course_id, courses.title").
joins("INNER JOIN courses ON course_enrollments.course_id = courses.id").
joins("INNER JOIN course_categories ON courses.course_category_id = course_categories.id").
group("course_enrollments.course_id").
order("course_categories.title")
unless course_id.blank?
category_course_enrollments = category_course_enrollments.where("course_enrollments.course_id = ?" , course_id.to_i)
course_enrollments = course_enrollments.where("course_enrollments.course_id = ?" , course_id.to_i)
end
unless client_id.blank?
category_course_enrollments = category_course_enrollments.where("courses.client_id = ?", client_id)
course_enrollments = course_enrollments.where("courses.client_id = ?", client_id)
end
#category_data = []
#course_assigned_data = []
#course_assigned_detail_data = []
category_course_enrollments.each do |category_course_enrollment|
#category_data.push([category_course_enrollment.title, category_course_enrollment.count]);
end
course_enrollments.each do |course_enrollment|
not_started = CourseEnrollment.select("COUNT(patient_id) AS total_not_started").
where('started IS NULL').
where('course_id = ?', course_enrollment.course_id).first.total_not_started
in_progress = CourseEnrollment.select("COUNT(patient_id) AS total_in_progress").
where('started IS NOT NULL').
where('completed IS NULL').
where('course_id = ?', course_enrollment.course_id).first.total_in_progress
completed = CourseEnrollment.select("COUNT(patient_id) AS total_completed").
where('completed IS NOT NULL').
where('course_id = ?', course_enrollment.course_id).first.total_completed
#course_assigned_data.push([course_enrollment.title, course_enrollment.count]);
#course_assigned_detail_data.push({'name'=>course_enrollment.title + " Not Started", 'y'=> not_started, 'color'=>'#ff8800'});
#course_assigned_detail_data.push({'name'=>course_enrollment.title + " In Progress", 'y'=> in_progress, 'color'=>'#0088ff'});
#course_assigned_detail_data.push({'name'=>course_enrollment.title + " Completed", 'y'=> completed ,'color'=>'#44cc44'});
end
end
The View for the donut graph (besides the input for a form is:)
<div id="reportcoursesallocatedgraph">
</div>
<script type="text/javascript">
new IS.ReportCoursesAllocated('Course Allocated', <%= raw(ActiveSupport::JSON.encode(#category_data)); %>, <%= raw(ActiveSupport::JSON.encode(#course_assigned_data)); %>, <%= raw(ActiveSupport::JSON.encode(#course_assigned_detail_data)); %>, 'reportcoursesallocatedgraph');
</script>
I want to reuse the logic from courses_allocated from a method in the same class; def dashboard. (The dashboard method basically creates a bunch of different graphs)
Should I make a private method that they can both share?
If the logic is identical, then you can just alias dashboard to courses_allocated. To do that, you can put this right below the courses_allocated action method.
alias dashboard courses_allocated

Resources