Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
234 lines (200 sloc) 7.42 KB
require 'rubygems'
require 'trollop'
opts = Trollop::options do
banner <<-EOS
Creates a Java class from a Rails model for use in an REST-oriented Android
app. Makes a number of assumptions:
* all Rails columns are represented in the app
* columns exclusive to the app database are preceded by an underscore. By
default, _id, _created_at, _updated_at, and _synced_at columns are added to
maintain local state
Usage:
script/runner path/to/rails2android.rb [options] model_name
where [options] are:
EOS
opt :ignore, "Rails columns to ignore completely",
:type => :string,
:short => "-i"
opt :non_merge, "Instance variables to ignore when running merge(), specified as space-separated strings",
:type => :string,
:short => "-m",
:default => "_id _synced_at"
opt :non_param, "Instnace variables to ignore to running getParams(), which generates params for POST requests",
:type => :string,
:short => "-p",
:default => "id created_at updated_at"
opt :extra, "Extra columns not in the Rails model to include in the Android model, specified as space-separated name:type pairs",
:type => :string,
:short => "-e",
:default => ""
opt :default_sort_order, "Default sort order for the corresponding table in the app",
:type => :string,
:short => "-s",
:default => "_id DESC"
end
def sql_type(type)
case type
when "int", "Integer", "Long", "Timestamp", "Boolean" then "INTEGER"
when "Float", "Double", "float", "double" then "REAL"
when "String" then "TEXT"
else "BLOB"
end
end
def sucky_digest(s)
s.each_byte.to_a.sum
end
klass = Object.const_get(ARGV.last) rescue nil
Trollop::die "Must specify a model" if klass.nil?
Trollop::die "#{klass} isn't an ActiveRecord model" unless klass < ActiveRecord::Base
ignore = opts[:ignore].try(:split) || []
vars = {
'_created_at' => 'Timestamp',
'_updated_at' => 'Timestamp',
'_synced_at' => 'Timestamp'
}
klass.columns.each do |column|
next if ignore.include?(column.name)
vars[column.name] = case column.type
when :date, :datetime then "Timestamp"
when :decimal then "Double"
when :text then "String"
else column.type.to_s.capitalize
end
end
opts[:extra].split.each do |extra_column|
name, type = extra_column.split(':')
Trollop::die "\"#{extra_column}\" isn't a valid name:type pair" if name.blank? || type.blank?
vars[name] = type
end
vars = vars.to_a.sort
non_merge_var_names = opts[:non_merge].split
merge_vars = vars.reject {|name,type| non_merge_var_names.include?(name) || name =~ /^_/}
non_param_var_names = opts[:non_param].split
param_vars = vars.reject {|name,type| non_param_var_names.include?(name) || name =~ /^_/}
content_value_vars = vars.reject{|name,type| name =~ /^_/}
now = Time.now
puts <<-JAVA
// BEGIN GENERATED BY #{__FILE__} AT #{now}
package org.inaturalist.android;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONObject;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.util.Log;
public class #{klass.name} implements BaseColumns, Serializable {
public Integer _id;
#{vars.map{|name, type| "public #{type == 'int' ? 'Integer' : type} #{name};\n"}}
#{vars.map{|name, type| "public #{type == 'int' ? 'Integer' : type} #{name}_was;\n"}}
public static final String TAG = "#{klass.name}";
public static final String TABLE_NAME = "#{klass.table_name}";
public static final int #{klass.to_s.pluralize.underscore.upcase}_URI_CODE = #{sucky_digest(klass.to_s.pluralize)};
public static final int #{klass.to_s.underscore.upcase}_ID_URI_CODE = #{sucky_digest(klass.to_s)};
public static HashMap<String, String> PROJECTION_MAP;
public static final String AUTHORITY = "org.inaturalist.android.#{klass.to_s.underscore}";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/#{klass.to_s.underscore.pluralize}");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.google.#{klass.to_s.underscore}";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.#{klass.to_s.underscore}";
public static final String DEFAULT_SORT_ORDER = "#{opts.default_sort_order}";
#{vars.map{|name, type| "public static final String #{name.upcase} = \"#{name}\";\n"}}
public static final String[] PROJECTION = new String[] {
#{klass}._ID,
#{vars.map{|name, type| "#{klass}.#{name.upcase}"}.join(",\n")}
};
static {
PROJECTION_MAP = new HashMap<String, String>();
PROJECTION_MAP.put(#{klass}._ID, #{klass}._ID);
#{vars.map{|name, type| "PROJECTION_MAP.put(#{klass}.#{name.upcase}, #{klass}.#{name.upcase});\n"}}
}
public #{klass}() {}
public #{klass}(Cursor c) {
if (c.getPosition() == -1) c.moveToFirst();
BetterCursor bc = new BetterCursor(c);
this._id = bc.getInt(_ID);
#{vars.map {|name, type|
"this.#{name} = bc.get#{type.capitalize}(#{name.upcase});\n" +
"this.#{name}_was = this.#{name};\n"
}}
}
public #{klass}(BetterJSONObject o) {
#{vars.map {|name, type|
"this.#{name} = o.get#{type.capitalize}(\"#{name}\");\n" +
"this.#{name}_was = this.#{name};\n"
}}
}
@Override
public String toString() {
return "#{klass}(id: " + id + ", _id: " + _id + ")";
}
public JSONObject toJSONObject() {
BetterJSONObject bo = new BetterJSONObject();
#{vars.map {|name, type|
"bo.put(\"#{name}\", #{name});\n"
}}
return bo.getJSONObject();
}
public Uri getUri() {
if (_id == null) {
return null;
} else {
return ContentUris.withAppendedId(CONTENT_URI, _id);
}
}
public void merge(#{klass} #{klass.to_s.underscore}) {
if (this._updated_at.before(#{klass.to_s.underscore}.updated_at)) {
// overwrite
#{merge_vars.map {|name, type|
"this.#{name} = #{klass.to_s.underscore}.#{name};\n"
}}
} else {
// set if null
#{merge_vars.map {|name, type|
"if (this.#{name} == null) { this.#{name} = #{klass.to_s.underscore}.#{name}; }\n"
}}
}
}
public ContentValues getContentValues() {
ContentValues cv = new ContentValues();
#{content_value_vars.map{|name, type|
if type == "Timestamp"
"if (#{name} != null) { cv.put(#{name.upcase}, #{name}.getTime()); }\n"
else
"cv.put(#{name.upcase}, #{name});\n"
end
}}
return cv;
}
public ArrayList<NameValuePair> getParams() {
final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
#{param_vars.map{|name, type|
"if (#{name} != null) { params.add(new BasicNameValuePair(\"#{klass.to_s.underscore}[#{name}]\", #{name}.toString())); }\n"
}}
return params;
}
public static String sqlCreate() {
return "CREATE TABLE " + TABLE_NAME + " ("
+ #{klass}._ID + " INTEGER PRIMARY KEY,"
+ "#{vars.map{|name, type|
"#{name} #{sql_type(type)}"
}.join(",\"\n+ \"")}"
+ ");";
}
#{vars.map do |name, type|
"public boolean #{name}_changed() { return !String.valueOf(#{name}).equals(String.valueOf(#{name}_was)); }\n"
end}
public boolean isDirty() {
#{vars.map {|name, type|
"if (#{name}_changed()) { return true; }\n"
}}
return false;
}
}
// END GENERATED BY #{__FILE__} AT #{now}
JAVA