rails 3 single table inheritance with multiple forms - ruby-on-rails-3

I have set up a rails application that uses single table inheritance but I need to have a distinct form for my child classes. The application keeps a collection of indicators of security compromise, such as malicious IP addresses. So I have a class called Indicator which holds most of the information. However, if the indicator is a malware hash I need to collect additional information. So I created another class called MalwareIndicator which inherits from Indicator. Everything is working fine with that.
I wanted my routes to be restful and look nice so I have this in my config/routes.rb file
resources :indicators
resources :malware, :controller => "indicators", :type => "MalwareIndicator"
That works very nicely. I have all these routes that point back to my single controller. But then in the controller I'm not sure how to handle multiple forms. For example, if someone goes to malware/new the Indicators#New function is called and it is able to figure out that the user wants to create a MalwareIndicator. So what must my respond_to block look like in order to send the user to the correct form? Right now it still sends the user to the new indicator form.
def new
if params[:type] == "MalwareIndicator"
#indicator = MalwareIndicator.new
else
#indicator = Indicator.new
end
#pagename = "New Indicator(s)"
respond_to do |format|
format.html # new.html.erb
format.json { render json: #indicator }
end
end
I feel like I'm pretty close. On the other hand, I might be doing everything wrong so if anyone wants to slap me and say "quit being a dumbass" I would be grateful for that as well.

I usually try to avoid STI because there are only troubles with that (image third indcator with different attributes and fourth and fifth with more fields and before you realize you end up with huge table where most columns are unused). To answer your question: you can create different new views for different classes and respond like that:
respond_to do |format|
format.html { render action: "new_#{#indicator.class.to_s.underscore}" }
format.json { render json: #indicator }
end
that should render new_indicator.html.erb or new_malware_indicator.html.erb depends on #indicator class.

I handled it in the view itself. The route entry for malware causes the controller to receive a type parameter and the controller uses that to create an instance of the correct class. In the new.html.erb file I put this at the end:
<%= render :partial => #indicator.class.to_s.downcase %>
So if a MalwareIndicator was created by the controller then #indicator.class.to_s.downcase will return malwareindicator. I have a partial file called _malwareindicator.html.erb which has the correct form in it.
So if I have to create another descendant of the Indicator class I can add another resources entry to the routes file and create a partial called _whateverindicator.html.erb and it should work out OK.

Related

How can I call a controller/view action from a mailer?

In my rails application I've created a business daily report. There is some non-trivial logic for showing it (all kind of customizable parameters that are used for filtering in the model, a controller that calls that model and some non-trivial view for it, for example, some of the columns are row-spanning over several rows).
Now I wish to send this report nightly (with fixed parameters), in addition to the user ability to generate a customize report in my web site. Of course, I wish not to re-write/duplicate my work, including the view.
My question is how can I call the controller action from my mailer so that it will be as if the page was requested by a user (without sending a get request as a browser, which I wish to avoid, of course)?
In answer to your question is if you are generating some sort of pdf report then go with using the wicke_pdf gem does exactly that generates pdfs. To send a report on a nightly basis the best thing for this is to implement some sort of cron job that runs at a particular time which you can do using the whenever gem. You can do something like:
schedule.rb
every :day, :at => '12:00am'
runner User.send_report
end
With this at hand you can see that you call the send_report method sits inside the User model class as shown below:
User.rb
class User < ActiveRecord::Base
def self.send_report
ReportMailer.report_pdf(#user).deliver
end
end
Inside send_report we call the mailer being ReportMailer which is the name of the class for our mailer and the method being report_pdf and pass in the user. BUT remember this is an example I have here I am not sure the exact specified information you want in a report.
Mailer
class ReportMailer< ActionMailer::Base
default :from => DEFAULT_FROM
def report_pdf(user)
#user = user
mail(:subject => "Overtime", :to => user.email) do |format|
format.text # renders report.text.erb for body of email
format.pdf do
attachments["report.pdf"] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "report",:template => 'report/index.pdf.erb',
:layouts => "pdf.html"))
end
end
end
end
Inside the mailer there are a variety of things going on but the most important part is inside the format.pdf block that uses a variety of wicked_pdf methods (this is assuming that you are using wicked_pdf btw. Inside the block you create a new WickedPDF pdf object and render it to a string. Then provide it with the name of the report, the template and the layout. It is important that you create a template. This usually will where the report will be displaying from. The file type is a .pdf.erb this means that when this view or report is generated in the view the embedded ruby tags are being parsed in and the output is going to be a pdf format.
UserController
def report
#user = User.scoped
if params[:format] == 'pdf'
#Do some stuff here
User.send_report(#users)
end
respond_to do |format|
format.html
format.pdf do
render :pdf => "#{Date.today.strftime('%B')} Report",
:header => {:html => {:template => 'layouts/pdf.html.erb'}}
end
end
end
The key thing you asked that I picked up on.
how can I call the controller action from my mailer
In the controller simply collate a scope of Users, then check the format is a pdf, providing it is do some stuff. Then it will run the method send_report which I earlier highlighted in the user model class (Btw in your words this is the controller calling the model). Then inside the respond block for this there is a format.pdf so that you can generate the pdf. Once again note that you need a template for the core design of the pdf, which is similar to how rails generates an application.html.erb in the layouts. However here we have a pdf.html.erb defined. So that this can be called anywhere again in your application should you want to generate another pdf in your application somewhere else.
Think I've provided a substantial amount of information to set you off in the right direction.

Rails nested resources and routing - how to break up controllers?

I have the following models:
Post
Tag
TaggedPost (from which Post and Tag derive their associations by has_many :through)
And I have the following routes.rb file:
resources :tags
resources :posts do
resources :tags
end
So when I navigate to, say, /posts/4/tags, that will shoot me into the index action for the Tag controller with the post_id value set in the parameters array. Cool.
My question is though, now that I'm accessing the nested tags resource under posts, should I be hitting the Tags controller still? Or should I setup some other controller to handle the nested nature of tags at this point? Otherwise I have to build additional logic into the Tags controller. This can be done of course, but is this the common way of handling nested routes and resources? The code I have in the index action for the Tags controller is as follows:
TagsController.rb
def index
if params[:post_id] && #post = Post.find_by_id(params[:post_id])
#tags = Post.find_by_id(params[:post_id]).tags
else
#tags = Tag.order(:name)
end
respond_to do |format|
format.html
format.json {render json: #tags.tokens(params[:q]) }
end
end
I can see the code in this controller growing increasingly large, as I plan for many additional resources to be associated with tag resources. Thoughts on how to break this out?
Summary of questions:
If a resource is nested, should the nested resource be going through a different controller representing the nested nature of the resource? This is opposed to going through the normal controller as I am in the code example that I provided.
If so, how should these controllers be named and setup?
Let me know if you need more information.
I think the best solution is to split up controllers:
resources :tags
resources :posts do
resources :tags, controller: 'post_tags'
end
And then you have 3 controllers. Optionally, you can inherit
PostTagsController from TagsController to do something like:
class PostTagsController < TagsController
def index
#tags = Post.find(params[:post_id]).tags
super
end
end
If the difference is only the retrieval of tags, you can:
class TagsController < ApplicationController
def tags
Tag.all
end
def tag
tags.find params[:id]
end
def index
#tags = tags
# ...
end
# ...
end
class PostTagsController < TagsController
def tags
Product.find(params[:product_id]).tags
end
end
Use that methods and simply override tags in the inheriting controllers ;)
All you are doing with nested resources is changing the routing URL. Only thing you would have to do is make sure you are passing the proper id (in your case post)to the tag controller. Most common error is the Can't Find *** ID.
If you don't nest a profile route into a user route it would look like this
domain.com/user/1
domain.com/profile/2
When you nest the routes it would be
domain.com/user/1/profile/2
That is all that it is doing and nothing else. You don't need additional controllers. Doing nested routing is just for looks. allowing your user to follow the association. The most important thing about nesting routes is that you make sure you make the link_to's to the right path.
When not nested: it would be
user_path
and
profile_path
when it is nested you would need to use
user_profile_path
rake routes is your friend to find out how the routes have changed.
Hope it helps.

REST Routes and overriding :id with to_param

My controller is using the default RESTful routes for creating, adding, editing etc
I want to change the default :id to use :guuid. So what I did was:
# routes.rb
resources :posts
# Post Model
class Post < ActiveRecord::Base
def to_param # overridden
guuid
end
end
This works but my modifed REST controller code has something like this
def show
#post = Post.find_by_guuid(params[:id])
#title = "Review"
respond_to do |format|
format.html # show.html.erb
end
end
When I see this this code ..
Post.find_by_guuid(params[:id])
it would seem wrong but it works.
I don't understand why I can't write it out like this:
Post.find_by_guuid(params[:guuid])
Why do I still have to pass in the params[:id] when I'm not using it?
Looking for feedback on whether my approach is correct or anything else to consider.
Even though it works it doesn't always mean it's right.
Type rake routes in your console, and check the output of the routes. You'll see the fragment ':id' in some of them, that's where the params[:id] comes from. It's a rails convention : when you use resources in your routes, the parameter is named id. I don't know if you can change it (while keeping resources; otherwise you could just go with matching rules), but you shouldn't anyway : even if it seems not very logic, it actually has sense, once your understand how rails routing works.

Rails authentication - Devise nested models

This is probably a really simple question but one I've never quite worked out myself being still fairly new to rails.
I've setup devise on one project locally that works very well, however at the time I couldn't think of a way to set authentication up for my models properly, I didn't want the user to have to edit the user details to edit their respective model details at the same time so I put a hidden form in the models form containing the value for the 'current_user.id' to ensure it always saved to the logged in user - however obviously I've realised that the value of this form could be changed to anything via the source and any data re-assigned to any user.
What is the proper way to easily setup models that belong to users, which can be created/edited/deleted etc. against that user independently without having to save user details alongside in the 'accepts_nested_attributes_for' way I've seen before with things like this?
I guess I'm just looking to dig a bit deeper and understand how to relate these models to a user, but the models work completely independently off themselves and don't require user data from the Users model to be saved.
An example data structure is:
User -> Posts and Posts -> Comments where posts/comments can be added/edited deleted without having to change the original user data, an email/password is just used for authentication purposes.
I appreciate the object structure could quite easily be:
User:[{
Posts:[{
"name":"test",
"description":"test"
{
"name":"test2",
"description":"test2"
}]
}]
}]
But in this specific example I would want Posts to be a separate model with their own comments on each and the only relationship to be that the post in question was created by "Joe Bloggs" or UserID 4.
Thanks in advance guys and apologies for the rambling, just want to make sure I make sense!
Using devise you can simply add a line to the create action in the controller that devise recognises and actually does this relationship for you without having to create nested attributes or forms. It's the '#post.user = current_user' line below that automagically does this! So the models are still technically nested if you will but you don't have to change any of your original forms etc. to get them to nest to users correctly, it just passes the user ID.
i.e.
def create
#post = Post.new(params[:post])
#post.user = current_user
respond_to do |format|
if #post.save
format.html { redirect_to #post, :notice => 'Post was successfully created.' }
format.json { render :json => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.json { render :json => #snippet.errors, :status => :unprocessable_entity }
end
end
end

Defining a route for a method in rails 3

I'm new to Rails and currently using Rails 3, so please bear with me. I have a basic app, with a basic scaffolded controller/model e.g Contacts.
Amongst the methods for Show/Edit etc.. i have added a method called newcontacts (i have also added a newcontacts.html.erb), which will eventually show the last 5 contacts imported , but at the moment i am using the same code one would find in the basic Index method of a controller (i intend to filter the data at a later point), the method in the controller is -
def newcontacts
#contacts = Contact.all
respond_to do |format|
format.html # index.html.erb
end
end
I can access localhost:3000/contacts which displays the index method action from the contact controller, but when i try and access this method (newcontacts) using localhost:3000/contacts/newcontacts it returns the error
Couldn't find Contact with id=newcontacts
I have looked at the routes.rb file as i believe this is what needs editing, and have added the following line to routes.rb
match 'newcontacts', :to => 'contacts#newcontacts'
but this only works when i call localhost:3000/newcontacts.
So my question is, how do i get the url localhost:3000/contacts/newcontacts to work?
Any help would be great.
I think what you're trying to do is add another RESTful action.
resources :contacts do
# This will map to /contacts/newcontacts
get 'newcontacts', :on => :collection # Or (not and; use only one of these)...
# This will map to /contacts/:id/newcontacts
get 'newcontacts', :on => :member # ... if you want to pass in a contact id.
end
Try this in your routes.rb file:
resources :contacts do
member do
put 'newcontacts'
end
end
That will add in a new action for the contacts controller.