ruby项目如何配置RVM自动切换ruby-version/gemset

RVM的wiki上有好几种指定ruby-version/gemset的方法。我推荐的是使用两个配置文件.ruby-version,.ruby-gemset分别指定ruby的版本和gemset。这种方式的优点是安全,方便,详细的解释请见参考链接。

在项目根目录下,新建下面两个文件。
.ruby-version:

1.9.3-p448 #指定项目的ruby版本

.ruby-gemset:

your_project_gemset #指定项目的gemset

配置好之后,cd到项目的根目录下,如果未安装该版本的ruby,命令行会有提示信息。如果没有指定的gemset,会新建gemset。

给不同的项目设置单独的gemset,能避免版本冲突,运行ruby命令时可以放心的省掉‘bundle exec’。

参考链接:

在heroku上部署spree电商平台完整过程

1.软件依赖

  • rvm-1.18.2
  • ruby-1.9.3
  • rails-2.3.11
  • postgresql-9.1
  • ubuntu-12.10
  • heroku-toolbelt

2.搭建本地RUBY ON RAILS环境

2.1安装RVM

我的开发环境是一台64位UBUNTU-12.10,选择用RVM管理RUBY版本。
运行下面的命令,安装RVM:

hao@ubuntu:~$ sudo apt-get install curl
hao@ubuntu:~$ curl -L https://get.rvm.io | bash -s stable --ruby

然后查看RVM的依赖包,并根据提示安装:

hao@ubuntu:~$ rvm requirements

....
# For ruby:
sudo apt-get --no-install-recommends install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev libgdbm-dev ncurses-dev automake libtool bison subversion pkg-config libffi-dev
...

2.2安装RUBY/RAILS

安装ruby、新建gemset(@spree),并把rails安装到@spree:

hao@ubuntu:~$ rvm install ruby
hao@ubuntu:~$ rvm gemset create spree
hao@ubuntu:~$ rvm use @spree
hao@ubuntu:~$ gem install rails bundler

3.安装POSTGRESQL数据库

HEROKU有免费的POSTGRESQL数据库,建议研发环境和生产环境保持一致。必须要安装libpq-dev,这样ruby的pg插件才能编译。

hao@ubuntu:~$ sudo apt-get install postgresql libpq-dev

安装完启动数据库,并根据需要配置好用户:

hao@ubuntu:~$ sudo /etc/init.d/postgresql restart
 * Restarting PostgreSQL 9.1 database server                             [ OK ]

4.新建一个spree工程

4.1新建一个rails工程

新建一个rails工程,使用POSTGRESQL数据库:

hao@ubuntu:~$ rvm use @spree | rails new mystore -d postgresql

注意做下面几个修改:

  • 修改Gemfile,添加上“gem ‘therubyracer’, :platforms => :ruby”(把原来的注释移除)
  • 修改conf/database.yml

4.2安装spree

安装spree,注意“spree install”必须在rails工程目录下运行:

hao@ubuntu:~$ gem install spree
hao@ubuntu:~$ cd mystore
hao@ubuntu:~/mystore$ spree install

安装过程中遇到问题的解决办法:

  • 安装过程中提示你添加devise-encryptable到Gemfile,注意要放到Gemfile的末尾
  • 安装rmagick时提示编译不成功,先执行“sudo apt-get install imagemagick libmagickwand-dev”。

安装完后,先在本地启动服务,体验下:

hao@ubuntu:~/mystore$ rails s
=> Booting WEBrick
=> Rails 3.2.11 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server

5.把工程部署到HEROKU

5.1注册账号并部署sample工程

参见HEROKU官方快速入门
如果需要生成RSA证书,可以使用:

hao@ubuntu:~$ ssh-keygen -t rsa

如果使用apt安装heroku toolbelt时需要使用代理,可以修改/etc/apt/apt.conf

Acquire::http::proxy "http://ip:port/";
Acquire::ftp::proxy "ftp://ip:port/";
Acquire::https::proxy "https://ip:port/";
Acquire::socks::proxy "socks://ip:port/";

5.2把mystore发布到HEROKU

在mystroe工程目录下执行下面的命令:

hao@ubuntu:~/mystore$ git init | git add . | git commit -m 'init'
hao@ubuntu:~/mystore$ heroku create
Creating severe-mountain-793... done, stack is cedar
http://severe-mountain-793.herokuapp.com/ | git@heroku.com:severe-mountain-793.git
Git remote heroku added
hao@ubuntu:~/mystore$ git push heroku master

上面的操作完成后,访问HEROKU提供的URL,显示的是错误页面,这是因为HEROKU运行rake assets:precompile失败:

could not connect to server: Connection refused
Is the server running on host "127.0.0.1" and accepting
TCP/IP connections on port xxxx?

这是因为spree工程运行”rake assets:precompile”时需要访问数据库,而HEROKU在做预处理时,没有数据库环境,所以我们可以部署之后再运行assets:precompile,如下:

hao@ubuntu:~/mystore$ heroku run env RAILS_ENV=production DATABASE_URL=postgres://xxxx:xxxxx@xxxx.amazonaws.com:5432/xxxx rake assets:precompile 2>&1

需要解释的就是DATABASE_URL后面的数据库连接串需要从HEROKU控制台去查看。

6.验证

登陆HEROKU控制台,找到我们的APP连接,访问。

参考链接:

使用jaxb注解的小技巧-给xml结构增加封装

1.使用maven引入jaxb依赖

jdk1.6以上自带了jaxb,但我还是推荐显式指定依赖:

<dependency>
  <groupid>javax.xml.bind</groupid>
  <artifactid>jaxb-api</artifactid>
  <version>2.2.7</version>
</dependency>

2.构造一个封装类

下面的类Wrapper,使用Wrapper.wrapper(yourObject),就能把yourObject的结构封装在<root><yourObject></yourObject></root>标签里。

@XmlRootElement(name = "root")
public class Wrapper {

	public static Wrapper wrapper(Object object) {
		Wrapper wrapper = new Wrapper();
		wrapper.addElement(object);
		return wrapper;
	}

	@XmlAnyElement
	private List<Object> root;

	private Wrapper() {
		root = new ArrayList<Object>();
	}

	public void addElement(Object object) {
		root.add(object);
	}
}

如果需要修改外层的封装标签,修改@XmlRootElement(name = “your_tag”)即可。

如何配置spring web-mvc返回资源的xml/json等不同视图

1.首先,列出两个很有用的资源:

  1. Spring 3 MVC ContentNegotiatingViewResolver Example

  2. Spring REST 3 to Support XML and JSON

我按照链接1说明下载了示例代码,根据链接2解决了示例代码中的问题。

2.spring web-mvc是怎样区分不同的视图请求(xml or json or ?)

spring web-mvc支持给资源返回不同形式的视图,现在有两种方式指定返回的视图类型:

a.通过URL后缀指定视图类型

  • http://sample.com/resource.xml 返回资源的xml视图
  • http://sample.com/resource.json 返回资源的json视图

b.通过在HTTP请求头的Accept参数里列出期望的资源类型(media-types)

  • Accept:application/xml 指定返回xml视图
  • Accept:application/json 指定返回json视图

3.配置ContentNegotiatingViewResolver来支持返回多种视图

下面是配置ContentNegotiatingViewResolver的一段spring配置文件:

<bean
  class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
  p:order="1">
  <property name="mediaTypes">
    <map>
      <entry key="xml" value="application/xml" />
      <entry key="json" value="application/json" />
    </map>
  </property>
  <property name="defaultViews">
    <list>
      <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
        <property name="marshaller">
          <bean class="org.springframework.oxm.xstream.XStreamMarshaller"
            p:autodetectAnnotations="true" />
        </property>
      </bean>
      <bean
        class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
    </list>
  </property>
</bean>

这段配置需要说明的有:

  1. 指定”后缀-资源类型”映射:<entry key=”xml” value=”application/xml” />,这样通过后缀就能对应到资源类型。
  2. 提供处理xml/json资源类型的bean,这里用来处理xml视图的bean是org.springframework.web.servlet.view.xml.MarshallingView,处理json视图的bean是
    org.springframework.web.servlet.view.json.MappingJacksonJsonView。

4.实现一个简单的Controller

class Somebody {
  private String name;
  private boolean isMale;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public boolean isMale() {
    return isMale;
  }

  public void setMale(boolean isMale) {
    this.isMale = isMale;
  }
}

@Controller
@RequestMapping("/hi")
public class Hi {
  @RequestMapping(value = "{name}", method = RequestMethod.GET)
  public @ModelAttribute
  Somebody getShopInJSON(@PathVariable String name) {
    Somebody somebody = new Somebody();
    somebody.setName(name);
    somebody.setMale(true);
    return somebody;
  }
}

注意!这里返回的对象,用的是@ModelAttribute注解。

5.部署和测试

把工程打成war包,部署到web容器下测试,访问http://host:port/hi/bob.json或是http://host:port/hi/bob.xml。

列下我的pom.xml的依赖:

...
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>3.1.2.RELEASE</version>
    </dependency>

    <!-- for xml -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>3.1.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.4.3</version>
    </dependency>

    <!-- for json -->
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>1.9.10</version>
    </dependency>

  </dependencies>
</project>

最后,感谢…!

使用maven WSDL2Code插件生成webservice代码

首先,配置我们的项目使用wsdl2code插件。

先看看pom.xml配置:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sample</groupId>
  <artifactId>wesd2code-demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.axis2</groupId>
        <artifactId>axis2-wsdl2code-maven-plugin</artifactId>
        <version>1.6.2</version>
        <configuration>
          <packageName>com.sample</packageName>
          <wsdlFile>src/main/wsdl/webservice.wsdl</wsdlFile>
          <generateTestcase>true</generateTestcase>
          <syncMode>sync</syncMode>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

这里给大家解释下上面的wsdl2code插件配置

  1. packageName:生成的源码会放到指定的package里
  2. wsdlFile:webservice定义文件的路径,可以是相对于pom.xml所在目录的相对路径
  3. generateTestcase:是否生成单元测试
  4. syncMode:生成同步还是异步接口,有三种可选值:sync, async或者both,其中both是默认选项
  5. generateServerSide:默认是false,这里没有配置,所以是只生成客户端代码
  6. outputDirectory:默认是target/generated-sources/axis2/wsdl2code

然后,我们可以运行mvn命令生成代码:

mvn clean axis2-wsdl2code:wsdl2code

最后,给生成的代码添加依赖:

  <dependencies>
    <dependency>
      <groupId>org.apache.axis2</groupId>
      <artifactId>axis2-integration</artifactId>
      <version>1.6.2</version>
    </dependency>
  </dependencies>

Maven使用体会及一些最佳实践

maven官网的介绍太简略(Run Maven),对于一些初次接触maven的用户来说,实在是让人困惑。
本文主要介绍了maven的一些常见用法,大家籍着了解一些maven的功能。

1.使用maven标准的目录结构来组织项目

maven引入了convention over configuration(约定优先配置)的原则,如果你的项目是按照maven标准的目录结构来组织,很多时候都不用写自己的配置。

2.明确指定字符编码

特别是对于需要使用非ASCII字符的开发者来说,字符编码是很严肃的一件事。
建议在你的pom.xml里添加下面的配置:

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

另外,单元测试时如果不明确指定字符编码,有可能输出乱码,甚至导致测试失败,建议配置:

<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.7.1</version>
  <configuration>
    <argLine>-Dfile.encoding=UTF-8</argLine>
  </configuration>
</plugin>

具体解释请参考maven-surefire-plugin乱码问题

待续…

使用ruby解析xml的简单例子

在使用hudson持续集成的工作中,我们会在构建完成后运行一个自动部署脚本。
自动部署脚本需要知道构建的模块信息,用ruby解析pom.xml是比较合适的选择。

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <artifactId>sample</artifactId>
        <version>0.1.9-SNAPSHOT</version>
...
</project>

ruby脚本片段如下:

#!/usr/bin/env ruby

require "rexml/document"
doc = REXML::Document.new(File.open('pom.xml'))

artifactId = doc.elements["project/artifactId"].text
version = doc.elements["project/version"].text

上面的脚本,使用rexml模块解析xml。
注意:如果版本号是通过继承得来(或是在profile里指定的),先使用maven生成实际生效的pom.xml才能获得正确的结果。
Tips:使用mvn help:effective-pom生成实际生效的pom。

Spring通过JNDI从Tomcat查询Datasource

首先,介绍下运行的软件环境:

  • tomcat 7.0
  • oracle 11.2
  • spring 3.0
  • c3p0 0.9.1

下面,我们开始配置。

1.准备好tomcat依赖的jar包
把下列的jar包拷贝到TOMCAT_HOME/lib/目录下(其他依赖可以放在web应用的lib目录下):

  • ojdbc6-11.2.0.jar: oracle jdbc驱动
  • c3p0-0.9.1.jar: c3p0连接池

2.在tomcat上下文里配置c3p0连接池
可以参考tomcat jndi datasource examples文档,和文档上不同的地方是这里使用了c3p0连接池。
在TOMCAT_HOME/conf/context.xml里添加下面的配置:

<Resource auth="Container"
          description="DB Connection"
          driverClass="oracle.jdbc.driver.OracleDriver"
          maxPoolSize="50"
          minPoolSize="3"
          acquireIncrement="3"
          name="jdbc/sample"
          user="user"
          password="password"
          factory="org.apache.naming.factory.BeanFactory"
          type="com.mchange.v2.c3p0.ComboPooledDataSource"
          jdbcUrl="jdbc:oracle:thin:@hostname:1521:sid" />

配置到这里,就可以使用JNDI查询到数据库连接池了。

3.在spring里注入数据源
使用JNDI查询数据源tomcat_DataSource:

<bean id="tomcat_DataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiName" value="java:comp/env/jdbc/sample" />
</bean>

现在,可以配置web应用使用tomcat_DataSource数据源了。

java读取classpath下资源的2种方法比较

A.使用ClassLoader.getResourceAsStream()读取资源:

@Test
public void test_ClassLoader_getResourceAsStream() {
	InputStream works = getClass().getClassLoader().getResourceAsStream(
			filename);
	assertNotNull(works);

	InputStream doesnt_work = getClass().getClassLoader()
			.getResourceAsStream("/" + filename);
	assertNull(doesnt_work);
}

B.使用spring上下文来加载资源:

@Test
public void test_ApplicationContext_getResource() {
	ApplicationContext ctx = new ClassPathXmlApplicationContext();
	// !!! dosen't work within tomcat
	Resource may_works = ctx.getResource("classpath:" + filename);
	assertTrue(may_works.exists());

	Resource always_works = ctx.getResource("classpath:/" + filename);
	assertTrue(always_works.exists());
}

下面来分析两种方法的适用情况:

方法A:
pros:不依赖额外的包,干净。
cons:资源和调用类必须由同一个(或有继承关系的)Classloader加载,否则找不到资源文件。例如在tomcat环境中,若资源文件和调用类的jar包分离,有可能出现找不到资源的情况。

方法B:
pros:只要在classpath下,就可以找到资源。
cons:需要依赖spring的包。

对于我来说,简单的应用,使用方法A就可以。
复杂些的应用,一般会使用spring,很多资源文件都使用依赖注入,使用方法B的情况很少。

在bash里使用PID文件

实践中,我们把一些任务放到后台运行,停止/重启任务时需要找到任务的PID(process Id)。
我们可以采用下面的方式:

$ ./task.sh &
$ ps -ef|grep task.sh
hao      18635 18496  0 22:20 pts/0    00:00:00 /bin/bash ./task.sh
hao      18673 18496  0 22:20 pts/0    00:00:00 grep task.sh

这种方式的不便之处在于不适合编写脚本。
更好的的方式是使用一个PID文件,记录下任务的PID,如下:

$ ./task.sh & echo $! > pid
$ cat pid
19219

需要杀死进程时,如下:

$ PID=`cat pid` && pkill -P $PID && kill $PID

下面是一个启动任务,然后关闭的例子:

#! /bin/bash

# 后台启动任务,把PID保存在文件pid里
# "1>/dev/null 2>&1"丢弃了标准输出和错误输出
nohup ./task.sh 1>/dev/null 2>&1 & echo $!>pid

# 打印服务状态
if ps -p `cat pid` >&-; then echo UP; else echo DOWN; fi

# 杀死任务的子进程及任务进程
PID=`cat pid` && pkill -P $PID && kill $PID

参考资料:
Write a PID file in bash
Best way to kill all child processes