Check out the data set from this post over 1 and half years ago. On top of the basic data set's year, make and model, the complete data set comes with data like MSRP, MPG (city and highway), dealer invoice, gas tank size, etc.
We get tons of hits for basic data, but at no additional charge we also provide the complete data set. Just add a complete
=> "true" to your request. So simple and there's no performance hit.
For more information, check out or examples and faq.
Enjoy!
Here's another look at the data elements from a comprehensive query:
<?xml version="1.0" encoding="UTF-8"?> | |
<hash> | |
<telescopic-steering-column>Std.</telescopic-steering-column> | |
<front-split-bench-seat>N/A</front-split-bench-seat> | |
<powertrain-warranty-distance>36,000 mile</powertrain-warranty-distance> | |
<leather-seat>Std.</leather-seat> | |
<load-bearing-exterior-rack>N/A</load-bearing-exterior-rack> | |
<front-spring-type>Coil</front-spring-type> | |
<steel-wheels>N/A</steel-wheels> | |
<maximum-gvwr>No data lbs</maximum-gvwr> | |
<rear-wiper>N/A</rear-wiper> | |
<leather-steering-wheel>Std.</leather-steering-wheel> | |
<front-brake-type>Disc</front-brake-type> | |
<rear-brake-type>Disc</rear-brake-type> | |
<overall-length>187.60 in.</overall-length> | |
<pickup-truck-bed-liner>N/A</pickup-truck-bed-liner> | |
<front-side-airbag-with-head-protection>N/A</front-side-airbag-with-head-protection> | |
<second-row-side-airbag-with-head-protection>N/A</second-row-side-airbag-with-head-protection> | |
<cd-changer>Std.</cd-changer> | |
<second-row-folding-seat>Std.</second-row-folding-seat> | |
<am-fm-radio>Std.</am-fm-radio> | |
<locking-pickup-truck-tailgate>N/A</locking-pickup-truck-tailgate> | |
<fog-lights>Opt.</fog-lights> | |
<maximum-towing>1000 lbs</maximum-towing> | |
<msrp>$25,900 USD</msrp> | |
<genuine-wood-trim>N/A</genuine-wood-trim> | |
<powertrain-warranty-duration>36 month</powertrain-warranty-duration> | |
<cruise-control>Std.</cruise-control> | |
<front-power-lumbar-support>N/A</front-power-lumbar-support> | |
<power-sunroof>Std.</power-sunroof> | |
<towing-preparation-package>N/A</towing-preparation-package> | |
<city-mpg>21 miles/gallon</city-mpg> | |
<pickup-truck-cargo-box-light>N/A</pickup-truck-cargo-box-light> | |
<front-legroom>43.10 in.</front-legroom> | |
<first-aid-kit>N/A</first-aid-kit> | |
<mpg-hwy>30 miles/gallon</mpg-hwy> | |
<sliding-rear-pickup-truck-window>N/A</sliding-rear-pickup-truck-window> | |
<cargo-area-tiedowns>Std.</cargo-area-tiedowns> | |
<wheelbase>105.10 in.</wheelbase> | |
<electronic-parking-aid>N/A</electronic-parking-aid> | |
<second-row-side-airbag>N/A</second-row-side-airbag> | |
<rear-spring-type>Coil</rear-spring-type> | |
<driveline>FWD</driveline> | |
<dvd-player>Opt.</dvd-player> | |
<tow-hitch-receiver>N/A</tow-hitch-receiver> | |
<track-rear>61.20 in.</track-rear> | |
<deep-tinted-glass>N/A</deep-tinted-glass> | |
<maximum-payload>No data lbs</maximum-payload> | |
<anti-brake-system>4-Wheel ABS</anti-brake-system> | |
<abs-brakes>Std.</abs-brakes> | |
<model-year>2003</model-year> | |
<standard-gvwr>No data lbs</standard-gvwr> | |
<driver-multi-adjustable-power-seat>Std.</driver-multi-adjustable-power-seat> | |
<automatic-load-leveling>N/A</automatic-load-leveling> | |
<power-adjustable-exterior-mirror>Std.</power-adjustable-exterior-mirror> | |
<keyless-entry>Std.</keyless-entry> | |
<standard-seating>5</standard-seating> | |
<heated-exterior-mirror>N/A</heated-exterior-mirror> | |
<cassette-player>Opt.</cassette-player> | |
<tilt-steering-column>Std.</tilt-steering-column> | |
<limited-slip-differential>N/A</limited-slip-differential> | |
<trunk-anti-trap-device>Std.</trunk-anti-trap-device> | |
<transmission>5-Speed Automatic Overdrive</transmission> | |
<depth>No data in.</depth> | |
<highway-mpg>30 miles/gallon</highway-mpg> | |
<cargo-net>Std.</cargo-net> | |
<dealer-invoice>$23,305 USD</dealer-invoice> | |
<second-row-multi-adjustable-power-seat>N/A</second-row-multi-adjustable-power-seat> | |
<passenger-volume>88.00 cu.ft.</passenger-volume> | |
<engine-type>3.0L V6 SOHC 24V</engine-type> | |
<rear-shoulder-room>55.40 in.</rear-shoulder-room> | |
<rain-sensing-wipers>N/A</rain-sensing-wipers> | |
<warranty-duration>36 month</warranty-duration> | |
<driver-airbag>Std.</driver-airbag> | |
<interval-wipers>Std.</interval-wipers> | |
<tank>17.10 gallon</tank> | |
<remote-ignition>N/A</remote-ignition> | |
<wind-deflector-for-convertibles>N/A</wind-deflector-for-convertibles> | |
<warranty-distance>36,000 mile</warranty-distance> | |
<optional-seating>No data</optional-seating> | |
<exterior-color>Taffeta White</exterior-color> | |
<front-headroom>37.50 in.</front-headroom> | |
<destination-charge>No data USD</destination-charge> | |
<voice-activated-telephone>Opt.</voice-activated-telephone> | |
<air-conditioning>Std.</air-conditioning> | |
<interior-trim>Ivory Leather Interior</interior-trim> | |
<chrome-wheels>Opt.</chrome-wheels> | |
<child-safety-door-locks>N/A</child-safety-door-locks> | |
<second-row-sound-controls>N/A</second-row-sound-controls> | |
<front-suspension>Ind</front-suspension> | |
<electronic-brake-assistance>N/A</electronic-brake-assistance> | |
<cd-player>Std.</cd-player> | |
<overall-height>55.70 in.</overall-height> | |
<front-heated-seat>Std.</front-heated-seat> | |
<track-front>61.10 in.</track-front> | |
<year>2003</year> | |
<splash-guards>Opt.</splash-guards> | |
<locking-differential>N/A</locking-differential> | |
<cargo-length>No data in.</cargo-length> | |
<navigation-aid>N/A</navigation-aid> | |
<model>Accord</model> | |
<adjustable-foot-pedals>N/A</adjustable-foot-pedals> | |
<front-power-memory-seat>N/A</front-power-memory-seat> | |
<front-hip-room>54.20 in.</front-hip-room> | |
<run-flat-tires>N/A</run-flat-tires> | |
<power-windows>Std.</power-windows> | |
<second-row-removable-seat>N/A</second-row-removable-seat> | |
<alloy-wheels>Std.</alloy-wheels> | |
<glass-rear-window-on-convertible>N/A</glass-rear-window-on-convertible> | |
<rear-spoiler>Opt.</rear-spoiler> | |
<heated-steering-wheel>N/A</heated-steering-wheel> | |
<tire-pressure-monitor>N/A</tire-pressure-monitor> | |
<electrochromic-exterior-rearview-mirror>N/A</electrochromic-exterior-rearview-mirror> | |
<rust-duration>60 month</rust-duration> | |
<separate-driver-front-passenger-climate-controls>Std.</separate-driver-front-passenger-climate-controls> | |
<rear-hip-room>46.10 in.</rear-hip-room> | |
<cargo-volume>12.80 cu.ft.</cargo-volume> | |
<subwoofer>N/A</subwoofer> | |
<four_wd-awd>N/A</4wd-awd> | |
<cargo-area-cover>N/A</cargo-area-cover> | |
<removable-top>N/A</removable-top> | |
<ground-clearance>No data in.</ground-clearance> | |
<rear-suspension>Ind</rear-suspension> | |
<third-row-removable-seat>N/A</third-row-removable-seat> | |
<running-boards>N/A</running-boards> | |
<power-trunk-lid>N/A</power-trunk-lid> | |
<rear-headroom>36.10 in.</rear-headroom> | |
<side-head-curtain-airbag>Std.</side-head-curtain-airbag> | |
<trip-computer>N/A</trip-computer> | |
<manual-sunroof>Std.</manual-sunroof> | |
<standard-payload>No data lbs</standard-payload> | |
<overall-width>71.30 in.</overall-width> | |
<steering-wheel-mounted-controls>Std.</steering-wheel-mounted-controls> | |
<turning-diameter>35.40 in.</turning-diameter> | |
<body-style>COUPE 2-DR</body-style> | |
<front-shoulder-room>56.10 in.</front-shoulder-room> | |
<telematics-system>N/A</telematics-system> | |
<make>Honda</make> | |
<tachometer>Std.</tachometer> | |
<vehicle-stability-control-system>N/A</vehicle-stability-control-system> | |
<width-at-wheelwell>No data in.</width-at-wheelwell> | |
<second-row-heated-seat>N/A</second-row-heated-seat> | |
<manufactured-in>UNITED STATES</manufactured-in> | |
<tires>205/60R16</tires> | |
<power-door-locks>Std.</power-door-locks> | |
<automatic-headlights>Std.</automatic-headlights> | |
<daytime-running-lights>N/A</daytime-running-lights> | |
<front-air-dam>Std.</front-air-dam> | |
<electrochromic-interior-rearview-mirror>Opt.</electrochromic-interior-rearview-mirror> | |
<standard-towing>1000 lbs</standard-towing> | |
<front-side-airbag>Std.</front-side-airbag> | |
<curb-weight>No data lbs</curb-weight> | |
<power-sliding-side-van-door>N/A</power-sliding-side-van-door> | |
<trim-level>EX V6 coupe AT</trim-level> | |
<rear-legroom>31.90 in.</rear-legroom> | |
<passenger-multi-adjustable-power-seat>Std.</passenger-multi-adjustable-power-seat> | |
<steering-type>R&P</steering-type> | |
<width-at-wall>No data in.</width-at-wall> | |
<mpg-city>21 miles/gallon</mpg-city> | |
<traction-control>Std.</traction-control> | |
<rust-distance>Unlimited mile</rust-distance> | |
<vehicle-anti-theft>Std.</vehicle-anti-theft> | |
<high-intensity-discharge-headlights>N/A</high-intensity-discharge-headlights> | |
<passenger-airbag>Std.</passenger-airbag> | |
<skid-plate>N/A</skid-plate> | |
<full-size-spare-tire>N/A</full-size-spare-tire> | |
<rear-window-defogger>Std.</rear-window-defogger> | |
<tilt-steering>Std.</tilt-steering> | |
<front-cooled-seat>N/A</front-cooled-seat> | |
</hash> |
This is completely based on the Hello Android tutorial - I didn't really do a whole lot, but the hope is that this will help get some more ruby devs into android development with a simple how-to.
Note: RVM is awesome, if you don't use it or don't know about it - read more here.
rvm install jruby cd /path/to/your/android/pindah/mirah/project/dir/ # using your .rvmrc will trigger `rvm use jruby` when you cd into your project dir. echo "rvm use jruby" > .rvmrc # this will make sure your .rvmrc is working, you should then be able # to use rvm info and see jruby cd .
# too easy gem install mirah pindah
pindah create com.example.android.hello_world cd hello_world
# from your project dir and inside your pindah app dir <editor> src/com/example/android/hello_world/HelloWorld.mirah
This is the code I used:
package com.example.android.hello_world; | |
import android.app.Activity; | |
import android.os.Bundle; | |
import android.widget.TextView; | |
class HelloWorld < Activity | |
def onCreate(state:Bundle) | |
super state | |
tv = TextView.new(self) | |
tv.setText("Hello, World!") | |
setContentView(tv) | |
end | |
end |
This is where you define your app, version, name, etc, but more importantly for this example - what activity will handle your main intent.
Mine looks like this:
<?xml version="1.0" encoding="utf-8"?> | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
package="com.example.android.hello_world" | |
android:versionCode="1" | |
android:versionName="1.0"> | |
<application android:label="@string/app_name" | |
android:debuggable="true" | |
android:icon="@drawable/ic_launcher"> | |
<activity android:name=".HelloWorld" | |
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> | |
</manifest> |
# make sure a device is recognized ... # and make sure adb is in your path (platform-tools in the SDK) adb devices rake install]]>
Ok, so what did we decide to do? Well first, and most importantly, we decided to keep the service up and running; for a while there it was looking pretty bleak and if you take a second to look at the numbers (financially) you can understand why. Anyway, to be as fair as possible, we did not feel this could be monetized as a subscription-based product, some months you need 500,000 VINs and some only a 1,000. Looking at the data for year, we had many questions - what plan would you pick? should the plan rollover it's unused decodings?
We discovered a lot of complexity in the subscription model, so we decided to setup the pay as you go plan, where you buy your decodings at various bulk levels. For instance, you could buy in groups of 1,000 or 10,000 or 100,000 such that you would realize savings on a price-per-VIN basis the larger the group you purchase. There is no use policy either, so you can sit on the VINs for as long as you want, or you can even buy decodings the day (even the minute) before you need them. No monthly credit card bill, no rollovers, just simple "buy what you need," and if you buy in bulk, you save.
Another consideration in doing this was having to maintain soft limits. For example, if the user purchases the 5,000 VINs per month plan, and the their site does well this month and they need 6000 VINs, should they have chosen the 10,000 VIN plan? We didn't think so, we figured no hard limit, just soft limits, and after the soft limit is hit, we would charge the additional VINs at the current rate per VIN to the user's next month's bill. We tried to explain this to a few current users that are helping us come up with reasonable rates and they were not all that thrilled about it. So we had a problem, we did not want users to pay for what they did not need, but at the same time we did not want to cut users off in the middle of a month when they hit their limit. That entire idea had to be scrapped and along with it went the idea behind the subscription plan itself. We like it though, it's simple now - buy your decodings and use your decodings, we will email you if you are getting low and may want to add decodings to your account.
Subscription models are great for static resources and static services, but not a single one of our customers decodes the same number of VINs each month, so it just won't work. So onward and upward, pay as you go and regardless of how the other API providers offer their service, we are excited to be a little different and lot cheaper as you will soon find out.
Enjoy!
]]><? | |
// VIN API decoder for PHP 4.x+ | |
define('ELEMENT_CONTENT_ONLY', true); | |
define('ELEMENT_PRESERVE_TAGS', false); | |
function getXML($vin) { | |
$curl = curl_init(); | |
curl_setopt ($curl, CURLOPT_URL, 'http://vinapi.skizmo.com/vins/'. $vin.'.xml'); | |
curl_setopt ($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/xml', 'X-VinApiKey: #YOURAPIKEYGOESHERE#')); //use your API key here | |
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, true); | |
$result = curl_exec ($curl); | |
curl_close ($curl); | |
return $result; | |
} | |
function parseVIN($VIN) { | |
$info = array("model", "body-style", "country", "world-region", "vin", "engine-type", "transmission", "year", "make"); //include all values you want decoded | |
$info = sort($info); //alphabetical order | |
foreach ($info AS $type) { | |
$vin[$type] = parseDATA($type, $VIN); | |
} | |
return $vin; | |
} | |
function parseDATA($element_name, $xml, $content_only = true) { | |
if ($xml == false) { | |
return false; | |
} | |
$found = preg_match('#<'.$element_name.'(?:\s+[^>]+)?>(.*?)'. | |
'</'.$element_name.'>#s', $xml, $matches); | |
if ($found != false) { | |
if ($content_only) { | |
return $matches[1]; //ignore the enclosing tags | |
} else { | |
return $matches[0]; //return the full pattern match | |
} | |
} | |
// No match found: return false. | |
return false; | |
} | |
// Parse passed VIN to remove illegal characters | |
$vin = preg_replace("/[^A-Za-z0-9.]/", "", $_GET['vin']); | |
if(isset($_GET['vin'])) { | |
$data = getXML($vin); | |
$data = parseVIN($data); | |
if ($data != false) { | |
foreach ($data AS $key => $value) { | |
echo ucfirst($key) . ": " . $value . "<br />"; | |
} // outputs each XML node available | |
} else { | |
echo "VIN did not return any data."; | |
} | |
} | |
?> |
Public Function DecodeVIN(ByVal VIN As String, Optional ByVal Complete As Boolean = False) As String | |
Try | |
Dim aResponse() As Byte | |
Dim sResponse As String = "" | |
Dim oWebclient As New WebClient | |
Dim oEncoder As New System.Text.ASCIIEncoding | |
'Call the API | |
oWebclient = New WebClient | |
oWebclient.Headers.Add("X-VinApiKey", "APIKEYGOESHERE") | |
If Complete = True Then | |
aResponse = oWebclient.DownloadData("http://vinapi.skizmo.com/vins/" & VIN & ".xml?complete=true") | |
Else | |
aResponse = oWebclient.DownloadData("http://vinapi.skizmo.com/vins/" & VIN & ".xml") | |
End If | |
sResponse = oEncoder.GetString(aResponse) | |
'Debug.Print(sResponse) | |
Return sResponse | |
Catch ex As Exception | |
End Try | |
Return Nothing | |
End Function |
And for PHP, you have this:
<?php | |
function decodeVIN($vin){ | |
$curl = curl_init(); | |
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1); | |
curl_setopt ($curl, CURLOPT_URL, 'http://vinapi.skizmo.com/vins/'.$vin.'.xml'); | |
curl_setopt ($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/xml', 'X-VinApiKey: YOUR_API_KEY_GOES_HERE')); | |
$result = curl_exec ($curl); | |
curl_close ($curl); | |
return $result; | |
} | |
$vin = preg_replace("/[^A-Za-z0-9.]/", "", $_GET['vin']); | |
if(isset($_GET['vin'])){ | |
$decoded_vin = decodeVIN($vin); | |
echo $decoded_vin; | |
} | |
?> |
Hope this helps and if you have examples for other programming languages, please let us know.
]]>So I'm going to do a few posts about what I learned and I am going to start with the very basics, since all I could find were completed and elaborate images, way too complicated for a beginner.
Let's start with rotating a square box by 45 degrees. Very simple.
require 'rubygems' | |
require 'cairo' | |
class Box | |
# height and width for the rectangle, angle for the rotation | |
attr_accessor :height, :width, :angle | |
def initialize(height=200, width=200, angle=45) | |
@height, @width, @angle = [height, width, degrees_to_radians(angle)] | |
# The first argument for a surface is the filename, the next two are our canvas/surface size. | |
# I am making the surface twice as big as the object (WxH) we want to create, just to give it some padding. | |
@surface = Cairo::SVGSurface.new("rotated_square.svg", @width * 2, @height * 2) | |
# setup our context object to draw on | |
@cr = Cairo::Context.new(@surface) | |
# set it up with rotation | |
setup_scene_with_rotation | |
# draw the square | |
return build_rotated_square | |
end | |
private | |
def setup_scene_with_rotation | |
# give it a white background | |
@cr.set_source_color(:white) | |
@cr.paint | |
# rotate everything by our angle (in rads) from the middle | |
# if you do not move the rotation point to the center, you will be rotating from 0,0 (bottom left of the canvas) | |
# so first move to the middle of the surface (coords of W,H, since our canvas is 2W x 2H) | |
# then do the rotation | |
# then move back to 0,0 | |
# we know that width, height is the center b/c we start at 0,0 | |
# see @cr.current_point to be sure | |
@cr.translate(@width, @height) | |
@cr.rotate(@angle) | |
@cr.translate(-@width, -@height) | |
end | |
def build_rotated_square | |
# build a rectangle with height and width dimensions | |
# x and y are the starting points from which the rectangle is drawn | |
# x and y are the bottom left of the box | |
x = @width / 2 | |
y = @height / 2 | |
@cr.rectangle(x, y, @width, @height) | |
# the object has been created but is invisible, so let's draw a line around it | |
# the line will be black | |
@cr.set_source_color(:black) | |
# apply the line | |
@cr.stroke | |
# create the SVG | |
@cr.target.finish | |
end | |
# the angle comes in, in the form of the degrees and will need to be converted to radians | |
def degrees_to_radians(angle_in_degrees) | |
return angle_in_degrees / 180.0 * Math::PI | |
end | |
end | |
# let's get our box ... I'm not using GTK, so it will just spit out an image in the local fs | |
Box.new |
That's it, pretty simple, right?
]]>Thanks go to kendagriff.
]]>Ok, so this a rant and I am sorry for that - but as simple as it is, I have been looking for a blog plugin lately. The problem with the plugins I find is that I don't want to have to deal with the engines plugin or have the controllers, models, views, etc ... all extracted into my applications code. I want it all external (hence a plugin) but let it be minimally configurable.
So in my recent search for a blog plugin for rails, I came across two that look very useful, but each with their flaws:
1) bloget - Everything is extracted to my code space. Why? Yes, I realize that it is most likely because I will want to override things, but get out of my space and keep to yourself!
Provide me a way to override things that I would need to (there really shouldn't be too many), after all it is ruby.
2) bloggity - Uses the engines plugin! I have nothing against the engines plugin (I think it is well written and documented) but for a freaking blog plugin?!? Why?
Is there a third option?
Glad you asked - yes, there is a third option - I hate to say it, but do it right! There's your third option.
Ok, but really, if there is a third option (a third plugin), I would love to hear about it.
Ok, so all that to lead up to a little plugin tutorial? Well, it got your attention didn't it?
Ok, I guess I will start from scratch. So let's get started.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
pullmonkey$ ./script/generate plugin simple_blog create vendor/plugins/simple_blog/lib create vendor/plugins/simple_blog/tasks create vendor/plugins/simple_blog/test create vendor/plugins/simple_blog/README create vendor/plugins/simple_blog/MIT-LICENSE create vendor/plugins/simple_blog/Rakefile create vendor/plugins/simple_blog/init.rb create vendor/plugins/simple_blog/install.rb create vendor/plugins/simple_blog/uninstall.rb create vendor/plugins/simple_blog/lib/simple_blog.rb create vendor/plugins/simple_blog/tasks/simple_blog_tasks.rake create vendor/plugins/simple_blog/test/simple_blog_test.rb create vendor/plugins/simple_blog/test/test_helper.rb |
1 2 3 4 5 6 7 8 9 10 |
pullmonkey$ cd vendor/plugins/simple_blog/ # pretty important pullmonkey$ ls init.rb install.rb lib MIT-LICENSE Rakefile README tasks test uninstall.rb pullmonkey$ mkdir app pullmonkey$ mkdir -p app/models pullmonkey$ mkdir -p app/controllers pullmonkey$ mkdir -p app/views pullmonkey$ mkdir -p app/helpers pullmonkey$ ls app/ controllers helpers models views |
Well that was easy, so let's move on.
Ok, so we have a clear path for where our models, controllers, views, and helpers should live, right?
For simplicity, let's just have a post and comment model - you have all seen this a billion times.
1 2 3 |
class Post < ActiveRecord::Base has_many :comments end |
1 2 3 |
class Comment < ActiveRecord::Base belongs_to :post end |
And there you have it.
So what do you do to tell your rails application about your models?
Simple - inside vendor/plugins/simple_blog/init.rb - add these lines
1 2 3 |
model_path = File.join(directory, 'app', 'models') $LOAD_PATH << model_path ActiveSupport::Dependencies.load_paths << model_path |
Step 1 - we will need some default migrations for the model to use.
Post migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
pullmonkey$ ./script/generate migration post # This is what mine looks like class Post < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :subject t.text :body t.timestamps end end def self.down drop_table :posts end end |
And then the comment migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
pullmonkey$ ./script/generate migration comment # This is what mine looks like def self.up create_table :comments do |t| t.string :username t.text :body t.references :post t.timestamps end end def self.down drop_table :comments end |
Run the migrations:
|
pullmonkey$ rake db:migrate |
That was all just setup - now for the actual testing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
pullmonkey$ ./script/console Loading development environment (Rails 2.2.2) >> Comment.new => #<Comment id: nil, username: nil, body: nil, post_id: nil, created_at: nil, updated_at: nil> >> Post.new => #<Post id: nil, subject: nil, body: nil, created_at: nil, updated_at: nil> >> p = Post.create(:subject => "Test 1", :body => "My Body") => #<Post id: 1, subject: "Test 1", body: "My Body", created_at: "2009-02-11 19:09:25", updated_at: "2009-02-11 19:09:25"> >> p.body => "My Body" >> p.subject => "Test 1" >> p.new_record? => false >> p.comments => [] >> c = Comment.create(:username => 'pullmonkey', :body => "this is simple") => #<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: nil, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:01"> >> p.comments << c => [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">] >> p.comments => [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">] >> Post.first.comments => [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">] |
That's probably good enough. We have a working model and relationships. The best part is that all the code is still in the plugin.
What does my code space contain?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
pullmonkey$ ls -l app/** app/controllers: total 4 -rw-rw-r-- 1 pullmonkey pullmonkey 720 Feb 11 11:00 application.rb app/helpers: total 4 -rw-rw-r-- 1 pullmonkey pullmonkey 115 Feb 11 11:00 application_helper.rb app/models: total 0 app/views: total 4 drwxrwxr-x 2 pullmonkey pullmonkey 4096 Feb 11 11:00 layouts |
Just the defaults - neat
In much the same way as models, we can easily use controllers from our plugin. No extracting, no engines plugin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class PostsController < ApplicationController def index @posts = Post.all end def show @post = Post.find(params[:id]) end def new @post = Post.new end def create if @post = Post.create(params[:post]) flash[:notice] = "Post Created" redirect_to :action => 'index' else flash[:error] = "Post Not Created" render :action => 'new' end end #.... more code end |
1 2 3 4 5 6 |
class CommentsController < ApplicationController def index @comments = Comment.find_all_by_post_id(params[:post_id]) end #.... more code end |
Now, to register the controllers, add the following to vendor/plugins/simple_blog/init.rb:
1 2 3 4 |
controller_path = File.join(directory, 'app', 'controllers') $LOAD_PATH << controller_path ActiveSupport::Dependencies.load_paths << controller_path config.controller_paths << controller_path |
Ok, before we can really test this we will need to do the views, so keep going.
Create your view directories:
1 2 |
pullmonkey$ mkdir -p app/views/posts pullmonkey$ mkdir -p app/views/comments |
Create your views:
For this example, I will just create one, then we will test it.
1 2 3 4 5 6 7 8 9 10 11 |
<h1>Posts</h1> <% @posts.each do |post| -%> <h2><%= h post.subject %></h2> <%= post.body %> <h3>Comments</h3> <% post.comments.each do |comment| -%> <b>by <%= comment.username %></b><br/> <%= comment.body %><br/> <br/> <% end -%> <% end -%> |
If you don't do this next step, you will very likely see an error message like this:
Missing template posts/index.erb in view path /home/pullmonkey/rails_projects/simple_blog/app/views:
So let's add it.
There are at least two ways to do this. 1) Added to your controllers individually or 2) Add to application controller globally.
I prefer the less obtrusive, so let's go with number 1.
For this test, we will just work with the posts controller, so open it up again and add this line:
|
self.append_view_path(File.join(File.dirname(__FILE__), '..', 'views')) |
So your file should look like this now:
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 PostsController < ApplicationController self.append_view_path(File.join(File.dirname(__FILE__), '..', 'views')) def index @posts = Post.all end def show @post = Post.find(params[:id]) end def new @post = Post.new end def create if @post = Post.create(params[:post]) flash[:notice] = "Post Created" redirect_to :action => 'index' else flash[:error] = "Post Not Created" render :action => 'new' end end end |
Start your web server - ./script/server
Browse to http://localhost:3000/posts
You should see the post we created up above via Post.create(...) and its associated comment that we also created above.
Note:Feel free to overwrite any of the views. This can be done simply for the posts index view by creating the same file under RAILS_ROOT/app/views/posts/index.html.erb and doing what you'd like.
Ok, so that's part 1. The goal was to keep everything external and I think we succeeded (aside from migrations).
No offense to those that use engines or extract files into one's application's space, we all have our ways - the above is what I prefer.
Part 2 will consist mainly of filling this out a bit more and further discussion on adding helpers, routes and migrations to your plugin without interfering in the application's code space.
As always, have fun and good luck!
]]>Here is the graph we are after in this example:
More Open Flash Chart II examples.
And here is the code (the controller):
NOTE: You will need the latest plugin and open-flash-chart.swf (as of this article) for the tooltips to register properly.
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 |
class TestItController < ApplicationController def index @graph = open_flash_chart_object(600,300,"/test_it/graph_code") end def graph_code # based on this example - http://teethgrinder.co.uk/open-flash-chart-2/tooltip.php title = Title.new("MultiBar Tooltip") bar = Bar.new bar.values = [9,8,7,6,5,4,3,2,1] bar.tooltip = "Title Bar l<br>val = #val#" bar.colour = '#47092E' # NOTE: you can use obj.variable=() or obj.set_variable() interchangeably bar2 = Bar.new bar2.set_tooltip("Spoon {#val#}<br>Title Bar 2") bar2.set_colour('#CC2A43') vals = [1,2,3,4] tmp = BarValue.new(5) tmp.set_colour('#000000') tmp.set_tooltip("Spoon {#val#}<br>Title Bar 2<br>Override bar 2 tooltip<br>Special data point") vals << tmp vals << [6,7,8,9] vals = vals.flatten bar2.values = vals t = Tooltip.new t.set_shadow(false) t.stroke = 5 t.colour = '#6E604F' t.set_background_colour("#BDB396") t.set_title_style("{font-size: 14px; color: #CC2A43;}") t.set_body_style("{font-size: 10px; font-weight: bold; color: #000000;}") chart = OpenFlashChart.new chart.title = title chart.add_element(bar) chart.add_element(bar2) chart.set_tooltip(t) render :text => chart.to_s end end |
And in your view (index.html.erb):
1 2 3 4 |
<script type="text/javascript" src="/javascripts/swfobject.js"></script> <%= @graph %> |
Good Luck!
]]>Just for reference, the code that follows represents this graph:
Examples for version 2 are here.
Using the new version of Open Flash Chart, here is an example for you to follow:
1 2 3 4 5 6 |
Result.create(:student_name => "Jack", :subject => "History", :test_score => 97 Result.create(:student_name => "Jack", :subject => "Science", :test_score => 85) Result.create(:student_name => "Jill", :subject => "History", :test_score => 92) Result.create(:student_name => "Jill", :subject => "Science", :test_score => 57) |
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 |
class TestItController < ApplicationController def index @graph = open_flash_chart_object(600,300,"/test_it/graph_code") end def graph_code # we will have bars for each student subject combo bars = [] # random colors to chose from colours = ["#459a89", "#9a89f9"] # the results results = Result.find(:all) # group by subject and use subject as the key results.group_by(&:subject).each do |subject, result| # 3d bar graph, could be any bar graph though bar = Bar3d.new bar.set_key(subject, 3) bar.colour = colours[bars.size] bar.values = result.map(&:test_score) bars << bar end # some title title = Title.new("Test Results") # labels along the x axis, just hard code for now, but you would want to dynamically do this x_axis = XAxis.new x_axis.labels = ["Jack", "Jill"] # go to 100% since we are dealing with test results y_axis = YAxis.new y_axis.set_range(0, 100, 10) # setup the graph graph = OpenFlashChart.new graph.bg_colour = '#ffffcc' graph.title = title graph.x_axis = x_axis graph.y_axis = y_axis graph.elements = bars render :text => graph.to_s end end |
1 2 3 4 |
<script type="text/javascript" src="/javascripts/swfobject.js"></script> <%= @graph %> |
Hope that helps.