Following the sequence I started here, but changing the proposed order, let’s talk a little about Rails Generators.
Generators are one of the coolest things in Rails!
Of course you have already seem them, remember the first screencast you’ve seen about rails? Yes, that one where the guy wrote an entire CRUD application in two minutes …
Generators are a way to, generate code
But the generated code can be what you want, and rails already has a great support for writing generators, because it is already used in the rails core.
OK, but why would you want to write a generator?
Think about the project that is starting today, remember all that CRUD forms you’ll need to create, or that almost equal start point for every page of the application …
Now think about all the applications for your company …
Got it?
Ok, I agree that “scaffolding” a generic CRUD is not really useful, but if that scaffold is done following your company standards, it can improve a lot the development performance …
Now, I guess you are thinking: Good, generators seem cool, but stop the easy talk e show me the code!
So, let’s play a little …
Wi’ll start creating a new rails project: rails plugins102
Now from the project directory run: script\generate plugin my_generator
(if you think this is kind of a flash back, maybe you read my previous post about creating rails plugins
)
Now that you have already created your brand new plugin, let’s write some code!
To create a generator you need to create a generators directory inside your plugin’s directory, a directory with the name you want for your generator (for example test_gen), and a templates directory inside the previous one, for example, it will look like this:
Ok, you do not really need a plugin to create a generator, you can of course, place your brand new generator inside the following directories:
But I think that a plugin is the easiest way to do it, and it will be easier to deploy to your applications too.
Of course that a GEM would be a better way, because you can install only once per machine, and does not need it installed on the server, but I have never created a GEM before, so let’s stay with the plugin for a wile …
to start coding the generator, we’ll need to create a file named [generator_name]_generator.rb inside the generator’s directory, in my case, I’ve created the file: my_generator/generators/test_gen/test_gen_generator.rb
Now inside that file, create the class to define your generator, it must be named according to the file name, in my case TestGenGenerator, and this class must extend one of the base classes for Rails generators:
Rails::Generator::Base or Rails::Generator::NamedBase, I’ll use NamedBase (The base for the controller generator), and I’ll create a generator for a model and a migration, just to show how to do it …
The NamedBase is the perfect base class for generators that expect parameters in the form: Name [param1] [param2] …
For all the others, Base is a best start point …
The starting point for our code is:
1 2 3 4 5 6 7 | class TestGenGenerator < Rails::Generator::NamedBase def manifest record do |m| end end end |
In this class, all we need to do is to set up the generator’s manifest (more on this later) and to setup any local variables to be used by our templates (more on this later too).
with only this, we can already run: ruby script\generate test_gen asdas_dasda asd:ash (randon letters as parameters for now).
The NamedBase will automatically set up some variables for us, and the values for the given parameters will be:
of course this generator now will generate nothing, because our manifest is empty, so let’s build a simple example to see how it really works …
so, lets create inside the template directory, a directory called “dummy”, and a blank file called “log.log” inside of it and let’s change the manifest method to some thing like this:
1 2 3 4 5 | def manifest record do |m| m.file 'dummy/log.log', "log/#{file_path}.log" end end |
This code will tell the generator, to copy our newly created, blank file, to $APP_ROOT/log/asdas_dasda.log if we run the generator with the same parameters as before …
but just copying files from one place to another is not a very cool thing to be done, so let’s play a little with ERB, and let’s create a migration for our plugin, so we need to change again the manifest method as follow:
1 2 3 4 5 6 7 8 | def manifest @migration_name = "Create#{class_name}" @migration_action = "add" record do |m| m.file 'dummy/log.log', "log/#{file_path}.log" m.migration_template 'lib/mymigration.rb',"db/migrate", :migration_file_name => "create_#{file_path}" end end |
As you can see in the code, I’m telling the manifest that I have a template named mymigration.rb inside the directory lib in my templates directory, it will be processed and the result will be placed inside the directory “db/migrate” and will be called “000_create_asdas_dasda.rb” (the 000 will be replaced with the latest migration number plus one as the normal “generate migration” command do.
to be able to access the attributes migration_name and migration_action in the template, we need create the accessor methods for them, it is as easy as adding the following line to the TestGenGenerator class (outside the manifest method):
1 | attr_accessor :migration_name, :migration_action |
My migration template is the following:
1 2 3 4 5 6 7 8 9 10 11 | class <%= migration_name.underscore.camelize %> < ActiveRecord::Migration def self.up<% attributes.each do |attribute| %> <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%> <%- end %> end def self.down<% attributes.reverse.each do |attribute| %> <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%> <%- end %> end end |
It is an ERB template that will generate a ruby file.
Now we have already a very cool working plugin, but what else can we do?
to answer this question, I’ll quote a little the documentation of the Rails::Generator::Commands::Create class, that means, what can you do within the manifest method:
I think that is it, you can ask more questions if you want, I’ll try to answer all, if any
for more resources on Rails generators you can follow this links:
http://wiki.rubyonrails.org/rails/pages/UnderstandingGenerators
http://www.aidanf.net/node/33
http://api.rubyonrails.org/classes/Rails/Generator/Base.html
http://api.rubyonrails.org/classes/Rails/Generator/NamedBase.html
http://api.rubyonrails.org/classes/Rails/Generator/Commands/Create.html
I hope this little step by step help some one!
The next one will be about writing tests for your plugins! and the fourth I have not started to think about yet
If you enjoyed this post, make sure you subscribe to my RSS feed!
One of the beauty of Ruby on Rails are the Plugins …
Ruby On Rails itself is already a great framework, but the combination of Rails plugins with the Ruby Open Classes is a killer feature!
This little combination enables you to create you own “tags” to be used in your views and layouts.
And it is really easy to create this kind of helper in Rails.
The non plugin way is to create a method in one of the Helper classes (the ones in app/helpers directory), for example, if all the forms in your application are inside a table, with one column for the label and one column for the real field, you can create a helper to simplify the code in your views like this:
The standard very simple form for the “Example” model we created for this example, in the application standards would be:
1 2 3 4 5 6 7 | <% form_for(@example) do |f| %> <table> <tr><td><label for="example_name">Name</label></td><td><%= f.text_field :name %></td></tr> <tr><td><label for="example_name">Url</label></td><td><%= f.text_field :url %></td></tr> <tr><td colspan="2"><%= f.submit "Update" %></td></tr> </table> <% end %> |
but if we edit the file app/application_helper.rb and add the following method there:
1 2 3 | def textfield label, object, property, options = {} %Q{<tr><td><label for="#{object.to_s}_#{property.to_s}">#{label}</label></td><td>#{text_field object, property, options}</td></tr>} end |
we could simplify a lot the code only with the help of this “helper”, the new form code would be like this:
1 2 3 4 5 6 7 | <% form_for(@example) do |f| %> <table> <%= textfield "Name", :example, :name %> <%= textfield "Url", :example, :url %> <tr><td colspan="2"><%= f.submit "Update" %></td></tr> </table> <% end %> |
for this little form, this is too much work for some less characters in the view, but if you think about your entire application it would help you a lot!
Now think bigger, this ugly table/form standard I’ve created is the standard for the entire company, all applications in the company follow this standard.
So all developers, always write the forms using the first option (the ugly one, with lots of HTML).
and one day, a new designer tells that you need to add one new class in the “TR” tag in all forms for all applications, and you start crying ![]()
Or you choose to use the second option (with the helper method) and just need to change one line of code for each application in the company!
Now you are a hero, right?
But if you are smarter than this? what about creating a plugin, that all applications can use, and define this tag inside the plugin?
That way you change only one line of code, test only once, and all applications will be fixed at the same time!
And as always with Rails, this is a lot easy to do!
Just follow this steps:
1 2 3 4 5 6 | # LifeSaver module LifeSaver def textfield label, object, property, options = {} %Q{<tr><td><label for=#{object.to_s}_#{property.to_s}>#{label}</label></td><td>#{text_field object, property, options}</td></tr>} end end |
1 2 | # Include hook code here ActionView::Base.send :include, LifeSaver |
Now you have just created your first Ruby On Rails plugin!
And yes, the code is the same as the one used in the application_helper.rb, the only trick here is in the init.rb file of the plugin, that line of code is including all methods of the module “LifeSaver” in the base class for all Rails views, the ActionView::Base class …
Now, if you did this, instead of just adding a helper method to each application, tell your boss how much time you have saved for the company with this little story here, and ask for a rise
I hope this little step by step help some one!
The next one will be about writing tests for your plugins, and the third one will talk about plugins with generators, the forth you’ll have to come back here to discover
If you enjoyed this post, make sure you subscribe to my RSS feed!
Tags: ActionView::Base, howto, plugin, rails, ruby, step-by-step
Working with Flex and Java as backend, I decided that the best choice for the project I’m working now is to simply post XML from Flex and get XML back from Java (Flex works really well with XML).
But there is a problem with that, creating a XML with one or two values is easy, but if you need to serialize a very large object tree you just got yourself a lot of work.
So I’ve wrote this very simple Flex XML Serializer, and decided to post it here, because some one might need some thing similar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package teste { import mx.utils.ObjectUtil; public class Utils { public static function objectToXml(obj : Object, name : String) : XML{ var result : XML; var info:Object = ObjectUtil.getClassInfo(obj); if(name==null) name = info.name; result = new XML("<" + name + "></"+ name + ">"); for each (var qn : QName in info.properties){ var val : Object = obj[qn.toString()]; if(ObjectUtil.isSimple(val)) result[qn.toString()] = val; else result.appendChild(objectToXml(val,qn.toString())); } return result; } } } |
To use is is really simple:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="appInit()"> <mx:Script> <![CDATA[ import teste.Utils; import mx.utils.ObjectUtil; import mx.controls.Alert; public function appInit() : void { var obj : Object = {name:'teste',address:{street:'rua',number:20}}; Alert.show(Utils.objectToXml(obj,'teste').toXMLString()); } ]]> </mx:Script> </mx:Application> |
The generated XML will look like this:
1 2 3 4 5 6 7 | <teste> <address> <number>20</number> <street>rua</street> </address> <name>teste</name> </teste> |
This might not be the best way to work, but I liked this solution for the problem I had
One thing I changed from this code to the code that is in production today is that I’m using XML attributes now, and as you can see below the change was big from the first example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package teste { import mx.utils.ObjectUtil; public class Utils { public static function objectToXml(obj : Object, name : String) : XML{ var result : XML; var info:Object = ObjectUtil.getClassInfo(obj); if(name==null) name = info.name; result = new XML("<" + name + "></"+ name + ">"); for each (var qn : QName in info.properties){ var val : Object = obj[qn.toString()]; if(ObjectUtil.isSimple(val)) result['@' + qn.toString()] = val; else result.appendChild(objectToXml(val,qn.toString())); } return result; } } } |
Could not find the difference? the only change was the “‘@’ +” at line 16, and I was using attributes for the values ![]()
So, what do you think about this solution?
The next step is to write a deserializer and we’ll make XML rock the Flex world (just kidding
)
If you enjoyed this post, make sure you subscribe to my RSS feed!
Tags: actionscript, flex, howto, tip
Probably you have already heard about Adobe Flex.
Flex is a SDK to develop Rich Internet Applications that will run within the Flash Player, and Flash Player is present in almost all browser and all desktop platforms today.
Flex applications generate an SWF file and this file will communicate with a back end server, this backend server can be a Flash Lifecycle Server, but you can use java and open source for it too.
This two/tree examples work without any paid software, you will need only:
Flex SDK – that is free, and the version 3 will be open source.
A servlet container – I’m using Tomcat
– For the first example that is all
And for the other two examples you’ll need to download
OpenAMF – A flex remoting implementation in Java
RemoteObjectAMF0 – an implementation of the RemoteObject tag that supports the version 0 of AMF protocol (OpenAMF does not supports the version 3 yet)
So, lets start, create a java web project with your favorite IDE, and create a Servlet named TestServlet, I’ll use jaxb to render XML from the servlet, but you can use anything else, if you want to use jaxb too, the schema I created for this example is the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.com/example" xmlns:tns="http://www.example.com/example" elementFormDefault="qualified"> <element name="TestSVo" type="tns:TestList"></element> <complexType name="TestSVoType"> <attribute name="id" type="int"></attribute> <attribute name="name" type="string"></attribute> <attribute name="other" type="string"></attribute> </complexType> <complexType name="TestList"> <sequence minOccurs="1" maxOccurs="unbounded"> <element name="all" type="tns:TestSVoType"></element> </sequence> </complexType> </schema> |
This schema is for a XML like the following:
1 2 3 4 5 | <TestSVo> <all id="0" name="foo" other="bar"/> <all id="1" name="foo1" other="bar1"/> ... </TestSVo> |
Generate the stubs for the XML generation using JaxB or use any other tool to generate a XML with this schema.
Like any other Java application, I’ll start with my web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd "> <servlet> <servlet-name>testServlet</servlet-name> <servlet-class>....servlet.TestServlet</servlet-class> </servlet> <servlet> <servlet-name>AdvancedGateway</servlet-name> <servlet-class>org.openamf.AdvancedGateway</servlet-class> <init-param> <description> Location of the OpenAMF config file. </description> <param-name>OPENAMF_CONFIG</param-name> <param-value>/WEB-INF/openamf-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>AdvancedGateway</servlet-name> <url-pattern>/gateway2</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>testServlet</servlet-name> <url-pattern>/TestServlet</url-pattern> </servlet-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>teste.html</welcome-file> </welcome-file-list> </web-app> |
for the first example, the only needed servlet is the test servlet, for the other examples the AdvancedGateway servlet is used …
Now write the following code in the servlet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; /** * Servlet implementation class for Servlet: TestServlet * */ public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet { static final long serialVersionUID = 1L; private JAXBContext jc; private Marshaller marc; private Unmarshaller unmarc; private ObjectFactory factory; public TestServlet() { super(); factory = new ObjectFactory(); try { jc = JAXBContext.newInstance("....servlet"); marc = jc.createMarshaller(); unmarc = jc.createUnmarshaller(); } catch (JAXBException e) { e.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { float v = Float.parseFloat(request.getParameter("v")); float v1 = Float.parseFloat(request.getParameter("v1")); response.getWriter().print(v * v1); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("List called"); TestList res = new TestList(); for(int i=0;i<40;i++){ res.getAll().add(new TestSVoType(i,"Téste ã " + i)); } JAXBElement<TestList> elem = factory.createTestSVo(res); System.out.println(elem.toString()); try { marc.marshal(elem, response.getWriter()); } catch (JAXBException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } |
the doGet method of this servlet is a calculator, it just multiply the two parameters and prints out the result.
The doPost prints a XML like the one above, automatically generated using the JAXB API.
Now the flex part:
Create a file named xmltest.mxml in the home folder of your webapp with the following content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" backgroundColor="#F4F4F4"> <mx:HTTPService id="calc" method="GET" url="http://localhost:8080/teste/TestServlet" > </mx:HTTPService> <mx:HTTPService id="list" method="POST" url="http://localhost:8080/teste/TestServlet" > </mx:HTTPService> <mx:Script> <![CDATA[ import mx.controls.DataGrid; import mx.controls.Alert; ]]> </mx:Script> <mx:Panel width="80%" height="531" title="Teste"> <mx:Label width="100%" color="blue" text="Type two numbers and press calculate." /> <mx:TextInput id="v1" text="3" /> <mx:TextInput id="v2" text="5" /> <mx:Label id="lbl" text="{calc.lastResult}"/> <mx:Button label="Calculate" click="calc.send({v:v1.text,v1:v2.text});" /> <mx:DataGrid dataProvider="{list.lastResult.TestSVo.all}" width="100%" height="195" change="Alert.show(DataGrid(event.currentTarget).selectedItem.other)"> <mx:columns> <mx:DataGridColumn headerText="Id" dataField="id" /> <mx:DataGridColumn headerText="Nome" dataField="name" /> </mx:columns> </mx:DataGrid> <mx:Button label="List" click="list.send({v:v1.text,v1:v2.text})" /> </mx:Panel> </mx:Application> |
The interface with the java code is in the two mx:HTTPService lines, the first one invokes the URL with a GET request, calling the calculator method
Then the first button (Calculate) is activated, the fist service is called [calc.send(parameters)] and the text on the first label will be updated because of the value binding on the last result …
When the seccond button (List) fires a post [list.send(params)] and the data grid will process the returned XML and display the lines …
That is all!
Gotcha: if you call list.send without parameters the HTTPService tag fires a GET method, this is the reason for the unused parameters there, at least with Flex 2 this happened every time
This aproach is very simple and does not need much code from any side, but it does not looks like OOP very much …
And flex has a great remoting support, so what do you think agout creating a POJO in Java and use it as a “remote object” from your flex code?
Like the idea?
So let’s create the TestService bellow with the same two operations (calc and list)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import java.util.ArrayList; import java.util.List; public class TestService { public float calc(float v, float v1){ System.out.format("%f X %f\n", v, v1); return v * v1; } public List<TestVo> list(){ System.out.println("List called"); ArrayList<TestVo> res = new ArrayList<TestVo>(); for(int i=0;i<40;i++){ res.add(new TestVo(i,"Testé ã " + i)); } return res; } } |
And now, we do not need all that XML stuf, a plain old VO will do the JOB …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class TestVo { private String name; private int id; public TestVo(int id, String name) { this.id = id; this.name = name; } public String getName() { return name; } public int getId() { return id; } public void setName(String name) { this.name = name; } public void setId(int id) { this.id = id; } } |
To use this service from Flash, we need to configure the OpenAMF framework using the file WEB-INF/openamf-config.xml, the name and location of this file was configured as a parameter to the AdvancedGateway in web.xml file …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?xml version="1.0" encoding="UTF-8"?> <config> <amf-serializer> <force-lower-case-keys>false</force-lower-case-keys> </amf-serializer> <invoker> <name>Java</name> <class>org.openamf.invoker.JavaServiceInvoker</class> </invoker> <custom-class-mapping> <java-class>....TestVo</java-class> <custom-class>TestVo</custom-class> </custom-class-mapping> <service> <name>TestService</name> <service-location>....TestService</service-location> <invoker-ref>Java</invoker-ref> <method> <name>calc</name> <parameter> <type>*</type> </parameter> </method> <method> <name>list</name> <parameter> <type>*</type> </parameter> </method> </service> </config> |
I have removed almost all the code from this file
There is the Serializer/Deserializer configured, the only registered invoker is the Java invoker, there is one custom-class-mapping for the VO and a Service definition for the service.
If you do not want to declare all methods for each service, you can use * as the name, and it will match any method …
The OpenAMF frameworks has many more configuration options, you can call EJBs, WebServices, any java class, JMX beans and Spring Beans from your flex code without any problem …
We can not use the standard mx:RemoteObject to call this service, because OpenAMF does not support AMF3, only AMF0 and I do not know how to configure the AMF protocol version for the mx:RemoteObject tag …
So the first example, uses only a NetConnection instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" backgroundColor="#F4F4F4" initialize="init()"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.rpc.events.ResultEvent; import mx.collections.ArrayCollection; [Bindable] public var lista:ArrayCollection; public var nc : NetConnection; function init() : void{ nc = new NetConnection(); nc.objectEncoding = ObjectEncoding.AMF3; nc.addEventListener(NetStatusEvent.NET_STATUS,netStatus); nc.connect("http://localhost:8080/teste/gateway2"); } function netStatus(event : NetStatusEvent) : void { Alert.show(event.info.code); } function onRetornaLista(event):void{ var e : Array = event; lista = new ArrayCollection(e); } function onRetornaCalc(event):void{ lbl.text = String(event); } function calc(v1 : Number, v2 : Number) : void { var r : Responder = new Responder(onRetornaCalc); nc.call("TestService.calc",r,v1,v2); } function list() : void{ var r : Responder = new Responder(onRetornaLista); nc.call("TestService.list",r); } ]]> </mx:Script> <mx:Panel width="80%" height="531" title="Teste"> <mx:Label width="100%" color="blue" text="Type two numbers and press calculate." /> <mx:TextInput id="v1" text="3" /> <mx:TextInput id="v2" text="5" /> <mx:Label id="lbl" /> <mx:Button label="Calculate" click="calc(Number(v1.text),Number(v2.text))" /> <mx:DataGrid dataProvider="{lista}" width="100%" height="195"> <mx:columns> <mx:DataGridColumn headerText="Id" dataField="id" /> <mx:DataGridColumn headerText="Nome" dataField="name" /> </mx:columns> </mx:DataGrid> <mx:Button label="List" click="list()" /> </mx:Panel> </mx:Application> |
the init method just initialized the NetConnection object (nc).
When the first button is called (Calculate) the calc method is called, in this method we call the TestService.calc method in the server, using the NetConnection object.
The new thing here is that we need the Responders to process the result from the server call …
I think this approach is better than the last one, but still not looking very good for me.
So I looked a little around and found the RemoteObjectAMF0 library …
It is a little ActionScript library, that enables you to use tags to call your service from flex using the AMF0 protocol like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:renaun="com.renaun.rpc.*" layout="vertical" backgroundColor="#F4F4F4"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.rpc.events.ResultEvent; import mx.collections.ArrayCollection; [Bindable] public var lista:ArrayCollection; NetConnection.defaultObjectEncoding = ObjectEncoding.AMF0; function onRetornaLista(event:ResultEvent):void{ lista = ArrayCollection(event.result); } function onRetornaCalc(event:ResultEvent):void{ lbl.text = String(event.result); } ]]> </mx:Script> <renaun:RemoteObjectAMF0 endpoint="http://localhost:8080/teste/gateway2" id="TST" source="....TestService" showBusyCursor="true" makeObjectsBindable="true" fault="Alert.show(String(event.fault)), 'Error'"> <renaun:methods> <renaun:method name="list" result="onRetornaLista(event)"></renaun:method> <renaun:method name="calc" result="onRetornaCalc(event)"></renaun:method> </renaun:methods> </renaun:RemoteObjectAMF0> <mx:Panel width="80%" height="531" title="Teste"> <mx:Label width="100%" color="blue" text="Type two numbers and press calculate." /> <mx:TextInput id="v1" text="3" /> <mx:TextInput id="v2" text="5" /> <mx:Label id="lbl" /> <mx:Button label="Calculate" click="TST.calc(Number(v1.text),Number(v2.text))" /> <mx:DataGrid dataProvider="{lista}" width="100%" height="195"> <mx:columns> <mx:DataGridColumn headerText="Id" dataField="id" /> <mx:DataGridColumn headerText="Nome" dataField="name" /> </mx:columns> </mx:DataGrid> <mx:Button label="List" click="TST.list()" /> </mx:Panel> </mx:Application> |
Still not perfect, since I did not liked the idea of using the full file name to call a service, I think my final solution will be to create an dynamic proxy for the services, but this last one looks good enought for me
I hope this post helps some one, I spend two days looking for solutions for this problem, because the current project does not have enought cash for a Life Cycle Server license …
PS.: to compile the mxml files you can use the mxmlc command from the free flex SDK
PS2.: you need to fix the URLs to the full path for your context, and your flex app must be downloaded from the same domain
PS3.: the é and ã letters are not encoding problems, I put it there to test if there would be any encoding problems and there were none
If you enjoyed this post, make sure you subscribe to my RSS feed!
Versão em portugues aqui
Before we start with the chat, you need to have the requisites installed, juggernaut need the gems json and eventmachine installed, so, run the following command before reading the rest of this example …
Ok, now we are ready to go!
First of all, create a rails application, and install the Juggernaut plugin with this commands:
Juggernaut uses an external Flash XML Push Server to do the reverse ajax magic, and we’ll need to configure that server, so, let’s edit the file: config/juggernaut.yml
I’ve put in the snipet bellow just the lines I changed in the file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | PUSH_PORT: 8080 ... DEFAULT_CHANNELS: - "chat" ... PUSH_HELPER_HOST: "localhost" ... SECRET: "481516232342edededededed" ... LOGIN_GET_URL: "http://localhost:3000/session/login" LOGOUT_GET_URL: "http://localhost:3000/session/logout" ... SESSION_ID: "_chattest_session_id" ... BASE64: true |
I had to change the PUSH_PORT because I’m using a linux box and the application does not run as root, so I was not able to use the default 443 port, and I do not think that 443 is a good port choice because it is the default HTTPS port.
Make sure you change the PUSH_HELPER_HOST to the same host name as the one you are using to access the application, localhost will do the job in the development environment, but remember to change it when you publish your site in a production environment.
the LOGIN_GET_URL and LOGOUT_GET_URL are used to notify the application about clients arriving and leaving, we will really use only the leaving notification.
the SESSION_ID must be the same as the defined cookie name for your application, it is defined in the application controller for rails 1.2.x and will be moved to environment.rb for rails 2.0
and BASE64 must be set to true if we want to use the rails helpers to generate the javascript for us.
Now let’s start the layout for the application. Create a file named pubic/stylesheets/public.css with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | body { background-color: white; } #users { float: left; width: 200px; height: 400px; border-style: inset; overflow: auto; color: white; background-color: gray; } #dasd { height: 400px; margin-left: 5px; border-style: inset; overflow: auto; color: white; background-color: gray; } #controls { clear: both; padding: 0 0 0 0; height: 55px; vertical-align: top; border-style: inset; overflow: auto; color: white; background-color: gray; } |
and a file named: app/views/layouts/application.rhtml with the following content.
1 2 3 4 5 6 7 8 9 10 | <html> <head> <title>Chat Test</title> <%= stylesheet_link_tag 'public' %> <%= javascript_include_tag :defaults %> </head> <body> <%= yield %> </body> </html> |
in this file it is important to add the stylesheet and the default javascript includes.
With the layout ready (ok, I know it is pretty ugly, but I’m a developer not a webdesigner so, for production, ask a designer in your team for a new layout
) let’s generate the needed files and database tables with the following four commands.
Every thing ready, we just need to edit some files …
Open the OnlineUser model (app/model/online_user.rb) and change the content to something like the following
1 2 3 4 | class OnlineUser < ActiveRecord::Base validates_presence_of :username, :session_id, :last_seen validates_uniqueness_of :username, :if => Proc.new {|user| user.online } end |
It is just a few validations, not really needed, this was my first idea for the chat, I’ve changed it a little but still works.
Now let’s code the main view of the application in the file:app/views/chat/index.rhtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!-- Register with Juggernault --> <%= listen_to_juggernaut_channels [:generic],session.session_id %> <!-- The Users List --> <div id="users"> <ul id="users_list"></ul> </div> <!-- The messages pane --> <div id="dasd"></div> <!-- The controls pane (login and send messages) --> <div id="controls"><%= render :partial => 'login' %></div> <!-- An util javascript to scroll the messages window --> <script type="text/javascript"> function scrollMessages(){ $('dasd').scrollTop = $('dasd').scrollHeight; } </script> |
That is all, just tree DIVs, the tag to initialize juggernaut and a simple javascript to scroll the messages DIV to the last sent message.
the messages DIV is named dasd because I was testing some conflicts and forgot to change it back
As seen in the page above, we need a login partial, and we’ll need a controls partial too, so let’s code the controls partial (app/views/chat/_controls.rhtml) with the following code:
1 2 3 4 5 6 | <% form_remote_tag( :url => { :action => :say }, :complete => "$('message').value = '';$('message').focus();" ) do %> <%= text_field_tag( 'message', '', { :size => 90, :id => 'message'} ) %> <%= submit_tag "Send" %> <% end %> |
It has only a remote form tag and two fields, after the form is submited the message field is cleared and the focus os placed back in that field so the user can type another message.
And the login partial (app/views/chat/_login.rhtml):
1 2 3 4 5 6 7 | <%= "#{@message}<br/>" if @message %><% form_remote_tag(
:url => { :action => :login },
:complete => "$('username').value = ''",
:after => "$('login').disabled = true" ) do %>
<%= text_field_tag( 'username', '', { :size => 90, :id => 'username'} ) %>
<%= submit_tag "Join", :id => 'login' %>
<% end %> |
Very similar to the controls partial, but it shows a message to the user if the chosen nick name is already taken.
the views are all set, and now we need the application logic, as seen in the views, we need a chat controller with two methods: login and say
Let’s take a look at the chat controller (app/controllers/chat_controller.rb):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | class ChatController < ApplicationController #this method does not need to exist, but I like to see it here, it only needs to render the index.rhtml view def index end def login #creates a new OnlineUser record, this is used to store who are the users that are online now @user = OnlineUser.new @user.username = Juggernaut.html_and_string_escape params[:username] @user.session_id = session.session_id @user.online = true @user.last_seen = Time.now #if we can save, it means that there is no other user with the same nick online, so this user can join the chat if @user.save #let's save the username in the session for future reference session[:username] = @user.username #if there are online users, fill the users box for the new user know who is online @users = OnlineUser.find(:all, :conditions => ["online = true and id != ?", @user.id]) if @users.size >0 data = render_to_string(:update) do |page| @users.each {|u| page.insert_html :bottom, :users_list, %Q{<li id="user_#{u.username}">#{u.username}</li>} } end #send the javascript only to the new user Juggernaut.send_to(@user.session_id, data) end #create a javascript call to add the new user to the end of the online users list data = render_to_string(:update) do |page| page.insert_html :bottom, :users_list, %Q{<li id="user_#{@user.username}">#{@user.username}</li>} page.insert_html :bottom, :dasd, "<b>user #{@user.username} just joined the chat</b><br/>" end #add the new user to the chat channel Juggernaut.add_channel(@user.session_id, 'chat') #send the javascript to all users in the chat channel Juggernaut.send_data(data, 'chat') render(:update) do |page| page.replace_html 'controls', :partial => "controls" end else @message = 'This nick name is already in use, please choose another' render(:update) do |page| page.replace_html 'controls', :partial => "login" end end end def say #escape the message, that way the user can not harm others sending HTML ot JavaScript commands message = "#{session[:username]}: #{Juggernaut.html_and_string_escape(params[:message])}" #create a javascript to add the new message to the end of the messages screen and scroll the div to the bottom data = render_to_string(:update) do |page| page.insert_html :bottom, :dasd, "#{message}<br/>" page.call "scrollMessages" end #send the message to all users Juggernaut.send_data(data, 'chat') render :nothing => true end end |
The method say is really simple, so we’ll start explaining it:
It first build a new message appending the escaped original message to the user name, then it creates the javascript to add the message to the bottom of the messages div using the Rails JavaScriptBuilder and the render_to_string method, that returns a string instead of rendering the code directly to the client.
then it sends the message to all users subscribed to the “chat” channel, and all the users will execute that javascript.
Now a little about the login method:
This is almost all logic needed for this chat application, the only missing thing is removing the nick of users that are no more online from the users DIV from the other users, and we’ll do that in the session controller (app/controllers/session_controller.rb)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class SessionController < ApplicationController #Called when a user disconnect (a refresh in the browser causes this to be called too) def logout #search for the user record using the session_id @u = OnlineUser.find_by_session_id(session.session_id) reset_session #if a user was found if @u username = @u.username #remove it from the database @u.destroy #remove from the online users list from all users, and tell others this user left the chat data = render_to_string(:update) do |page| page.remove "user_#{username}" page.insert_html :bottom, :dasd, "<b>User #{username} left the chat</b><br/>" end Juggernaut.send_data(data,'chat') end render :nothing => true end def login render :nothing => true end end |
The login method does nothing, but we have some code in the logout method …
That is all folks, we just need to run the application
to run this application we need to start the rails application server as usual, and then start the push server.
to do this, just run the following two commands:
and access your newly build chat with the URL: http://localhost:3000/chat and play a little around.
I’ve built this example while studding the juggernaut lib, so it is possible that there is a easy way to do this, but I think this is a good start point ![]()
The start idea was not to use a database, but I could not find anything like the ServletContext in java for rails (an application context), I’ll try to use ENV to store the online user names, but I could not make it work until now.
Any tips for improving this example will be very welcome.
If you enjoyed this post, make sure you subscribe to my RSS feed!
Tags: ajax, chat, example, flash, howto, push, rails, reverse, ruby, simple, xml
Following the very simple example line, as in the “login example with Rails“, now we’ll see a very simple login example with Java Server Faces.
I’m writing this sequence of short howto posts because in the forums I read one of the recurrent question from the beginners is “how to implement a login with X”, so, let’s go to the JSF login example.
Of course there are many ways to implement a login in a JSF aplication, you can use JAAS, you can use a Servlet Filter, but this one I think is one of the best approaches, of course, in a real application, I usually combine it with some AOP and annotations, but AOP and annotations are out of the scope of this tutorial …
First, write a web.xml for your application, with the faces servlet in it …
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.jsf</url-pattern> </servlet-mapping> <error-page> <exception-type>java.lang.SecurityException</exception-type> <location>/login.jsf</location> </error-page> </web-app>
In this web.xml we are using the servlets 2.5 specification, and I’m using JSF 1.2, for this example you can use other versions without problem.
The servlet container is configured to show a login page if at any time the application throws a java.lang.SecurityException, this is a important point for the example.
And now a backing bean for the application, here is where we’ll validate the user’s login, in this example there is no need for database access, but in a real application you’ll search this data in your user repository (database, ldap, …)
package br.com.urubatan.jsfjpasec; public class Login { private boolean loginOk; private String userName; private String password; public boolean isLoginOk() { return loginOk; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String validateLogin(){ if(userName!=null && password!=null && !userName.equalsIgnoreCase(password)){ loginOk = true; return "secpage"; }else return "login"; } }
It is a very simple backing bean, with only 3 properties:
name and password for the user, and a property telling the application that a user has already logged in.
probably this last property will be replaced by a list of groups filled by the validateLogin method …
Now a bean with some static data for the example:
package br.com.urubatan.jsfjpasec; import java.util.List; import java.util.ArrayList; public class SomeData { private List<String> data = new ArrayList<String>(); private List<String> securedData = new ArrayList<String>(); private boolean loginOk; public SomeData() { for(int i=0;i<10;i++){ data.add("Simple data " + i); securedData.add("Secure data " + i); } } public void setLoginOk(boolean loginOk) { this.loginOk = loginOk; } public List<String> getSecuredData() { if(!loginOk) throw new SecurityException(); return securedData; } public List<String> getData() { return data; } }
This one has only the getters for two properties, and in the secureData property, if there is no logged in user, the application throws a java.lang.SecurityException, this will redirect the user to the login page.
Now some XML tricks in the faces-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?xml version='1.0' encoding='UTF-8'?> <faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd" version="1.2"> <managed-bean> <managed-bean-name>login</managed-bean-name> <managed-bean-class>br.com.urubatan.jsfjpasec.Login</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <managed-bean> <managed-bean-name>mdata</managed-bean-name> <managed-bean-class>br.com.urubatan.jsfjpasec.SomeData</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>loginOk</property-name> <property-class>java.lang.Boolean</property-class> <value>#{login.loginOk}</value> </managed-property> </managed-bean> <navigation-rule> <from-view-id>/login.jsp</from-view-id> <navigation-case> <from-outcome>login</from-outcome> <to-view-id>/login.jsp</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>secpage</from-outcome> <to-view-id>/secureView.jsp</to-view-id> <redirect/> </navigation-case> </navigation-rule> </faces-config> |
In this file we have two navigation cases, if “login” is returned from a backing bean, it shows the login page, if “secpage” is returned, the secureView.jsp page is shown …
The first lines are used to declare the backing beans, pay attention to the line 15, where we are reading the property “loginOk” from the login bean.
The login bean is session scoped, and the mdata is request scoped.
With this written we have all the needed logic for this application, the only missing part is the “view”, or the JSP files …
So, let’s write them …
login.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %> <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %> <html> <head><title>System Login</title></head> <body> <f:view> <h:form> <h:panelGrid columns="2"> <h:outputLabel value="User Name" for="un"/> <h:inputText id="un" value="#{login.userName}"/> <h:outputLabel value="Password" for="pw"/> <h:inputText id="pw" value="#{login.password}"/> </h:panelGrid> <h:commandButton value="Login" action="#{login.validateLogin}"/> </h:form> </f:view> </body> </html>
This is only a simple JSF page with two fields and a commandButton …
dataView.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %> <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %> <html> <head><title>Unsecured Data Page</title></head> <body> <f:view> <h:dataTable value="#{mdata.data}" var="v"> <h:column> <f:facet name="header"> <h:outputText value="Data List"/> </f:facet> <h:outputText value="#{v}"/> </h:column> </h:dataTable> <h:panelGrid columns="3"> <h:outputLink value="dataView.jsf"> <h:outputText value="Data that every one can access"/> </h:outputLink> <h:outputLink value="secureView.jsf"> <h:outputText value="Data that you can view after login"/> </h:outputLink> <h:outputLink value="login.jsf"> <h:outputText value="Login"/> </h:outputLink> </h:panelGrid> </f:view> </body> </html>
This is simple page with a dataTable rendering the “data” property from “mdata” bean and two links.
secureView.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %> <%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %> <html> <head><title>Secured Data Page</title></head> <body> <f:view> <h:dataTable value="#{mdata.securedData}" var="v"> <h:column> <f:facet name="header"> <h:outputText value="Data List"/> </f:facet> <h:outputText value="#{v}"/> </h:column> </h:dataTable> <h:panelGrid columns="3"> <h:outputLink value="dataView.jsf"> <h:outputText value="Data that every one can access"/> </h:outputLink> <h:outputLink value="secureView.jsf"> <h:outputText value="Data that you can view after login"/> </h:outputLink> <h:outputLink value="login.jsf"> <h:outputText value="Login"/> </h:outputLink> </h:panelGrid> </f:view> </body> </html>
And this is almost a copy from the previous page, but now reading the “secureData” property from “mdata” bean.
And just to avoid the “404″ error when running the application, an “index.jsp” with a link to “dataView.jsf”
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>Entry Page</title></head> <body>Entry Page, this could redirect to the JSF Index, but for now, click here: <a href="dataView.jsf">JSF Index</a> </body> </html>
That is all folks!
to run this application you need the following jar files: jstl.jar, jsf-api.jar e jsf-impl.jar
They can be found in the JSF-RI distribution, and are already present in containers with JSF support.
And now, some questions:
If you enjoyed this post, make sure you subscribe to my RSS feed!
The Ruby language, started to grow inside the enterprises and to be “the topic” in many blogs after the Rails framework showed up, but Rails is not the only option for developing web applications with Ruby, there are other frameworks, and one of “the others” is called “Nitro Framework“, thsi one has almost the same age as Rails but it has a lot less documentation and AFAIK a lot less users too.
This post is about my first 30 minutes with Nitro.
One of the better things about rails, and one of the things that you can not do with Nitro is just to change a class while the server is running, and see the change after a refresh in the browser window, but I’ll talk about these problems later …
The Nitro framework, goes through a path different from Rails, while Rails tell you where to put your models, where to put your controllers, where to put your views, …
What I think is a great feature, since when you start working in a new project you already know where every thing is, Nitro let you put your stuff just where you want it to be.
What is a nice feature too, but if I want to put things where I want them to be, I’ll use Java as I’m doing for the last 5 years
Nitro is almost a two framework in one, with Nitro you can develop MVC style applications like Rails, but you can write page oriented applications like in PHP or ASP too, and the best thing is that you can mix the two styles too, of course it will increase the complexity of your application, but it can make it a lot easier to work too, you can write what you need using the easier way to accomplish that task.
To start playing with Nitro just run the following commands:
Every thing ready, you are running your first Nitro application!
Until now, I think that nitro is more complicated than Rails, probably the lack of documentation contributed a lot to this, the terrible examples have their share in this responsibility too, but Nitro has some cool things too.
If you want to create a page based application, you just need to create some “.xhtml” files and place your Ruby code between <?r and ?>, or if you just want to display some string, do it just like in any Ruby String, placing your code between #{ … }.
Since nitro does not have a fixed directory structure, you need to “require” some of your Ruby files from inside the “run.rb” code, I think that this is the cause of the lack of automatic reloading of your code.
Nitro uses Og for persistence, and Og does not need your persistent objects to extend any class, it will persist any object that uses any of it’s “property definition helpers”, itlooks for an automatic created method called “serializable_attributes”, I think it is more the “ruby way”, since it uses “Duck Typing” instead of hierarchy.
Nitro does not have any thing like “migrations”, at least I did not find it yet, and I really like Rails Migrations.
I found the source code for Nitro and Og easier to read and understand than the Rails code, but the code for the Rails applications is a lot cleaner and easier to read.
One thing that I found cool about nitro is that they use the explicit parameters for almost all methods, when rails use hashes for almost every thing, both approaches have advantages …
The example I wanted to write for this post will be published another time, because I need to study a little more about Nitro and Og to develop any thing that I’m not ashamed of publishing.
They do not follow a standard directory structure even in the sample applications, in some of them the code is in the source directory, in others it is in the app directory, the only standard is that the public things (style sheets, images, …) are in a directory called public.
For now, they (Nitro and Og) looks like they need more work, they stopped in time for a while, and the development restarted now with the “Rails Boom“.
Nitro has some cool things, but at least for now, I’ll use Rails for my projects (at least the ones that aren’t in Java)
If you enjoyed this post, make sure you subscribe to my RSS feed!
This is just a little step by step on how to create a simple login with Rails, of course you can use a plugin to do it for you, but this way you can understand what the plugin is doing.
Let’s start creating a new Rails application:
$rails applicationNameThen we create a controller with a view for the login:
$script/generate controller login loginThe code for the login_controller.rb will be the following:
class LoginController < ApplicationController def index render :action => 'login' end def login end def do_login username = params[:username] password = params[:password] if username.nil? || password.nil? || username==password redirect_to :action => "login" flash[:notice] = 'Unknown user or invalid password' else session["user_id"] = username redirect_to :controller => "secure", :action => "index" end end end
Basically the controller has to implement only the do_login method, this is the one that will authenticate the user, in this example it only validates if the user filled the username and password fields, but later you can search the database for it.
The login view will have the following code:
<% form_tag :action => 'do_login' do %> <table class="loginForm" align="center"><tbody> <tr> <td>Login</td> <td><input name="username" type="text" /></td> </tr> <tr> <td>Password</td> <td><input name="password" type="password" /></td> </tr> <tr> <td colspan="2"><%= submit_tag "Login" %></td> </tr> </tbody></table> <table><% end %>
It is a simple view, with only two fields and since we do not have a user model I did not used the rails form helpers …
Ok, perfect until now, but how do I use it?
let’s change the application.rb file, the code will be the following:
class ApplicationController < ActionController::Base # Pick a unique cookie name to distinguish our session data from others' session :session_key => '_untitled6_session_id' before_filter :authorize protected # Override in controller classes that should require authentication def secure? false end private def authorize if secure? && session["user_id"].nil? session["return_to"] = request.request_uri redirect_to :controller => "login", :action => "login" return false end end end
The code above adds a before filter to every method of every controller in your application (since all controllers extends ApplicationController), this method will intercept the calls and if the controller is “secure” and the user did not authenticated yet, the user will be redirected to the login page, otherwise the filter will do nothing. To secure a controller you just need to override the “secure?” method and return true.
Now let’s create a simple controller that will need authentication …
$script/generate controller secure indexThis line above creates a new controller with only an index action.
Now let’s edit the secure_controller.rb file
class SegureController < ApplicationController def index end protected def secure? true end end
Overriding the “secure?” method to return true we tell the application that all actions in this controller need authentication.
But if we and only a few actions to need authentication? Than we can use some thing like the code bellow!
protected def secure? ["secureMethod","anotherSecureMethod"].include?(action_name) end
This way only the “secureMethod” and the “anotherSecureMethod” will need authentication.
Every thing ready, now we have a login implemented in a rails application …
Try starting the server (ruby script/server from the application’s directory) and access http://localhost:3000/secure and you will see the login screen instead of the index.rhtml contents.
Of course we can improve this example in many ways, for example:
And you, how would you improve the example above?
Do you see any problem in this example?
If you enjoyed this post, make sure you subscribe to my RSS feed!