So, I have an index page with grid of some students. With a button I would call an email method to send an email at all student in #students = Student.all
How to call the method ?
The params for the method is #students, is it right ?
<%= link_to 'Email', send_to_student_path(#students) %>
As I have seen here
StudentController.rb
def send_to_student()
#binding.pry
#students.each do |student|
StudentMailer.email_recall(student).deliver
end
end
And the Mailer :
class StudentMailer < ActionMailer::Base
def email_recall(student)
#url = 'http://example.com/login'
mail(to: #student.email,
from: current_user.email,
subject: 'Valid your datas')
end
end
In routes.rb I have :
resources :students do
member do
post :send_to_student
end
end
First question :
How can I pass id of each student from #students = Student.all in the method param ?
Second question :
How to call the send_to_student method in the right way ?
Many thanks in advance
Nicolas
1) The way you're using it, you'd pass the ID through the route, like: students/:id/send - you can read more about member routing on the Rails documentation. This will set a parameter of params[:id], which you can use in your action
Alternatively, if you're looking to send a message to all students, you can just use the controller function I created below:
2) I think you can typically use a Model Method for this. This is where you put a function in the model, cleaning up your controller
I don't know if it will work with the mailer, but I'd try this:
#app/controllers/students_controller.rb
def index
#students = Student.all
Student.send_to_students(#students)
end
#app/models/student.rb
def self.send_to_student(students)
students ||= self.all # -> not sure about the self.all call
students.each do |student|
StudentMailer.email_recall(student).deliver
end
end
Related
I'm creating a simple Rails app that fetches data from the Open Weather Map API and returns the current weather data of the city that is searched for in a form field. I would like an API call to look like this for example:
http://api.openweathermap.org/data/2.5/weather?q=berlin&APPID=111111
I've tested this in Postman with my API key it works fine but with my code it returns "cod":"400","message":"Nothing to geocode"
Can anyone see where I am going wrong? Here is my code.
services/open_weather_api.rb
class OpenWeatherApi
include HTTParty
base_uri "http://api.openweathermap.org"
def initialize(city = "Berlin,DE", appid = "111111")
#options = { query: { q: city, APPID: appid } }
end
def my_location_forecast
self.class.get("/data/2.5/weather", #options)
end
end
forecasts_controller.rb
class ForecastsController < ApplicationController
def current_weather
#forecast = OpenWeatherApi.new(#options).my_location_forecast
end
end
current_weather.html.erb
<%= form_tag(current_weather_forecasts_path, method: :get) do %>
<%= text_field_tag(:q) %>
<%= submit_tag("Search") %>
<% end %><br>
<%= #forecast %>
routes.rb
Rails.application.routes.draw do
root 'forecasts#current_weather'
resources :forecasts do
collection do
get :current_weather
end
end
end
The error describes itself:
"cod":"400","message":"Nothing to geocode"
it means that you didn't provide it the city in your query. One possible cause of this error is that you are overriding the default value in your initialize method with the #options variable from the controller in this line:
class ForecastsController < ApplicationController
def current_weather
#forecast = OpenWeatherApi.new(#options).my_location_forecast
end
end
From the information you provided, you've not defined the #options variable in your controller or it is nil. So this is overriding the default value of the initialize method in OpenWeatherApi .
Since the appid in your case will not change, only the city name will change so you can send it from controller.
def current_weather
#city = params[:city] // the city you want to send to API. Change it with your value
#forecast = OpenWeatherApi.new(#city).my_location_forecast
end
I have a RoR app. And in app users can create posts. I've connected Posts table in my routes.rb via resources :posts. And right now - link to created post look like: http://mysitename.com/posts/1 (where 1 is post number).
What i want to do, is to make rails generate link to post. So users didn't see how much posts I have in my DB. And as result it must look like http://mysitename.com/post/generatedlink. It must generate, for example post theme.
For start, we must create link column in Posts table. And make it to generate something like that:
#post.link = #post.theme.parameterize.underscore
But I don't understand, where to put this code.
And the next problem is: "How to replace post/1 for #post.link?"
Hope, I make my self clear. If you'll say I can provide information, what is needed to resolve my question.
UPDATE
What I did after #SteveTurczyn advise.
I've created new column, called random_link as a string.
I didn't touch my routes.rb:
resources :posts
My post.rb (post model) look like this:
after_validation :add_link
def add_link
self.random_link = self.theme.to_slug_param
# to_slug_param it's a gem for translating from other language into english
end
def to_param
random_link
end
I don't have find method. My posts_controller.rb look like this:
def show
#post = Post.find_by_random_link(params[:id])
right_menu_posts
random_link_to_other_post(#post)
end
private
def random_link_to_other_post(post)
random_post = Post.where.not(id: post.id)
#random_post = random_post.sort_by {rand}.first
end
def right_menu_posts
#posts_for_video_in_right_menu = Post.where(video: true)
end
And html.erb:
<%= #post.theme %>
<%= #post.content %>
<% for post in #random_post %>
<%= link_to post %>
<% end %>
<% for post in #posts_for_video_in_right_menu %>
<%= link_to post %>
<% end %>
And on a main page (where i have a list of posts) a keep getting an error: NoMethodError in Home#index private method 'to_param' called for #<Post:0x007fae3096bf78>.
The technique is referred to as slugifying and you need to do three things...
(1) create a new field called slug in your posts table.
(2) add this code to your Post model...
after_validation :generate_slug
private
def generate_slug
self.slug = theme.parameterize.underscore
end
public
def to_param
slug
end
(3) finally, in your controllers where you have find_post methods, rewrite it to be...
def find_post
Post.find_by_slug(params[:id])
end
The to_param method in the model is how things like post_path(#post) build the url... the to_param if not replaced substituted the id field but by writing your own to_param method you can ensure that the slug field is substituted instead.
Ensure that 'to_param' is a public method! Don't put it in the private part of your model. You can do that by putting public immediately before the to_param method. You should then put private after the method definition if subsequent methods are to be private.
I have a piece of code in Rails,
def create
#registration = Registration.new(registration_params)
if #registration.save
redirect_to #registration.paypal_url(registration_path(#registration))
else
render :new
end
end
I took it from tutorial. But I need just in this line:
#registration.paypal_url(registration_path(#registration))
Now, about my own controller, feed_controller, where
def create
#feed = Feed.new(check_params)
end
In the view erb file I put:
#feed.paypal_url(feed_path(#feed))
In my feed.rb (model):
def paypal_url(return_path)
values = {
business: "merchant#gotealeaf.com",
cmd: "_xclick",
upload: 1,
return: "#{Rails.application.secrets.app_host}#{return_path}",
invoice: id,
amount: course.price,
item_name: course.name,
item_number: course.id,
quantity: '1'
}
"#{Rails.application.secrets.paypal_host}/cgi-bin/webscr?" + values.to_query
end
Rake routes:
feed GET /:locale/feed(.:format) feed#index
feed#create POST /:locale/feed/create(.:format)
feed#new feed_new GET /:locale/feed/new(.:format)
feed#destroy feed_destroy GET /:locale/feed/destroy(.:format)
feed#edit feed_edit GET /:locale/feed/edit(.:format)
feed#update feed_update GET /:locale/feed/update(.:format)
But it prints the next error:
undefined method `paypal_url' for <#Feed::ActiveRecord_Relation:0x007fee24f5fc98>
How can I fix it? What is the problem?
UPDATE
def index
#current_user_is = current_user.email
session[:email] = #current_user_is
session[:id] = current_user.id
unless (current_user.member.present?)
#member = Member.new(:user_id => current_user.id)
#member.save()
redirect_to '/feed'
else
#new_feed = Feed.new
#feed = Feed.where(:member_id => current_user.member.id)
#category = Category.all
render 'home/uploads'
end
end
Simply use def self.paypal_url(return_path) instead of def paypal_url(return_path).
Explanation
You ran into your problem by defining a Class Method instead of an Instance Method, there's other posts discussing this.
The basic difference is, when defining:
def self.get_some_url
# code to return url of an instance
end
you can easily get the desired url of any objects, as in a view:
<% #feeds.each do |feed| %>
<%= feeds.get_some_url %>
<% end %>
Now calling Feed.get_some_url on the class would make no sense. Which url of the thousands would it call?
But there is a lot of use for class methods (where you define the method without self as you did)
def get_top_5
# code to return the top 5 most viewed feeds
end
Since this has nothing to do with a single instance, you define it for the entire Class. Leading to this call: Feed.get_top_5, which makes perfectly sense.
The second problem was not understanding the difference between where & find, this post will help you out with that.
My show action:
def show
# Multiple keywords
if current_user.admin?
#integration = Integration.find(params[:id])
else
#integration = current_user.integrations.find(params[:id])
end
#q = #integration.profiles.search(search_params)
#profiles = #q.result.where(found: true).select("profiles.*").group("profiles.id, profiles.email").includes(:integration_profiles).order("CAST( translate(meta_data -> '#{params[:sort_by]}', ',', '') AS INT) DESC NULLS LAST").page(params[:page]).per_page(20)
#profiles = #profiles.limit(params[:limit]) if params[:limit]
end
There can be many different filters taking place in here whether with Ransacker, with the params[:limit] or others. At the end I have a subset of profiles.
Now I want to tag all these profiles that are a result of the search query.
Profiles model:
def self.tagging_profiles
#Some code
end
I'd like to create an action within the same controller as the show that will execute the self.tagging_profiles function on the #profiles from the show action given those profiles have been filtered down.
def tagging
#profiles.tagging_profiles
end
I want the user to be able to make a search query, have profiles in the view then if satisfied tag all of them, so there would be a need of a form
UPDATE:
This is how I got around it, don't know how clean it is but here:
def show
# Multiple keywords
if current_user.admin?
#integration = Integration.find(params[:id])
else
#integration = current_user.integrations.find(params[:id])
end
#q = #integration.profiles.search(search_params)
#profiles = #q.result.where(found: true).select("profiles.*").group("profiles.id, profiles.email").includes(:integration_profiles).order("CAST( translate(meta_data -> '#{params[:sort_by]}', ',', '') AS INT) DESC NULLS LAST").page(params[:page]).per_page(20)
#profiles = #profiles.limit(params[:limit]) if params[:limit]
tag_profiles(params[:tag_names]) if params[:tag_names]
end
private
def tag_profiles(names)
#profiles.tagging_profiles
end
In my view, I created a form calling to self:
<%= form_tag(params.merge( :controller => "integrations", :action => "show" ), method: :get) do %>
<%= text_field_tag :tag_names %>
<%= submit_tag "Search", class: "btn btn-default"%>
<% end %>
Is this the best way to do it?
Rails public controller actions correspond always to a http request. But here there is just no need for 2 http requests. A simple solution would be just creating to private controllers methods filter_profiles(params) and tag_profiles(profiles) and just call them sequentially.
You can also extract this problem entirely to a ServiceObject, like this:
class ProfileTagger
attr_reader :search_params
def initialize(search_params)
#search_params = search_params
end
def perform
search
tag
end
def tag
#tag found profiles
end
def search
#profiles = #do the search
end
end
As processing 30,000 records is a time consuming operation, it would make sence to perform it outside of the rails request in background. This structure will allow you to delegate this operation to a sidekiq or delayed_job worker with ease
Instance Variables
If you want to "share" variable data between controller actions, you'll want to look at the role #instance variables play.
An instance of a class means that when you send a request, you'll have access to the #instance variable as long as you're within that instance of the class, I.E:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
before_action :create_your_var
def your_controller
puts #var
end
private
def create_your_var
#var = "Hello World"
end
end
This means if you wish to use the data within your controller, I would just set #instance variables, which you will then be able to access with as many different actions as you wish
--
Instance Methods
The difference will be through how you call those actions -
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def action
#-> your request resolves here
method #-> calls the relevant instance method
end
private
def method
#-> this can be called within the instance of the class
end
end
I am attempting to send an email to the present borrower of a book. I've created an ActionMailer called ReturnRequestMailer which has a method called please_return.
class ReturnRequestMailer < ActionMailer::Base
def please_return(book_loan)
subject 'Book Return Request'
recipients book_loan.person.email
from 'andrew.steele#west.cmu.edu'
sent_on Time.now
body :book_loan => book_loan
end
end
I am attempting to call this method from an action inside of my BooksController
def request_return
#book = Book.find(params[:id])
ReturnRequestMailer.please_return(#book.current_loan)
end
Which I invoke from my books index with the following link_to (ignoring for the time being that doing this in this manner probably isn't the smartest permanent solution).
<%= link_to 'Request Return', {:action => 'request_return' , :id => book} %>
Everything links up correctly but I get a NoMethodError in BooksController#request_return stating that it cannot find the method please_return for ReturnRequestMailer. What is going on that is preventing the please_return method from being visible to the BooksController?
add a 'deliver_' in front of your method so it will be :
def request_return
#book = Book.find(params[:id])
ReturnRequestMailer.deliver_please_return(#book.current_loan)
end
You don't need to define 'deliver_please_return' method, The method_missing method in ActionMailer will know to call please_return.
The Mailer in rails is usually used like this:
class ReturnRequestMailer < ActionMailer::Base
def please_return(book_loan)
subject 'Book Return Request'
recipients book_loan.person.email
from 'andrew.steele#west.cmu.edu'
sent_on Time.now
body :book_loan => book_loan
end
end
Then in the controller out deliver_ in front of the method name and call it as a class Method:
def request_return
#book = Book.find(params[:id])
NewsletterMailer.deliver_please_return(#book.current_loan)
end
Looking at your code it looks like the please_return method has been called as a class method, but you have defined it as an instance method. (for more detail on this see To use self. or not.. in Rails )
class ReturnRequestMailer < ActionMailer::Base
def self.please_return(book_loan)
...
should fix it.
Note this won't actual make it send the email, but will stop the NoMethodFound error.
As nasmorn states, you need to call ReturnRequestMailer.deliver_please_return to have the mail delivered.