Creating XML with Haml - ruby-on-rails

I'm trying to make a dynamic sitemap for my CMS-style rails app, but I am having trouble creating a sitemap in XML using Haml. I looked at the docs and they say that I should be able to use !!! XML to insert the <?xml version="1.0" encoding="UTF-8"?> tag at the beginning of the document. When I try to do this, it does not render anything at all and I am forced to use a literal meta-xml tag. What am I doing wrong?
content_controller.rb
=====================
class ContentController < ApplicationController
# other methods
def sitemap
#sections = Section.all :include => :pages
respond_to do |format|
format.xml
end
end
end
sitemap.xml.haml
================
<?xml version="1.0" encoding="UTF-8"?>
-# !!! XML
-# the above tag does not work
%urlset{:xmlns => 'http://www.sitemaps.org/schemas/sitemap/0.9'}
%url
%loc= root_url
- #sections.each do |section|
- section.pages.each do |page|
%url
%loc= "#{root_url}#{section.url}/#{page.url}"
%lastmod= page.updated_at

You'll need to set :format => :xhtml in order for this to work.
In your environment.rb
Haml::Template.options[:format] = :xhtml
More info here http://www.mail-archive.com/haml#googlegroups.com/msg06984.html

Related

RoR HTML template to .docx

I need to create a .docx file from a HTML template, so I used htmltoword gem.
Usage:
I added the gem (Gemfile):
gem 'htmltoword', '~> 0.5.1' #last version of the gem
I put a route (route.rb):
get 'preview' => 'foo#preview'
And in my bar.html.erb I have a link which target's that url:
<%= link_to '.docx', preview_path %>
Template (preview.docx.erb):
<h1>foobar</h1>
And in the controller (foos_controller.rb):
class FoosController < ApplicationController
respond_to :docx
#other code
def preview
respond_to do |format|
format.docx do
render docx: 'foobar', filename: 'preview.docx'
end
end
end
end
However, I'm getting an error:
ActionController::UnknownFormat
How to fix this error?
My config:
RoR v4.2.4
Ruby v2.2.3p173
Also, there is an open github issue for this/similar topic.
Update: as #kajalojha mentioned, respond_with / Class-Level respond_to has been removed to an individual gem, so I installed the responders gem, however, I get the same error.
Since respond_to has been removed from rails 4.2 to a individual gem i will recommend you to use formatter gem..
For further details you can look to the link given below.
Why is respond_with being removed from rails 4.2 into it's own gem?
Have you tried caracal-rails? You can find it here
I had to build this same functionality in an app earlier this year and also used the htmltoword gem.
# At the top of the controller:
respond_to :html, :js, :docx
def download
format.docx {
filename: "#{dynamically_generated_filename}",
word_template: 'name_of_my_word_template.docx')
}
end
I then have two "view" files that come into play. The first, is my method view file download.docx.haml. This file contains the following code:
%html
%head
%title Title
%body
%h1 A Cool Heading
%h2 A Cooler Heading
= render partial: 'name_of_my_word_template', locals: { local_var: #local_var }
From there, I have another file name_of_my_word_template.docx.haml that contains the meat of my Word file.
%h4 Header
%h5 Subheader
%div= local_var.method
%div Some other content
%div More content
%div Some footer content
When someone hits my_app.com/controller_name/download.docx, a Word file is generated and downloaded for them.
In order to ensure this happens, I have a route for the download method in my routes.rb file:
resources :model_name do
member do
get :download
end
end
Apologies for the long reply ... this has worked well for me and I hope helps you through this issue!
So, I figured it out. I added format: 'docx' to the route and it works now.
Note: as #kajalojha mentioned, respond_with / Class-Level respond_to has been removed to an individual gem, so I installed the responders gem.
Let's create a download logic.
Gemfile
gem 'responders'
gem 'htmltoword', '~> 0.5.1'
routes.rb
get 'download' => 'foos#download', format: 'docx' #added format
foos_controller.rb
class FoosController < ApplicationController
respond_to :docx
def download
#bar = "Lorem Ipsum"
respond_to do |format|
format.docx do
# docx - the docx template that you'll use
# filename - the name of the created docx file
render docx: 'download', filename: 'bar.docx'
end
end
end
end
download.docx.erb
<p><%= #bar %></p>
And I've added some link to trigger the download logic:
<%= link_to 'Download bar.docx', foo_download_path %>
Which will download the bar.docx file with "Lorem Ipsum" in it.

XLS Format in Ruby on Rails Resources

In an RoR application, I would like the index resource to download an xls file.
Currently, I can get the file to download but it requires a link to a path I created like so
link_to subscribers_path(:format => :xls)
Ideally like the subscribers_path on it's own to add the format xls, and I don't want a view in html format with index.
My file structure
/subscribers
index.html.erb
index.xls.erb
and my controller code
def index
#subscribers = Subscriber.all
respond_to do |format|
format.html
format.xls #{send_data #subscribers.to_csv(col_sep: "/t")}
end
end
I tried to remove html to see if it would just default to another path but it says unknownformat error. I also tried to remove the index.html.erb file to see if would fallback to another format of the same name, but that didn't work.
Hopefully my objective is clear: How do I use the resource index path to link with xls instead of html format?
First, to clarify things: Yes, Rails does fully support XLS.
To use it just put the following line under config/initializers/mime_types.rb:
Mime::Type.register "application/xls", :xls
Then, as the author did it, change the format to xls and create an index.xls.erb
Now, to answer your question, there is a number of ways:
The first thing I thought of, was to just change the request format. In your controller:
class SubscriberController < ActionController::Base
before_filter :change_format
def index
#subscribers = Subscriber.all
respond_to do |format|
format.xls #{send_data #subscribers.to_csv(col_sep: "/t")}
end
end
private
def change_format
request.format = "xls"
end
end
There should also be a dozen other ways to do this, but I would prefer this one.

How to generate custom response for REST API with Ruby on Rails?

I'm implementing a REST API in Rails 3. We allow for JSON and XML as response formats.
The default respond_with works fine as long as one wants only the requested resource to be returned, e.g.:
def show
respond_with User.find(params[:id])
end
GET /users/30.xml
<?xml version="1.0" encoding="UTF-8"?>
<user>
<birthday type="date">2010-01-01</birthday>
<company-name>Company</company-name>
<email>email#test.com</email>
<id type="integer">30</id>
</user>
However, I would like to get the following standardized response:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>
<success type="boolean">true</success>
</status>
<result>
<user>
<birthday type="date">2010-01-01</birthday>
<company-name>Company</company-name>
<email>email#test.com</email>
<id type="integer">30</id>
</user>
</result>
</response>
How can I achieve this result?
I tried the following, using a custom Response class
class Response
STATUS_CODES = {
:success => 0,
}
extend ActiveModel::Naming
include ActiveModel::Serializers::Xml
include ActiveModel::Serializers::JSON
attr_accessor :status
attr_accessor :result
def initialize(result = nil, status_code = :success)
#status = {
:success => (status_code == :success),
}
#result = result
end
def attributes
#attributes ||= { 'status' => nil, 'result' => nil }
end
end
and redefining the respond_with method in my ApplicationController:
def respond_with_with_api_responder(*resources, &block)
respond_with_without_api_responder(Response.new(resources), &block)
end
alias_method_chain :respond_with, :api_responder
However, that does not yield the intended result:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>
<success type="boolean">true</success>
</status>
<result type="array">
<result>
<birthday type="date">2010-01-01</birthday>
<company-name>Company</company-name>
<email>email#test.com</email>
<id type="integer">30</id>
</result>
</result>
</response>
What should be <user> is now again <result>. This gets even worse when I return an array as the result, then I get even another <result> layer. And if I look at the JSON response, it looks almost fine – but notice that there is an array [] too much wrapping the user resource.
GET /users/30.json
{"response":{"result":[{"user":{"birthday":"2010-01-01","company_name":"Company","email":"email#test.com"}}],"status":{"success":true}}}
Any clue what is going on here? How can I get the desired response format? I also tried looking into writing a custom Responder class, but that boiled down to rewriting the display method within ActionController:Responder, giving me the exact same problems:
def display(resource, given_options={})
controller.render given_options.merge!(options).merge!(format => Response.new(resource))
end
I believe that the problem is somehow hidden in the serialization code of ActiveModel, but I can't seem figure out how I can wrap a resource within a container tag and still achieve that the wrapped resource is being serialized correctly.
Any thoughts or ideas?
Here's what I did in the end:
I got rid of the Response class.
I added to_json and to_xml methods to all models:
[:to_json, :to_xml].each do |method_name|
define_method(method_name) do |options = {}|
options ||= {}
options[:only] ||= # some filtering
super(options)
end
end
I redefined the respond_with method in my ApplicationController:
def api_respond_with(resources, &block)
default_respond_with do |format|
format.json { render :json => resources, :skip_types => true, :status => :ok }
format.xml { render :xml => resources, :skip_types => true, :status => :ok }
end
end
alias_method :default_respond_with, :respond_with
alias_method :respond_with, :api_respond_with
I wrote a custom middleware with appropriate methods to add the desired wrapping:
class StandardizedResponseFilter
def _call(env)
status, headers, response = #app.call(env)
if headers['Content-Type'].include? 'application/json'
response.body = standardized_json_wrapping(response.body, env)
elsif headers['Content-Type'].include? 'application/xml'
response.body = standardized_xml_wrapping(response.body, env)
end
[status, headers, response]
end
end
If anyone knows a better approach, feel free to leave a comment.
Normally what I'd do in this case is override the ActiveModel#to_xml and ActiveModel#to_json methods. The documentation on #to_xml describes the possible options. You could probably make your Request object inherit from ActiveModel, and then override the #to_xml method with a pattern like this:
def to_xml(options = {})
# muck with options such as :only, :except, :methods
options[:methods] ||= []
[:status, :result].each { |m| options[:methods] << m }
super(options)
end
In particular I think you'll find options[:methods] useful because it lets you define arbitrary methods that return attributes and get included in the output.

How to render sitemap.xml in rails app

I have added /views/sitemap/index.xml and want it displayed when i go to the relevant url.
class SitemapController < ApplicationController
def index
respond_to do |format|
format.html
format.xml
end
end
end
And in routes.rb
match "sitemap/" => "sitemap#index"
Using Rails 3
When I go to mydomain.com/sitemap/ I just get a white page. Any ideas?
index.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>http://www.mydomain.com/</loc>
<changefreq>weekly</changefreq>
</url>
</urlset>
Problem is that you are using your index action to render xml and it will render "index.xml"
file not "sitemap.xml" which is what you have created in your views
While your routes are correct, you are using the wrong filename in views
Try renaming sitemap.xml file to index.xml ( in the views/sitemap folder)
If you define name routes, you need to define :format with it
match "/sitemap/sitemap.[:format]", :to => "sitemap#index"
it will pickup your format from there. Also you can define a default format in the routes
match "sitemap/sitemap.xml", :to => "sitemap#index", :defaults => {:format => :xml}
I may be wrong , but I see 2 reasons:
index action doesn't actually do anything judging by this code sample, it just responds back with no info.
you need to render your object as xml - if you don't rails, doesn't know you want xml - it just treats it as another file extension. It actually lets you do little tricks - like sending json to an xml request ( thou I have no idea why would anyone try to do that). Thou one useful application is that you can make rails send custom rendering of an object to a common format or render regular data in common format for an unusual extension ( we had a client who wanted csv data for a .dat request)
Here is a short example, from a sample home controller:
class HomeController < ApplicationController
def index
#m = {
:color => "yellow",
:total => "20"
}
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #m}
end
end
end
this returns this object as xml:
<hash>
<total>20</total>
<color>yellow</color>
</hash>

Rails nested resources

I'm trying to get my head around nested associations in Rails using ActiveResource.
My example is as follows:
What I have is an airport with many runways.
My show action in airports controller contains:
#airport = Airport.find(params[:id])
When I call http://localhost/airports/2.xml I get that piece of XML:
<airport>
<code>DUS</code>
<created-at type="datetime">2009-02-12T09:39:22Z</created-at>
<id type="integer">2</id>
<name>Duesseldorf</name>
<updated-at type="datetime">2009-02-12T09:39:22Z</updated-at>
</airport>
Now, I changed the action to
#airport = Airport.find(params[:id], :include => :runways)
How can I achieve that above loading above URL is giving me something like:
<airport>
<code>FRA</code>
<created-at type="datetime">2009-02-12T09:39:22Z</created-at>
<id type="integer">2</id>
<name>Frankfurt</name>
<updated-at type="datetime">2009-02-12T09:39:22Z</updated-at>
<runways>
<runway>
<id>1</id>
<name>bumpy runway</name>
</runway>
</runways>
</airport>
And on top of that: If I have a client with
class Airport < ActiveResource::Base
..
end
and
class Runway < ActiveResource::Base
..
end
How can I get it to automatically load associations like:
a = Airport.find(1)
puts a.runways.length
=> 1
And (last but not least): Is there a way to store data from the client like:
a = Airport.find(1)
a.runways << Runway.find(1)
a.save
Maybe I'm really too blind, but I'm stuck...
Any idea is warmly welcome.
Thanks
Matt
Resolved it myself finally.
Wasn't aware to put the include into the render statememt:
def show
#airport = Airport.find(params[:id], :include => :runways)
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #airport.to_xml(:include => :runways) }
end
end
The :include option for the finder specifies that it should eagerly fetch the related items from the database. The :include option for to_xml specifies that it should be included in the XML rendering.
If the canonical XML representation includes the related objects, you can override the to_xml method to make your life a little simpler:
class Airport
def to_xml(options={})
super(options.merge(:include => :runways))
end
end
and then since render will call to_xml if you don't, your controller code can simply be
format.xml { render :xml => #airport }

Resources