How to send mail related to a category - ruby-on-rails

So, I have 3 models Quotes, Categories, and Subscribers.
Essentially, it's a newsletter application. Subscribers can select a category and then enter their email and they will receive quotes related to that category via email.
There's two issues here,
1. The category's are stored in a constant in the category model.
so how do I display the category type that the subscriber chose
in text format? I'd like to show in the email
something like "here's your email on <%= category.name %>" which would translate to
"here's your email on Food."
2. I'd like to ONLY send emails about the category that the subscriber subscribed to.
How might I accomplish this? Please provide examples.
This is what I have so far:
Category.rb
class Category < ActiveRecord::Base
belongs_to :quote
belongs_to :subscriber
CATEGORY_TYPE = {
1 => "Food",
2 => "Fitness",
3 => 'Cats',
}
end
Quote.rb
class Quote < ActiveRecord::Base
has_many :categories
belongs_to :category
validates :title, presence: true
end
Subscriber.rb
class Subscriber < ActiveRecord::Base
has_one :category
validates :email, presence: true
end
schema
create_table "categories", force: true do |t|
t.string "name"
t.integer "quote_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "subscriber_id"
t.integer "category_type"
end
create_table "quotes", force: true do |t|
t.string "title"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "subscribers", force: true do |t|
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
end
Update subscribers_mailer.rb
def SendMyEmail(email, category, quote, subscribers)
#category = category
#quote = quote
#subscribers = subscribers
#email = email
mail to: email, subject: 'New Quotes'
end
end
and then of course:
Category.all.each do |category|
SubscriptionMailer.SendMyEmail("email#test.com", category, category.quotes.first, category.subscribers).deliver
end
Update:
There's two remaining issues here,
1. Categories aren't syncing to subscribers. For example when I run category.subscribers.last
it's nil. and category.subscribers throws an empty array. How can I sync these? I think it has
to do with the fact that subscribers are selecting a category from the Category::CATEGORY_TYPES
constant as seen in the view code below.
2. I'd like to automate it so that these emails are sent to subscribers once a day.
How might I do this?
view code (subscribers/new.html.erb:
<div class="styled email-input2">
<%= form_for #subscriber do |f| %>
<% if #subscriber.errors.any? %>
<div class="errorExplanation" style="color: white;">
<h2><%= pluralize(#subscriber.errors.count, 'error') %> encountered:</h2>
<ul>
<% #subscriber.errors.full_messages.each do |m| %>
<li><%= m %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.fields_for :subscriber, #subscriber.build_category do |cat| %>
<%= cat.select(:category_type, Category::CATEGORY_TYPE.map{|p| [p[1], p[0]]}, {prompt: 'Please select'}, {class: 'styled email-input2'}) %>
<% end %>
</div>

Ok first lets modify the migrations:
create_table "categories", force: true do |t|
#DELETE QUOTE_ID AND SUBSCRIBER ID
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "category_type"
end
create_table "quotes", force: true do |t|
t.string "title"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.references :category #this will add integer category_id
end
create_table "subscribers", force: true do |t|
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
t.references :category #this will add integer category_id
end
Next we will modify the models to reflet the changes in the migration:
class Category < ActiveRecord::Base
has_many :quotes
has_many :subscribers
CATEGORY_TYPE = {
1 => "Food",
2 => "Fitness",
3 => 'Cats',
}
end
class Quote < ActiveRecord::Base
belongs_to :category
validates :title, presence: true
end
class Subscriber < ActiveRecord::Base
belongs_to :category
validates :email, presence: true
end
Now you can get the subscribers for a cateogyr using the following:
category = Category.find(1) #use whatever id you want
category.subscribers #list of all subscribers for a category
Getting the quotes for a category is similarly straight forward:
category = Category.find(1)
category.quotes
So assuming your mailer takes a category, quote and a list of subscribers for the email to send
Category.all.each do |category|
Mailer.SendMyEmail(category, category.quotes.first, category.subscribers).deliver
end
In the "SendMyEmail" function in your mailer you will have
def SendMyEmail(category, quote, subscribers)
#YOUR CODE FOR TYHE VIEW HERE
end

Related

Rails 7 How do I allow the user to create a new instance of a question from their given poll?

I have a poll and a poll has questions. These are the models:
class poll < ApplicationRecord
validates_presence_of :user, :title, :URL, :poll_type, :start_date, :end_date
belongs_to :user
has_many :questions, dependent: :destroy
end
class Question < ApplicationRecord
validates_presence_of :poll_id, :question_type, :title
belongs_to :poll
end
The schema for polls is as follows
create_table "polls", force: :cascade do |t|
t.bigint "user_id", null: false
t.string "title"
t.text "description"
t.text "URL"
t.string "poll_type"
t.datetime "start_date"
t.datetime "end_date"
t.boolean "weighted_voting"
t.boolean "show_results"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_polls_on_user_id"
end
The schema for quesitons
create_table "questions", force: :cascade do |t|
t.bigint "poll_id", null: false
t.string "question_type"
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["ballot_id"], name: "index_questions_on_ballot_id"
end
my routes.rb
resources :users do
resources :polls do
resources :questions do
resources :options
end
end
end
How can the user create a new instance of a question from their assigned given poll?
Currently the field poll_id in the questions form is empty. I can't figure out how to get this id populate. the polls form gets the user_id from devise's current_user.id variable via a form.
Also is doing this via the form the rails correct way of doing it?
Bonus Question: Would the method be the same for the options model?
You can do this by nested forms.
Details are here
Firstly you should create new pool form. Basically this form must include Poll and Question form fields. You must use fields_for helper to create related forms for specific model.
poll form
<%= form_for #poll do |poll| %>
Poll name: <%= poll.text_field :poll_name%>
<%= fields_for :question, #poll.questions do |question_fields| %>
Question 1 : <%= question_fields.text_field :question_1 %>
Answer 1 : <%= question_fields.check_box :answer_1 %>
Answer 2 : <%= question_fields.check_box :answer_2 %>
Answer 3 : <%= question_fields.check_box :answer_3 %>
<% end %>
<%= poll.submit %>
<% end %>
in model file
class Poll < ApplicationRecord
validates_presence_of :user, :title, :URL, :poll_type, :start_date, :end_date
belongs_to :user
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions
end
in poll controller permit the question attributes
questions_attributes: [
:id,
:question_text,
:answer_1,
:answer_2,
...
:poll_id]
poll controller new action
5.times { #poll.questions.build }
info about routes
Rails docs says: Resources should never be nested more than 1 level deep.
You can solve your problem with only
resources :polls
All you need to do inject current_user to poll controller when saving form. You don't need to another resources for questions. You can show all questions under the poll show page.

Retrieving a piece of data from a different Active Record Model/table

Any advice would be most appreciated.
Is it possible to retrieve the email address used by a user, posting a comment, in a list#show/show.html.erb view? Without adding another column "email" to my Comments model?
The best I can do is retrieve the user_id, which is not that helpful.
<% #list.comments.each do |comment| %>
<p><%= comment.body%></p>
<p><%= comment.user_id %>
<% end %>
Comment.rb
class Comment < ApplicationRecord
belongs_to :list, optional: true
belongs_to :user
Comments Table
create_table "comments", force: :cascade do |t|
t.text "body"
t.bigint "list_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "user_id"
t.index ["list_id"], name: "index_comments_on_list_id"
t.index ["user_id"], name: "index_comments_on_user_id"
end
List controller#show
def show
#list = List.find(params[:id])
#current_user = current_user.id
end
Use Module#delegate from ActiveSupport:
class Comment < ApplicationRecord
belongs_to :list, optional: true
belongs_to :user
delegate :email, to: :user
end
<% #list.comments.each do |comment| %>
<p><%= comment.body%></p>
<p><%= comment.user_id %>
<p><%= comment.email %>
<% end %>
And make sure you use .includes or .eager_load in the controller to avoid a N+1 query:
def show
#list = List.includes(comments: :user).find(params[:id])
#current_user = current_user.id
end

How can I display the name instead of an ID in rails

I am trying to have a drop down from the other model and display its name in the index page
I have a 3 tables "students", "classrooms", and "classroom_students"
what I am trying is when a student is created he should be able to add the classroom from a dropdown which is populated from classroom table, at the moment the drop down is working however it is getting the id from the dropdown
How to get the classroom name to display in index page
classroom model
class Classroom < ApplicationRecord
belongs_to :user
has_many :classroom_students
has_many :students, through: :classroom_students
end
student model
class Student < ApplicationRecord
has_many :classroom_students
has_many :classrooms, through: :classroom_students
validates :student_fname, presence: true, length: { minimum: 3, maximum: 50 }
end
classroom_student model
class ClassroomStudent < ApplicationRecord
belongs_to :classroom
belongs_to :student
end
students controller
def student_params
params.require(:student).permit(:student_fname, :student_lname, :gender, :dob, :aboriginal, :esl, :special_provisions, :user_id, :classroom_id, :group_id, :active)
end
classrooms controller
def classroom_params
params.require(:classroom).permit(:classroom_name, :classroom_year, :classroom_student)
end
students views form
<%= form.select :classroom_id, Classroom.where(:user_id => current_user.id).map {|r| [r.classroom_name, r.id]} %>
student index
at the moment it is id but I want it to be classroom name
<td><%= student.classroom_id %>
Schema file
create_table "classroom_students", force: :cascade do |t|
t.integer "classroom_id"
t.integer "student_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "classrooms", force: :cascade do |t|
t.string "classroom_name"
t.date "year"
t.string "classroom_student"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "users_id"
t.integer "user_id"
t.string "classroom_year"
t.index ["users_id"], name: "index_classrooms_on_users_id"
end
create_table "students", force: :cascade do |t|
t.string "student_fname"
t.string "student_lname"
t.boolean "gender"
t.string "dob"
t.boolean "aboriginal"
t.boolean "esl"
t.text "special_provisions"
t.integer "user_id"
t.integer "classroom_id"
t.integer "group_id"
t.boolean "active"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "classroom_name"
end
Your association says that student has many classrooms but your database schema says you have a clasroom_id too in the students table. Remove that classroom_id first if you need to associate a student to multiple classrooms. Then your select says:
<%= form.select :classroom_id, Classroom.where(:user_id => current_user.id).map {|r| [r.classroom_name, r.id]} %>
Now as you have multiple classrooms associated to one student so this will not work because classroom_id is not valid. Instead it should be like this:
<%= f.select :classroom_ids, Classroom.where(user_id: current_user.id).map { |r| [r.classroom_name, r.id] }, {}, multiple: true %>
This will associate multiple classrooms with the student.
And donot forget to add classroom_ids in strong params:
def student_params
params.require(:student).permit(:student_fname, :student_lname, :gender, :dob, :aboriginal, :esl, :special_provisions, :user_id, :classroom_id, :group_id, :active, :classroom_ids => [])
end
Hope this helps.
First of all, you have an association User has_many :classrooms, so you can't think that you only have 1 classroom.
You can get all the classes associated to an user with something like this:
<%= f.form_for #student do |f| %>
<%= f.collection_select(:classrrom_id, Classroom.all, :id, :classroom_name,
{:prompt => "Select your classrooms"}, {:multiple => true}) %>
<% end %>
And at your students_controller.rb you should add classroom_ids to your params
def student_params
params.require(:student).permit(:student_fname, :student_lname, :gender,
:dob, :aboriginal, :esl, :special_provisions, :user_id, :classroom_id,
:group_id, :active, :classroom_ids => [])
end

Rails4: How can I display the name in assosiated table?

What I'd like to do is to display the name in the details table.
I tried some codes, but I couldn't.
Please advise me on how to display the value.
Controller code:
class BooksController < ApplicationController
def show
#book = Book.find(params[:id])
#article = Article.where(book_id: (params[:id])).order(day: :asc)
end
end
View code:
<div class="row">
<% #article.each do |a| %>
<%= a.day %><br>
<%= a.title %><br>
# want to display the name in detail table where (details.day = articles.day and details.book_id = articles.book_id)
<% end %>
</div>
Relevant model setup:
class Article < ActiveRecord::Base
belongs_to :Book
default_scope -> { order(day: :asc, start_time: :asc) }
end
class Detail < ActiveRecord::Base
belongs_to :Book
end
class Book < ActiveRecord::Base
has_many :articles
has_many :details
end
Schema:
ActiveRecord::Schema.define(version: 2015099999999) do
create_table "details", force: true do |t|
t.integer "book_id"
t.integer "day"
t.string "name"
t.string "detail"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "articles", force: true do |t|
t.integer "book_id"
t.integer "day"
t.string "start_time"
t.string "end_time"
t.integer "category"
t.string "title"
t.string "contents"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "books", force: true do |t|
t.date "publish_date"
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
end
end
You want to define an association between Article and Detail using the :through option:
class Article < ActiveRecord::Base
belongs_to :book
has_many :details, through: :book
end
You can also define an accessor to find just the detail for the matching day:
class Article < ActiveRecord::Base
def matching_detail
details.find_by_day day
end
end
Which you can then use in the view:
<% #article.each do |a| %>
<%= a.matching_detail.try(:name) %>
<% end %>
It's good to define the associations for articles like
has_many :details, through: :book
You can also also use this .
Article.includes(:details).where(book_id: (params[:id])).order(day: :asc)
Or
Article.select('articles.name,details.name,..').joins(:details).where([condition])

Search from one view through a joined HABTM table

I'm trying to set up a simple search from one view through a joined table and, being a newbie, it's not quite working. I'm testing with the word "books" which I know is in the articles table in the subject column.
My error is:
SQLite3::SQLException: no such column: articles.subject: SELECT COUNT(*) FROM "keywords" WHERE (articles.subject LIKE '%books%')
My schema is:
create_table "articles", force: true do |t|
t.string "title"
t.string "subject"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "articles_keywords", id: false, force: true do |t|
t.integer "article_id"
t.integer "keyword_id"
end
create_table "keywords", force: true do |t|
t.string "keyword"
t.datetime "created_at"
t.datetime "updated_at"
end
My model is:
class Keyword < ActiveRecord::Base
has_and_belongs_to_many :articles
accepts_nested_attributes_for :articles
def self.search_for(query)
where('articles.subject LIKE :query', :query => "%#{query}%")
end
My controller action is:
def index
#keywords = params[:q] ? Keyword.search_for(params[:q]) : Keyword.all
end
My view action is:
<%= form_tag "/", method: "GET" do %>
<%= text_field_tag :q %>
<%= submit_tag "Search" %>
<% end %>
<% if #keywords.any? %>
<% #keywords.each do |k| %>
<section>
<h3><b>Title</b>: <%= link_to k.keyword.title, keyword.title %></h3>
<p><b>Subject</b>: <%= keyword.subject%></p>
<% end %>
Many thanks for any help!
You have to join the asocciated table (and you can use scopes):
class Keyword < ActiveRecord::Base
has_and_belongs_to_many :articles
accepts_nested_attributes_for :articles
scope :search_for, ->(query){ joins(:acticles).where('articles.subject LIKE :query', :query => "%#{query}%")}
end
See RubyOnRails-Guides
A gem that eases complex querying is squeel. With squeel the scope would by
scope :search_for, ->(query){ joins{articles}.where{articles.subject =~ "%#{query}%"} }

Resources