Active Record Associations

为什么要关联?

为什么我们需要在两个model之间建立关联?因为它让通用操作变得简单和容易。例如,考虑有一个rails应用程序包含一个customer model和一个order model。每一个customer有很多的order。没有关联时,model声明如下:

class Customer < ActiveRecord::Base
end

class Order < ActiveRecord::Base
end

现在,假设我们想为一个已存在的客户添加一个新的订单,我们需要像这样做:

@order = Order.create(:order_date => Time.now, :customer_id => @customer.id)

再考虑在删除一个客户时,确保他的订单也被删除了。

@orders = Order.find_by_customer_id(@customer.id)

@orders.each do |order|
    order.destroy
end

@customer.destroy

使用Active Record关联,我们通过声明告诉Rails在这两个model之间存在的关联使这些及其他一些操作流线化,这里是建立customer model和order model的改进代码

class Customer < ActiveRecord::Base
  has_many :orders, :dependent => :destroy
end

class Order < ActiveRecord::Base
  belongs_to :customer
end

有了这些改变,很容易实现为一个特定的客户创建一个订单

@order = @customer.orders.create(:order_date => Time.now)

删除一个客户和它的订单则更加容易

@customer.destroy

Rails中的关联类型

在Rails中,关联是两个Active Record Model之间的连接,关联通过macro-style的调用来实现的,因此你可以声明来添加特性到你的model。例如,通过声明一个model belongs_to另一个,你的Rails指令去维护在两个model的实例之间的primay_key, foreign_key信息,然后你同时有许多有用的方法添加到了你的model中。Rails支持六种类型的关联:

  • belongs_to 从属关系
  • has_one 拥有(一对一)
  • has_many 拥有(一对多)
  • has_many :through 一对多,通过中间关联
  • has_one :through 一对一,通过中间关联
  • has_and_belongs_to_many 多对多

belongs_to关联

belongs_to关联与另一个model建立一对一联系,这样子声明的模型的每一个实例belongs_to其他模型的一个实例。例如,如果你的应用程序包含客户和订单,且每一个订单会被精确的分配给一个客户,你可像这样声明这个订单model:

class Order < ActiveRecord::Base  
  belongs_to :customer  
end

has_one关联

has_one关联同样是与另一个model建立一对一关联,但语义上有些不同(还有结果)。这种关联表明每一个model实例包含或者持有另一个model的实例。例如,如果你的应用程序里的每一个供应商仅拥有一个账号,你可像这样声明供应商model:

class Supplier < ActiveRecord::Base  
  has_one :account  
end

has_many关联

has_many关联表明与另一个model的一对多关系。你会经常在belongs_to关系的“另一边”找到这种关系。这种关系表明这种model的每个实例拥有0或多个的另一个model的实例。例如,在一个应用程序里包含客户和订单,客户model可以这样声明:

class Customer < ActiveRecord::Base  
  has_many :orders  
end

has_many :through关联

has_many :through关联通常用于和另一个model建立多对多关联。这种关系表明这样声明的model可以通过through处理匹配0或多个另一个model的实例。例如,考虑一个有关病人预约内科医生的医学练习,相关的声明可能像这样:

class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, :through => :appointments
end
  
class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient
end
  
class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, :through => :appointments
end

has_many :through关联同样有益于建立”快捷方式”通过嵌套的has_many关联。例如,如果一个文章有多个章节,而每个章节有很多段落,你也许有时想得到一个文档中所有段落的简单集合。你可以这种方式设置:

class Document < ActiveRecord::Base
  has_many :sections
  has_many :paragraphs, :through => :sections
end  
  
class Section < ActiveRecord::Base
  belongs_to :document
  has_many :paragraphs
end  
  
class Paragraph < ActiveRecord::Base
  belongs_to :section
end

设计模式之TemplateMethod

最近开始看《Ruby设计模式》一书,结合“追mm与设计模式”,虽然有些YD,不过也有助于帮助理解设计模式 ,边学习边记录一下吧。Template method模式是最简单的一种设计模式了.

Template method——看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤(Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦(具体实现);

模板方法模式

模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。

实例

以一个例子来说, 写一个输出一个不同格式的report generater。可以输出html格式,也可以输出纯文本的内容。

这个例子是hard-coded输出html格式的report类,但是无法处理纯文本格式。

class Report
  def initialize
    @title = 'Monthly Report'
    @text =  [ 'Things are going', 'really, really well.' ]
  end

  def output_report
    puts('<html>')
    puts('  <head>')
    puts("    <title>#{@title}</title>")
    puts('  </head>')
    puts('  <body>')

    @text.each do |line|
       puts("    <p>#{line}</p>" )
    end

    puts('  </body>')
    puts('</html>')
  end
end

如果你要处理纯文本,那么只能重新写一个类:

class Report
  def initialize
    @title = 'Monthly Report'
    @text =  ['Things are going', 'really, really well.']
  end

  def output_report(format)
    if format == :plain
      puts("*** #{@title} ***")
    elsif format == :html
      puts('<html>')
      puts('  <head>')
      puts("    <title>#{@title}</title>")
      puts('  </head>')
      puts('  <body>')
    else
      raise "Unknown format: #{format}"
    end

    @text.each do |line|
      if format == :plain
        puts(line)
      else
        puts("    <p>#{line}</p>" )
      end
    end

    if format == :html
      puts('  </body>')
      puts('</html>')
    end
  end
end

上面的代码是可以工作的,但是如果将来又有需要让你处理其他格式呢,再重写一遍啊?这个时候template method就起作用了。我们可以把公共的部分抽象出来,也就是说先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。Ruby本身并没有抽象类,那么我们可以像下面方式一样去模拟:

class Report
  def initialize
    @title = 'Monthly Report'
    @text =  ['Things are going', 'really, really well.']
  end

  def output_report
    output_start
    output_head
    output_body_start
    output_body
    output_body_end
    output_end
  end

  def output_body
    @text.each do |line|
      output_line(line)
    end
  end

  def output_start
    raise 'Called abstract method: output_start'
  end

  def output_head
    raise 'Called abstract method: output_head'
  end

  def output_body_start
    raise 'Called abstract method: output_body_start'
  end

  def output_line(line)
    raise 'Called abstract method: output_line'
  end

  def output_body_end
    raise 'Called abstract method: output_body_end'
  end

  def output_end
    raise 'Called abstract method: output_end'
  end
end

那么我们可以实现处理html的子类了:

class HTMLReport < Report
  def output_start
    puts('<html>')
  end

  def output_head
   puts('  <head>')
   puts("    <title>#{@title}</title>")
   puts('  </head>')
 end

 def output_body_start
   puts('<body>')
 end

 def output_line(line)
   puts("  <p>#{line}</p>")
 end

 def output_body_end
   puts('</body>')
 end

 def output_end
   puts('</html>')
 end
end

同样,处理文本的子类:

class PlainTextReport < Report
  def output_start
  end
 
  def output_head
    puts("**** #{@title} ****")
    puts
  end

  def output_body_start
  end

	def output_line(line)
	  puts(line)
	end

  def output_body_end
  end

  def output_end
  end
end

看看结果:

report = HTMLReport.new
report.output_report
#=>
<html>
<head>
  <title>Monthly Report</title>
</head>
<body>
<p>Things are going</p>
<p>really, really well.</p>
</body>
</html>


report = PlainTextReport.new
report.output_report
#=>
** Monthly Report ****
Things are going
really, really well.

Android Source Code

我们知道,源代码是最好的学习资料,所以今天就来记录下android中下载并查看源代码的方法:

  1. 安装Git,如果你还没有用Git,那么建议去Google下学习下相关知识,Git是现如今最流行的版本控制工具,没有之一。

  2. 新建源代码文件夹:

mkdir android_4.0_src
cd android_4.0_src
  1. 克隆sdk 远程仓库
git clone http://android.googlesource.com/platform/frameworks/base.git
  1. 找到android sdk里的android.jar所在目录, 在此目录下新建sources文件夹, 将下载的源码中的core/android和core/com目录复制到source文件夹里, 重启Eclipse. 即可查看SDK中的Java源码.

或者直接到这里也可以直接下载jar文件


Android全局异常处理

在做android项目开发时,大家都知道如果程序出错了,会弹出来一个强制退出的弹出框,这个本身没什么问题,但是这个UI实在是太丑了,别说用户接受不了,就连我们自己本身可能都接受不了。虽然我们在发布程序时总会经过仔细的测试,但是难免会碰到预料不到的错误。

今天就来自定义一个程序出错时的处理,类似iphone的闪退。(虽然闪退也是用户不愿意看到的,但是在用户体验上明显比那个原生的弹窗好多了)

废话不多说,直接上代码:

CrashHandler

/** 
 * 自定义的 异常处理类 , 实现了 UncaughtExceptionHandler接口  
 * 
 */  
public class CrashHandler implements UncaughtExceptionHandler {  
    // 需求是 整个应用程序 只有一个 MyCrash-Handler   
    private static CrashHandler INSTANCE ;  
    private Context context;  
      
    //1.私有化构造方法  
    private CrashHandler(){  
          
    }  
      
    public static synchronized CrashHandler getInstance(){  
        if (INSTANCE == null)  
            INSTANCE = new CrashHandler();  
        return INSTANCE;
    }

    public void init(Context context){  
        this.context = context;
    }  
      
  
    public void uncaughtException(Thread arg0, Throwable arg1) {  
        System.out.println("程序挂掉了 ");  
        // 在此可以把用户手机的一些信息以及异常信息捕获并上传,由于UMeng在这方面有很程序的api接口来调用,故没有考虑
          
        //干掉当前的程序   
        android.os.Process.killProcess(android.os.Process.myPid());  
    }  

}

CrashApplication

/** 
 * 在开发应用时都会和Activity打交道,而Application使用的就相对较少了。 
 * Application是用来管理应用程序的全局状态的,比如载入资源文件。 
 * 在应用程序启动的时候Application会首先创建,然后才会根据情况(Intent)启动相应的Activity或者Service。 
 * 在本文将在Application中注册未捕获异常处理器。 
 */  
public class CrashApplication extends Application {  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        CrashHandler handler = CrashHandler.getInstance();  
        handler.init(getApplicationContext());
        Thread.setDefaultUncaughtExceptionHandler(handler);  
    }  
}

在AndroidManifest.xml中注册

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="org.wp.activity" android:versionCode="1" android:versionName="1.0">  
    <application android:icon="@drawable/icon" android:label="@string/app_name"  
        android:name=".CrashApplication" android:debuggable="true">  
        <activity android:name=".MainActivity" android:label="@string/app_name">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  
                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  
    </application>  
    <uses-sdk android:minSdkVersion="8" />  
</manifest>

至此,可以测试下在出错的时候程序会直接闪退,并杀死后台进程。当然也可以自定义一些比较友好的出错UI提示,进一步提升用户体验。


Android Asynchronous Http Client

在Android的SDK中封装一些请求http的包,其中最常用的便是使用HttpClient了,我们一般都是自己定义一个http的工具类,然后把get和post方法封装起来,然后自己手动处理一些http的异常,值得注意的是http请求在android中是阻碍UI主线程的,所以必须开启新的线程在后台请求,所以这一来,发现只是发起一个http的请求就必须要做这么多事,而且自己封装的工具类也不一定是最好用的。

上面便是我以前的做法,本周在做moya的app时,重新审视了以前的代码,进行了一些重构。其中的一个地方便是使用了一个比较成熟的组件android-async-http来代替自己封装的http工具类,使用起来非常方便,省去了一些臃肿的代码,使代码总体看起来比较清晰。

Overview

android-async-http是基于Apache的HttpClient异步Http请求封装类,全部request都是脱离UI主线程的。这个包已经非常成熟了,国外有名的Instagram,Pinterest等apps都在使用。

Installation & Basic Usage

下载最新的.jar文件,在自己的android app中引用。

import com.loopj.android.http.*;

创建一个新的AsyncHttpClient实例并发起一个请求:

AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {
    @Override
    public void onSuccess(String response) {
        System.out.println(response);
    }
});

建议的用法:创建一个静态基类Http Client

import com.loopj.android.http.*;

public class BooheeClient {
  private static final String BASE_URL = "http://www.boohee.com/api/";

  private static AsyncHttpClient client = new AsyncHttpClient();

  public static void get(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
      client.get(getAbsoluteUrl(url), params, responseHandler);
  }

  public static void post(String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {
      client.post(getAbsoluteUrl(url), params, responseHandler);
  }

  private static String getAbsoluteUrl(String relativeUrl) {
      return BASE_URL + relativeUrl;
  }
}

然后在整个工程的其他地方便很方便的调用:

import org.json.*;
import com.loopj.android.http.*;

class BooheeClientUsage {
    public void getPublicTimeline() throws JSONException {
        BooheeClient.get("moya_video", null, new JsonHttpResponseHandler() {
            @Override
            public void onSuccess(JSONObject video) {
                // Pull out the first event on the public timeline
                String normal_url = video.getString("normal_url");

                // Do something with the response
                System.out.println(normal_url);
            }
        });
    }
}

上面的例子看到,对于json数据的处理也非常方便。不仅如此,还有其他更多的用法,如处理cookie,上传、下载文件等。github项目地址:android-async-http