September 13, 2007

My app’s up but not quite running.  I’m getting this when i go to the beta url (which is http://beta.reeplies.com/public) :

Application error

Rails application failed to start properly”

Yesterday, Jay Gooby from the Brighton Ruby group kindly gave me several hours of his time to try and get Reeplies up and running.  Here’s a breakdown of what i’ve done so far, before meeting Jay and with his help, as much for my own benefit as anyone elses.

Got my account with bluehost, which included free domain name registration with a years’ hosting (which cost about £45).

Got them to give me SSH access (i needed to provide some id, i emailed photos of the front and back of my driving license which was fine).  They provided me with an ssh login and password.  I learned that SSH is basically like telnet – a way of communicating with a remote host via the command line  The difference is that the communication is encrypted, but that all happens under the hood – from a users perspective it’s like telnet.

Using the bluehost control panel, added an ftp account “max_williams@reeplies.com”.  This created a subfolder called /public_html/max_williams.

Jay added another ftp account “beta@reeplies.com” which has the folder /rails/beta.  This folder is to hold my working rails application folder.

Next step was to copy the application folder over!  Bluehost provide their own ftp client for this but it’s a java applet and is a bit clunky.  We used CoreFTP which i had already installed.  We logged into beta@reeplies.com, and then just dragged the ‘newspipe’ (the working name of Reeplies) folder into the root directory (ie into rails/beta).

We were doing this using the wifi in the Windmill on montpelier terrace (nice pub btw – well kept ale and great chips) so the upload took a long time.  Tip for anyone doing this – copy your app folder over to a temporary location and clear out anything you don’t need – saved files from the public folder and svn files for example.  Then upload the cleaned up copy instead of your original folder.  Obviously the PROPER way to do it is to get an online svn repository and install it from there.  I’m working on it, for now the priority is to get SOMETHING up there.

Anyway, next step is to deal with Ruby gems and plugins:  the host doesn’t have the ones i use, so i need to install them myself.  According to Jay this is simple – we just go into the host unix environment (using ssh) and treat it like we would our own local command line.  One difference is that the host is a unix environment whereas i’m used to windows: that means that some or all of the plugins/gems will use different versions to the ones i used.

To connect to the ssh command line i used PuTTY,  with these connection settings:

Host name = reeplies.com
port = 22
connection type = SSH

(putty lets you save these details for future use)

So, now we’re on the remote server’s command line!  Time for me to do a little bit of research into unix commands – i’ve never really ‘done’ unix.  OK…

cd dirname – open directory – same as windows.
cd .. – go up – again same as windows
ls – list files and folders – like dir in windows

ok…

Next step was to get a mysql database up for my app to use.  Created this using the bluehost control panel – a new mysql database called ‘reeplies_newspipedevelopment’ (it wouldn’t let us put underscores in for some weird reason, even though it adds the “reeplies_” prefix itself.  Then, after the database.yml file had copied over, Jay changed it to point to the new database, and also set it so that it won’t be overwritten when i copy my files over.

I’m *not sure* whether we actually managed to set the database up, ie create all the tables and fields using my schema.  There was an issue with rake – it didn’t seem to want to work.  I think that this was maybe just because the files were still copying over the ftp.  I just tried it now, but it’s having a problem because hpricot isn’t installed.  hpricot is one of my gems (or is it a plugin?), so i guess all the gems and plugins need to be there before the migrate can happen.

Hmm.  Think i’ll try commenting out those lines from environment.rb for now to see if i can get the migration to work…nope, now it has a permissions issue:

Access denied for user ‘max’@’localhost’ (using password: YES)
I’m a bit nervous about flailing around with this in case i break something.  Think i’ll leave it till i get my next expert help session tomorrow from James McCarthy.

So, might as well get on with installing the gems and plugins!  According to my environment.rb file, this is what i’m using (in future it would be a good idea to put in a comment next to them with the precise command i used to install them):

require ‘rubygems’
require ‘RMagick’
require ‘open-uri’
require ‘hpricot’ – used to get the title of a web page from a url
include Magick
gem ‘actionpack-imagemagick’
require “mini_magick”

Now, i remember reading about a ruby gem that basically takes all of your gems, and maybe plugins, that you’re using and does something with them that makes them easy to copy over.   Looks like this is it – Rick Olsen’s Tzinfo: http://agilewebdevelopment.com/plugins/gems 

Looks a bit complex – or rather, i’m nervous :)  Going to try installing them one by one.  Let’s do hpricot first as i think that was pretty simple (unlike rmagick/imagemagick).

hpricot: seems simple enough – just type

gem install hpricot

Gah, foiled again:  i get a choice of versions (windows, jruby, ruby) and when i choose ruby i get:

ERROR:  While executing gem … (Errno::EACCES)
Permission denied – /usr/lib/ruby/gems/1.8/cache/hpricot-0.6.gem

OK – time to mail the bluehost people i think…

OK – before i post my question, they suggest some previous replies that may be helpful.  One of them is “How do I install my own Gems?”.  Let’s have a look.

OK – according to this, i do the following:  use their file manager to open .bashrc, which is in the root directory (back it up first).  Then add the following lines to the end:

export PATH=”$PATH:$HOME/packages/bin:$HOME/.gems/bin”
export GEM_HOME=”$HOME/.gems”
export GEM_PATH=”$GEM_HOME:/usr/lib/ruby/gems/1.8″

export GEM_CACHE=”$GEM_HOME/cache”

Then connect to the site with ssh (i’m in there already), and enter the following in the root folder:

cp /usr/lib/ruby/gems/1.8/cache/sources-0.0.1.gem ./
gem install sources-0.0.1.gem
gem update -y

Did the first line, got a permission denied error again.  OK, i’m mailing them now!  Arggh, they’ve got no option to edit your ‘ticket’ ie your help request – it’s either send it or cancel it and start again!  That’s really annoying.   Going to have to rewrite it as i want to tell them that this other suggestion doesn’t work.

Next day…still waiting for a reply.  bah.

One last thing i want to do – i want to add a new page where you can see all the reeplies, listed by score and date (like the article list). This uses a paginator, like the article list.

By basically copying the article code (i’m not going to reproduce it all here), this is all working. However, it looks bad – the format used to list stories at the bottom of the article doesn’t look so good once they’re a big list using the whole screen. In addition, each story/reeply should have a link to the article to which it replies.

So, i need a new format partial, to list stories and show the associated article as well. This one will be called ‘story_and_article’, and the format will be closer _article than it is to the existing _story partial. Here it is:

<% story = story_and_article %>
<tr>
<td>
<%= render :partial => ‘story/updown_arrows’,
:locals => { :story => story } %>
</td>
<td class=”poster_name”>
<%= story.points_to_s %>
</td>
<td>
<span class=”article_list”><%= link_to story.title,
:controller => ‘story’,
:action => “show”,
:id => story %></span>
<span class=”summary_list”> Reeplying to <%= link_to story.article.title,
:controller => ‘article’,
:action => “show”,
:id => story.article %></span>
<br/>
<% unless story.summary == nil || story.summary == “” %>
<%= render :partial => ‘shared/summary_brief’,
:locals => { :item => story } %>
<br/>
<% end %>
<%= render :partial => ‘shared/added_by’,
:locals => { :item => story } %>
<br/>
</td>
</tr>

Note that the first thing that i do is take the input object “story_and_article” and rename it to “story”, just so i’m not typing stuff like “story_and_article.article.summary”. The rest of it is pretty standard – note that i’m calling another two partials for the summary and ‘added_by’.

The summary partial is quite interesting, as it uses a helper method to show only the first n words of the summary: here’s the partial –

<span class=”summary_show”>”<%= first_x_words(item.summary, 20)%>”</span>

Minimal eh? I only split this into a partial in case i want to add a summary to articles as well – that’s why it works on an ‘item’, not a ‘story’.

Here’s the method it calls:

def first_x_words(str,n=20,finish=’…’)
truncated = str.split(‘ ‘)[0,n].inject{|sum,word| sum + ‘ ‘ + word}
truncated += finish if truncated.length < str.length
return truncated
end

This uses the ruby inject method, which i *have* understood in the past but haven’t used before. It’s a bit like a recursive method call i think. In case you’re wondering, … is n html symbol for “…” – it’s neater than actually writing “…”: a bit smaller and renders better across all platforms apparently.

The ‘added_at’ partial is the same code i’ve been reusing in various places – i just partialled it out for DRYness: again it uses ‘item’ since it IS used by article and story. Note that in the story_and_article code above i pass through a local variable, setting :item to be equal to ‘story’.
<span class=”poster_name”>
<%= “Added” if item.added_at or item.user_id %>
<%= “#{time_ago_in_words(item.added_at)} ago ” if item.added_at %>
<%= “by #{item.user.login}” if item.user%></span>

Done a bit more tweaking and polishing…

Found a bug with those submit_tag confirmation dialogs from yesterday – the box that’s displayed says “Submit Reeply “my reeply title”?”, getting the title from the saved object. But, the title hasn’t been saved to the object yet, so it displays the old name (eg the default of “untitled reeply”). I need to get the data out of the form field rather than the object.

OK, with some more forum help (the forums have been real good to me recently, probably because i’m doing run of the mill stuff rather than arsey video conversion), i just need to change the :onclick parameter:

<%= submit_tag “Delete Reeply”,
:onclick => “return confirm(‘Delete \”‘ + getElementById(‘story_title’).value + ‘\”?’);” %>

The key here is “getElementById” – the camelized name should be a giveaway that this isn’t ruby, it’s javascript. Good to know.

Just noticed something else – on the article list page, if an article has 1 Reeply, it says “1 Reeplies”. Need to adjust my number_of helper method to take pluralization (or lack of it) into account:

def number_of(params = {})
word = params[:word]
number = params[:number].to_i
if number != 1
word = word.pluralize
end
if number == 0
if params[:capitalize]
number = “No”
else
number = “no”
end
end
return number.to_s + ” ” + word
end

so, a an article with stories = 0 has “No Reeplies”, one with stories = 1 has “1 Reeply”, one with stories = 2 has “2 Reeplies”. I’ve left points as being displayed as “0 points” rather than “No points” as points can go to minus numbers, so 0 is a number, unlike Reeplies where 0 means an absence of something.

I think i’m just about done with the tweaking. Now, to get back to the video stuff again. Whoops, it’s 6 o clock already, time flies when you’ve not got much of it left.

More tweaking

September 5, 2007

At the moment, i’m avoiding the nasty video conversion and deployment problems, and just focussing on improving Reeplies. Feels like shirking but hey (i’m also avoiding writing up my project). The next thing i’m going to do is to add a page for “Your draft Reeplies”, ie “stories where user_id = session[:user].id and complete = 0”. Ideally i’d do a tabbed page but this is unneccessary polish at this stage.

The drafts page is simply going to be a list of stories. To stay DRY i want to use the story_list partial, but there’s a problem – currently the story list partial defines what makes it into the list of stories, which won’t work for drafts as the criteria are different. I think it would be better if story_list works with a variable called ‘story’, and i define ‘for story in @some_stories’ before calling the partial.

Looking in my copy of ‘Agile web dev with RoR’, i see the perfect answer:  if you call a partial and pass a :collection value through, then the contents of the partial get called for every item in the collection.  In order for this to work, the name of the individual items in the partial should be the same as the name of the partial.  To avoid rewriting the partial, where i’m constantly referring to ‘story’, i’m going to rename the partial to be ‘story’.

So, the new _story partial is the same, but minus the for and end lines.  I now call i like this from article/show:

<%= render :partial => ‘story/story’, :collection => @article.complete_stories %>

and like this from story/list_drafts:

<%= render :partial => ‘story/story’, :collection = @drafts %>
I’ve done the same for the other partials that list stuff – i’ve moved the conditions for what gets listed outside of the partial, as above.

OK.  Next step is to show the drafts. First, i set up a new relation for User, ‘drafts’, which is all stories belonging to them where complete = false. Then, if i have @user, i can just say @user.drafts. In user.rb:

has_many :drafts,
:class_name => “Story”,
:conditions => [ ‘complete = ?’, false ],
:order => “added_at DESC”

Next, i add a link to the “logged in” partial, so you see

You are logged in as patton Log out Change password Your Draft Reeplies

at the top of the page. This is the link:

<%= link_to ‘Your Draft Reeplies’, :controller => “story”, :action => “list_drafts” %>

Next i need a story controller method to set up a couple of variables:

def list_drafts
@user = User.find(session[:user].id)
@drafts = @user.drafts
end

Then finally the view page for the list of drafts:

<%= render :partial => ‘shared/save_page’ %>
<%= render :partial => ‘user/login_manager’ %>

<span class=”page_header”>Your draft Reeplies</span>
<br/>

<% @stories = @drafts%>
<%= render :partial => ‘story/story_list’ %>

Looks good, but there’s a problem: when you delete a draft, it takes you to the article page that the draft reeply was associated with. I want it to stay on the draft reeplies page.

I’m already saving the page with the save_page partial, which simply has this:

<% session[:return_to] = request.request_uri %></div>

So, it should simply be a case of making sure that the delete action goes to the saved page after doing it’s stuff. The story/delete action looks like this:

def delete
@story = Story.find(params[:id])
@article = @story.article
@story.destroy
redirect_to :controller => “article”, :action => “show”, :id => @article
end

session[:return_to] is used by the application controller method redirect_to_stored. So, if i just change the last line to

redirect_to_stored

then it should work fine. It does! Nice.

Did a little bit of tidying up with partials – the login_manager is now part of the standard layout, and has a Reeplies.com link, which goes to the article list page. At some point it would be nice to replace this with a little logo.

Another thing i’ve been meaning to do for ages is to put a confirmation dialog on delete actions. This is simple enough: along with the parameters for a link_to, we pass a k-v pair with the key :confirm, and the value for whatever we want to be displayed for the confirmation question, eg “Delete this Reeply?”. One gotcha with this is that it goes in a different hash to the one that has :controller, :action, and :id, so we need to put those in { }.

(personally i think rails would be clearer if we always used the { }, but the convention is to omit them whenever possible. To me that highlights the problem with cases like this, where it breaks if you omit them).

Anyway, the link to delete now looks like this:

<span class=”option_link”><%= link_to “Delete \”#{story.title}\””,
{ :controller => “story”, :action => “delete”, :id => story.id },
:confirm => “Delete Reeeply: are you sure?” %></span>

I added this option into the story show page, and of course it crashed because it was trying to go back to the show page for a story that doesn’t exist. So, when i call delete from story, i need to go to the article show page instead.

The way i’ve done this (which may not be the most elegant way) is to do a regexp check on session[:return_to] in the controller, and reroute to the article/show if it was going to go back to story/show:

if session[:return_to].match(/reeply\/show/)
redirect_to :controller => “article”, :action => “show”, :id => @article
else
redirect_to_stored
end

Ah, wait – what if they came to story/show from the drafts page? Deleting the story should take you back to the drafts page. Hmm.

I wonder if i could handle this a bit more elegantly? Maybe on every page i could push the current url onto a stack, and if there’s a problem with the current page, go back down the stack until i find one that works? Someone must have thought of this stuff for rails already though…i think, instead of doing a massive stack, i’ll just have the previous and the page before that, using the same save_page system i’ve got at the moment. This is the new save_page partial:

<% session[:return_to_older] = session[:return_to]%>
<% session[:return_to] = request.request_uri %>

This is the new redirect_to_stored method:

  def redirect_to_stored(params = {})
if params[:older]
return_to = session[:return_to_older]
else
return_to = session[:return_to]
end
#test if we set return_to to something other than nil
if return_to
session[:return_to] = nil
redirect_to_url(return_to)
else
#flash[:message] = “session[:return_to] was empty, returning to list page”
redirect_to :controller => ‘article’, :action => ‘list’
end
end

And this is where i call it, from the delete_story method:

 def delete
@story = Story.find(params[:id])
@article = @story.article
#before deleting, we need to check that we weren;t on the story/show page
#if we were, we can’t go back there after deleting: go to the previous instead
@story.destroy
if session[:return_to].match(/reeply\/show/)
redirect_to_stored :older => “true”
else
redirect_to_stored
end
end

This now works for deleting drafts – you go back to the “your drafts” page.  But it doesn’t work for deleting stories that you got to from the article/show page now – you go back TOO FAR!  agghh.  I think i’m going about this in a very clumsy way.   OK, tweaked it a bit – now the controller goes back to the draft_list if we came from their recently, otherwise it always goes back to article/show, for the article that owned the deleted reeply.

  def delete
@story = Story.find(params[:id])
@article = @story.article
@story.destroy
    #after deleting a reeply, we either go to the ‘your drafts’ page or to
#the show page for the article that had that reeply

if session[:return_to].match(/reeply\/list_drafts/) ||
session[:return_to_older].match(/reeply\/list_drafts/)
redirect_to :controller => “reeply”, :action => “list_drafts”
else
redirect_to :controller => “article”, :action => “show”, :id => @article.id
end
end

OK, next thing – i think that links to ‘edit story’ or ‘delete_story’, etc, should be buttons, in keeping with their more significant actions.  There should also be buttons to ‘save draft’ and ‘delete’ on the ‘edit_story’ page.

Changing links to buttons couldn’t be simpler – just ‘button_to’ instead of ‘link_to’.  There seems to be a slight difference in that if you put two buttons next to each other, each inside a span element, they go on seperate lines.  So i put them in a little table if i need them next to one another.  So, that’s all easy.

A bit more complex is adding in the new buttons to the story/edit page: the way i did this, since all the buttons are in the middle of the form, is to have them all labelled as submit tags, and then, in the  create method of the story_controller, use a case statement to analyse :commit (which has the text from the button that was pressed) to decide what to do next.

Here’s my new list of buttons on the story/edit form:

<%= submit_tag “Submit Reeply” %>
<%= submit_tag “Save changes” %>
<%= submit_tag “Delete Reeply” %>

And here’s the two new cases  from story_controller/create:

  when “Save changes”
if @story.save
flash[:notice] = “Saved Reeply draft.”
else
flash[:notice] = “Sorry, your Reeply draft was not able to be saved.”
end
redirect_to :controller => “story”, :action => “edit”, :id => @story
when “Delete Reeply”
redirect_to :controller => “story”, :action => “delete”, :id => @story

So, they work.  Cool. Next though, i want to add confirmation dialogs to them, like i do with the other buttons to ‘Delete story’.  As these are “submit_tag”, rather than “button_to”, however, it doesn’t seem to be possible.

After a bit of forum help, of course it’s possible!  We just need to use a bit of javascript (of which i’m woefully ignorant).  Here’s how to do it:

  <%= submit_tag “Delete Reeply”,
:onclick => “return #{confirm_javascript_function(“Delete \”#{@story.title}\”?”)}”  %>

Note the complicated set of brackets and quotes at the end there!

Back from holiday

September 3, 2007

Ahhh…holiday was great. Now i have 3 weeks left to finish and write up my degree project. wahey! I need to leave the video conversion business for now and get the thing online and usable.

Orginally i was hoping to get my uni to host a rails server – they seemed up for it but hadn’t done a rails server before, which made me a bit nervous – my uni is two hours travel away and i didn’t want to have to go in to try to get it up and running too often. So, i decided to go with a paid web hosting service. I’d been looking at BlueHost and a friend suggested them, so i went with them. $7.95 a month for 12 months, comes out at about £48 for the year.

I decided to change the name to http://www.reeplies.com, with the ‘stories’ that people attach to articles renamed to ‘reeplies’ – this makes the focus of the site more on the reeplies than the articles, which is good.

Anyway, going through the bluehost setup wizard at the moment. After getting it set up i’m going to get capistrano up and running so i can update it easily.

OK, finished the wizard, now i need to set up my stuff for uploading to the server. Before i do that i’m going to do that rename of stories to ‘reeplies’. Most of it is just simple cosmetic stuff, but one thing that isn’t is the urls: instead of the url for editing a story being

http://www.reeplies/story/edit/84

i want it to be

http://www.reeplies/reeply/edit/84

This involves routes, which i’ve seen referenced but not used. To skip past all my fumbling about and forum questions, this is how i did it: in config/routes.rb, add the following line:

map.story ‘/reeply/:action/:id’, :controller => “story”

Amazingly, this is all that’s required – i don’t have to change any of my link_to or redirect_to parameters – it just works! Outstanding!

I did have one little problem that puzzled me for a while – my reeply form submission wasn’t working. This turned out to be due to a case statement that i was using to detect which button the user pressed on the form page (they could submit the form, add a picture or add a movie). The case statement works by looking at the value in params[:commit], which contains the text displayed on the buttons – the easiest/only? way to tell them apart in the way i set it up. So i had to change the case condition to use the updated button text “Submit Reeply”. Sorted.

So, i think it’s ready to get hosted now. Obviously there’s still lots of stuff i want to fix but it’s useable and has the right names for stuff. So, on with the hosting setup!

One day later, no progress. One of the brighton Ruby group people has kindly offered to help, a bit later in the week. So, in the meantime, i’m going to do a bit of tweaking – in particular, to let the user rate reeplies up and down. This will work pretty much the same as for articles – for example, a reeply added by a user will automatically be marked as having been scored up by that user.

So, just as we had a ‘scores’ class that had user_id, article_id and points, we now need a story_scores class to do the corresponding task for stories/reeplies. (i’m calling it story_scores as reeplies are still called stories everywhere in the code). Time to go back and read my old blog post about setting up scores! Actually, on rereading that i remembered that i revised it loads as i went along, making it messy and hard to follow. So, here’s the final working version, but for stories this time.

At the command line:

ruby script/generate model StoryScore

In the new story_score.rb file:

class StoryScore < ActiveRecord::Base
belongs_to :story
belongs_to :user
end

In story.rb:

has_many :scores, :through => :story_scores
has_many :scorers, :through => :story_scores, :source => :user

A migration to make the table –

class CreateStoryScores < ActiveRecord::Migration
def self.up
create_table :story_scores do |t|
t.column :story_id, :integer
t.column :user_id, :integer
t.column :points, :integer
end
end

def self.down
drop_table :story_scores
end
end

A change_score method for story_controller:

def change_score
begin
@story = Story.find(params[:story_id])
rescue ActiveRecord::RecordNotFound
flash[:message] = “Couldn’t find Reeply##{params[:story_id]}, redirecting to article list.”
redirect_to :controller => “article”, :action => “list”
else
flash[:message] = @story.change_score(params)
redirect_to :controller => “article”, :action => “show”, :id => @story.article_id
end
end

And the story model method it calls:

def change_score(params = {})
new_points = params[:points].to_i
@score = StoryScore.find(:first, :conditions => [“story_id = ? and user_id = ? “, params[:story_id], params[:user_id]])
if @score == nil
#@score not found
#no score exists from this user so make a new one then add the points

return_string = “Didn’t find score, making a new one”
@score = StoryScore.new(:story_id => params[:story_id], :user_id => params[:user_id], :points => new_points)
self.points += new_points
@score.save
self.save
else #found something for @score in db already
if @score != nil #@score was already there
#look for a nil and turn it into a 0 if present
@score.points = 0 if @score.points == nil
if @score.points != new_points
#score already there but points are different
#subtract old points then add new points

self.points -= @score.points
self.points += new_points
return_string = “changed your score for this story from #{@score.points} to #{new_points}”
#then change @score to have new points
@score.points = new_points
@score.save
self.save
else
return_string = “you already gave this story #{@score.points} point”
end #if
end #if
end #if
return return_string
end #method

And the simple story model methods called by the above method:

def score_from_user(params = {})
begin
score = StoryScore.find(:first, :conditions => [“story_id = ? and user_id = ? “, self.id, params[:user_id]])
return score.points
rescue
return 0
end
end

def change_points(params = {})
self.points += params[:amount]
self.save
end

Cool, that all seems to be working ok! Noticed a couple of bugs though:

  • Uncompleted reeplies (drafts) are being listed alongside completed reeplies – they shouldn’t be.
  • When you add a reeply, it should be scored up by you automatically.

Fixed the first problem by adding the following to the relational info at the start of the article class:

has_many :complete_stories,
:class_name => “Story”,
:conditions => [ ‘complete = ?’, true ]

This is a top tip i just got from a forum, i’m going to look into doing more of this sort of stuff.

Now in the view, instead of saying

for story in @article.stories

I just say

for story in @article.complete_stories

Next – i want to call change_score from the create methods of story and article.  Here’s how I do it:

When a story is created, its points are set to 0.  There’s no StoryScore record for it at this stage.  When the user hits the ‘submit’ button to submit the completed story, the submit method of the Story model is called.  I simply added a line to the submit method to call change_scores with 1, so it’s just as if the user hit the “score up” button when they submitted the story.

Next is to do the same for articles…ok.

I also want to check if there’s an article there already, and if so then score it up for the current user, instead of re-adding it.

Done that, in the add method of the article controller:

def add
#first of all, see if we already have an article with the passed url.
#if we do, we just want to score it up for the current user (and inform the user that we did it)
if (@existing_article = Article.find(:first, :conditions => [“url = ?”, params[:article][:url]]))
@existing_article.change_score :user_id => session[:user].id, :points => 1
flash[:notice] = “This article is in Reeplies already.  It has been automatically scored up for you.”
redirect_to :action => ‘list’
else
@article = Article.new(params[:article])
@article.user_id = session[:user].id
if @article.save
flash[:notice] = ‘Article was successfully created.’
@article.change_score :user_id => session[:user].id, :points => 1
redirect_to :action => ‘list’
else
render :action => ‘new’
end
end
#if else
end

One last thing – for each story, i want to list Reeplies in order of points.  This turns out to be super-easy!  I already set up a new associated called complete_stories, so i just add an ‘order’ parameter into that!  So my complete_stories association, in Article, looks like this:

  has_many :complete_stories,
:class_name => “Story”,
:conditions => [ ‘complete = ?’, true ],
:order => “points DESC, added_at DESC”

Sweet!  That’s a good point to end on today i think.

I’ve decided to convert all  movies to flv (flash video) format before saving them, inna youtube stylee.

This page seems to spell out how to do that – there’s a lot involved.  Here goes.

Step 1:  Get the source for ffmpeg from subversion.  It involves a checkout, which seems odd…i don’t want to check anything back in (real open source coding is still scary to me), i just want a copy.  Ah well, i’ll follow the instructions.

I made a new folder outside of my rails app (i hope this was the right thing to do) and in it typed this at the command line:

svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg

This copied over a load of stuff.  Next step is to configure and compile it.  This sounds complicated.   First hurdle – the instructions are all for mac!

Mencoder looks promising as well…

OK, going with a different guide

Step 1 – mencoder.  DLd and installed mplayer and mencoder and associated codecs.

Step 2 – test mencoder, with this, at the command line:

mencoder input.avi -o output.avi -oac lavc -ovc lavc -lavcopts vcodec=xvid:acodec=mp3 > output.txt

I get this error:

Cannot find codec ‘xvid’ in libavcodec…
Couldn’t open video filter ‘lavc’.
Failed to open the encoder.

After a bit of mucking around i got it to work (blob_woman2.avi is my test video, converted to output.avi using the lavc codec).  I had to remove the -oac lavc bit, and the -lavcopts vcodec etc bit as well.

mencoder blob_woman2.avi -ovc lavc -o output.avi  > output.txt 

It reduced to about 10% of previous size which is impressive, but i think it was an unoptimal avi in the first place.  Let’s try with a 237 meg divx movie now:  ok, it’s working….i’m getting messages about duplicate frames andthe occasional missing frame but it seems to be working away there…Finished!  237meg down to 130meg, that’s pretty good!  I had to specify an audio codec as well this time: lavc as well.

Following the instructions in the guide, the next thing to do is to incorporate it into rails.  Dmytro, who’s blog i’m using, sets up a helper method that takes a command line instruction, and generates a new type of exception (which is also defined but simply extends StandardError).  To be continued.

August 17, 2007

OK, got subversion installed, along with tortoise svn, which adds svn commands into windows explorer. I’m a little bit suprised that it (svn) doesn’t have a ‘proper’ interface – i was expecting something along the lines of visual source safe. I guess though that doing everything in windows explorer makes sense.

Anyway, the point of doing this was to get the movie playing stuff in. Let’s just try that command line before trying to work out how to do anything… it was

ruby script/plugin install svn://rubyforge.org/var/svn/flashplayrhelpr

Well, it did *something*. I got a load of stuff copied over, but then a cryptic message:

A C:\code\InstantRails\rails_apps\newspipe\vendor\plugins\flashplayrhelpr\install.rb

A C:\code\InstantRails\rails_apps\newspipe\vendor\plugins\flashplayrhelpr\MIT-LICENSE
A C:\code\InstantRails\rails_apps\newspipe\vendor\plugins\flashplayrhelpr\README
Exported revision 14.
Installing flash_player…
Plugin not found: [“svn://rubyforge.org/var/svn/flashplayrhelpr”]

What does that mean – do i have it or not? It was supposed (according to this guide) to make a folder called swf in public, but it didn’t. Goddammit, why isn’t anything ever easy? grrrr. ok, chill. The guide has a backup plan if the files don’t appear, which is to run this:

rake flash_player:install

which apparently makes the viewer available to use. Hmm. It didn’t complain when i ran this, but it didnt say it had done anything either. However when i did rake blahblah:install it complained that it couldn’t find blahblah so i guess it’s there in some form. Anyway, let’s try and use it.

I put this in my standard partial, which is the basis for all templates in my app:

<%= javascript_include_tag ‘ufo’ %>

then this in my movie/show view page:

<%= flv_player :file => “/data/movies/feynman.flv” %>

Here goes. Nope – i get this error message
undefined method `flv_player’ for #<#<Class:0x4815ad4>:0x4815aac>

which suggests it didn’t install.

Looking back at the messages, i think that the flash_player did install, but flashplayrhelper, which is a seperate plugin didn’t.

…some time passes…

Got it! After a helpful suggestion from the maker of flashplayrhelpr, Farooq Ali Farooq, I added the svn exe to my path variable, and forced reinstall – worked first time! Good old Feynman lecturing away there. Awesome.

There’s a little bit of tidying up required with the movie – it would be nice to be able to edit the associated details (title, summary etc) after posting for example. This isn’t essential though – what is essential is to be able to convert other video formats to flash video, so people can upload an avi, wmv etc. This is what YouTube does – you can upload various formats but everything is displayed in flv format.

This looks promising : http://www.danielfischer.com/2007/06/27/how-to-use-ffmpeg-to-convert-video-via-ruby-on-rails/

There’s quite a bit of pre-requisites but all stuff that would be useful i think: It seems perfect for my needs, dan even includes code for making a thumbnail. I’ll have a go with this (next post).

today – VIDEO

August 16, 2007

Can’t put it off any longer! It’s time to get that video functionality in there. As usual, its research time…

Preliminary research indicates that i should use a flash movie player, which can be embedded into html. This looks promising: http://flowplayer.org/ .

Toyed with the idea of rigging something up to show this in a page somewhere but thought i might as well do all the setup properly, since it’s going to be largely the same as for pictures except for a) where thumbnails come from (not so easy to generate for movies!) and b) the mechanics of actually SHOWing the movie, ie what happens on the movies/show view page.

So – that’s all set up, with blanks for generating and showing thumbnails, and for showing the movie.

Downloaded an flv (of the great Richard Feynman) from youtube using this handy website that let’s you rip flv movies off sites: http://keepvid.com/ . Also downloaded a free flv player just so i could check i actually had a working flv movie. I do.

OK, testing it – something weird’s happened – the movie has been saved as an ‘octet-stream’ – ah, wait, i think i was using imagemagick to save the full sized picture, i’ve just copied that over to movies, no wonder. I’ll change the controller method to just copy and rename a file rather than a picture. Actually i moved that to the model, let’s have a look: the current code says this:

#save the movie to newspipe/data/movies with the name ‘mov#{self.id}.extension’
new_filename = “mov#{self.id}.#{self.content_type.split(“/”).last}”
File.open(“#{RAILS_ROOT}/public/data/movies/#{new_filename}”, “wb”) { |f| f.write(self.file_data) }

Hmmm – that’s not using imagemagick! It’s just using File.open.  I think i need another way of saving a file without opening it.

In the meantime, been trying to get flowplayer working with a saved flv file.   Tested it outside of rails, it’s having an issue with security:

flash security hassle with flowplayer

I’ve mucked about with the flash player settings, to no avail.

Ah wait, found it:  in the ‘global security settings’ panel, added the location “C:\code\useful stuff\flowplayer” (where i was testing it out) to the ‘allowed to access’ locations list.  So, i get to the movie player now but it doesn’t play properly – i just get a black screen in the player, and a stripy progress bar, whatever that means.

When i try it in newspipe, then i don’t get anything at all – not even the player.

There doesn’t seem to be anything about flowplayer in rails on the net.  Going to try a different line of enquiry for now.

OK, this looks promising:  another flash player, this time the JW FLV player.   Someone’s done a rails plugin for it here.  Here goes.  Ran this at the command line:

ruby script/plugin install svn://rubyforge.org/var/svn/flashplayrhelpr

nothing happened.  This looks like it might be a subversion (svn) – based plugin – maybe it’s time for me to install subversion finally.