Hello Webiva! |
Backend Interface - Create a Poll ![]() |
Module Tutorial: User Polling / Rating: Add Models and Migrations
If our module is going to create some module-specific models, which it is, we’re going to need to be able to put some module-specific tables into the database. Webiva piggybacks on the existing migration system by allowing you to create module migrations that exist in the db/ subfolder of your module.
Creating the migration
There’s a module-specific migration generator you can use to create a migration. Let’s do that now from the top-level rails directory:
$ ./script/generate webiva_module_migration PollDemo initial_tables
You should see output something like this.
exists /vendor/modules/poll_demo
create /vendor/modules/poll_demo/db
create /vendor/modules/poll_demo/db/20091223195006_initial_tables.rb
Now lets open up the migration 20091223195006_initial_tables.rb file it created (the prefixed timestamp will be different) and create a couple of tables called poll_demo_polls and poll_demo_responses. Let’s keep them simple to start with, and just do the following:
/vendor/modules/poll_demo/db/20091223195006_initial_tables.rb
class InitialTables < ActiveRecord::Migration
def self.up
create_table :poll_demo_polls do |t|
t.string :name
t.string :question
t.text :answers
t.integer :response_count, :default => 0
t.text :results
t.timestamps
end
create_table :poll_demo_responses do |t|
t.integer :poll_demo_poll_id
t.integer :response
t.string :ip_address
t.timestamps
end
add_index :poll_demo_responses, :poll_demo_poll_id, :name => 'poll_id'
end
def self.down
drop_table :poll_demo_polls
drop_table :poll_demo_responses
end
end
You’ll notice we’ve prefixed the table names with the name of the module. This is a good idea to avoid naming conflicts, in case another module comes along and wants to create a different type of poll.
Running a module migration
The migrations for a module are automatically run when the module is activated, but since our module is already active we’ll need to run it manually. We can do this using the custom rake command cms:migrate_domain_components:
$ rake cms:migrate_domain_components
This will update all modules to the latest version of all their latest components. If we wanted to be a little more specific, we could pass any combination of DOMAIN_ID, COMPONENT and VERSION to run more specific migrations. Let’s say we wanted to modify the initial tables migration and rerun it. Your DOMAIN_ID in this case is likely ‘1’, so we could run:
$ rake cms:migrate_domain_components COMPONENT=poll_demo DOMAIN_ID=1 VERSION=0
$ rake cms:migrate_domain_components COMPONENT=poll_demo DOMAIN_ID=1
This migrates the component back down to version 0 and then back up, dropping the poll_demo_* tables and recreating them.
Creating the model classes
Now let’s create a couple of models for our module. As you would expect, models go in the vendor/modules/poll_demo/app/models directory. Like the tables, it’s a good idea to prefix the models with the module name so as to prevent any name space conflicts down the road. You could also put your module models in a Ruby module (e.g. PollDemo::Poll), though it’s a little more of a pain to work with when using relationships. In general, though, either way is fine.
Let’s create the responses model first, as it’s going to be very simple.
vendor/modules/poll_demo/app/models/poll_demo_response.rb
class PollDemoResponse < DomainModel
validates_presence_of :poll_demo_poll_id, :response
belongs_to :poll_demo_poll
end
This is pretty much a standard Rails model except that it inherits from DomainModel instead of ActiveRecord::Base. This new base class of DomainModel allows Webiva to have support for multiple databases, as well as a master database. Individual website models will inherit from DomainModel, and Webiva’s master database models inherit from SystemModel. For our PollDemoResponse file, we added a belongs_to for the as-of-yet-uncreated poll model, but otherwise that’s all we’ve got.
Next, let’s create the poll model, which is going to be a little more complicated. Like PollDemoResponse, the only thing that’s Webiva-specific about this model is that it inherits from DomainModel; everything else is just normal Rails and Ruby code dedicated to the Poll logic. Let’s show the whole model first, and then go over the pieces:
vendor/modules/poll_demo/app/models/poll_demo_poll.rb
class PollDemoPoll < DomainModel
has_many :poll_demo_responses
serialize :answers
serialize :results
validates_presence_of :name, :question, :answers_string
def answers_string=(val)
self.answers = val.strip.split("\n").map(&:strip).reject(&:blank?)
end
def answers_string; (self.answers||[]).join("\n"); end
def answer_options
idx=0;
self.answers.map { |answer| idx+=1; [answer,idx] }
end
def recalculate_results!
self.results = { }
responses = (self.poll_demo_responses.count(:id,:group => :response) || { }).to_hash
answers.each_with_index { |ans,idx| self.results[idx+1] = responses[idx+1] || 0 }
self.response_count = self.poll_demo_responses.count
self.save
end
def result_values
self.answers.map { |answer| self.results[answer] }
end
def add_response(response,ip=nil)
response = response.to_i
if response > 0 && response <= self.answers.length
self.poll_demo_responses.create(:response => response, :ip_address => ip)
recalculate_results!
else
false
end
end
end
The top of the class is just some standard Rails relation and validation stuff. To keep things as simple as we can, we’re going to store each poll question and set of possible answers in one model, along with a cache of the results, so we will be serializing answers and results into two :text fields. We have added an answers_string getter and setter, so we can store the answers in a serialized array on the model, but we’ll want to get and set them with a simple text area. For adding responses, we’ve added an answer_options method, which returns a select-friendly array.
Next, we have the recalculate_results! method, which will update the internal response_count field and the serialized hash results with the most up-to-date values. There are lots of different ways we could cache results and try to ensure consistency, but this way is nice and simple, and if there’s a race condition somewhere when adding a new response, it’ll get fixed the next time around.
Finally, we have an add_response method, which just takes an answer number (starting with 1) and adds a response into the system.
Using the console to exercise our models
Normally, we would have built some RSpec unit tests as this model was developed, but for brevity, let’s just run a few quick tests from the console to make sure it’s working. You can use the standard Rails console with Webiva, but you’ll need to called DomainModel.activate_domain(DOMAIN_ID) before you access any DomainModels. Let try adding a poll and checking the results. Note: we are using DOMAIN_ID 1 in this example, although yours could be different.
$ ./script/console
Loading development environment (Rails 2.3.4)
>> DomainModel.activate_domain(1)
=> true
>> poll = PollDemoPoll.create(:name => 'Test Poll',
:question => 'What operating system do you use?',
:answers_string => "Windows\nMac OS X\nLinux\nOther")
=> #<PollDemoPoll id: 2, name: "Test Poll", ...snip...
>> poll.answers
=> ["Windows", "Mac OS X", "Linux", "Other"]
>> [3, 1, 3, 2, 5, 0].map { |response| poll.add_response(response) }
=> [[], [], [], [], false, false]
>> poll.response_count
=> 4
>> poll.results
=> {1=>1, 2=>1, 3=>2, 4=>0}
Looks good - we were able to create a poll by passing a name, question, and answer string (with each answer separated by a newline). We then checked that the answers were saved correctly as an array. We then tried adding 6 responses (only the first four of which were valid), and the add_response method correctly returned [] for valid responses and false for invalid responses. Next, we checked the response_count and results hash and both were in line with what we expected.
Alright, now that we have a couple of data models, the next step is to add a management interface to allow us to create polls from the backend of the website.
Hello Webiva! |
Backend Interface - Create a Poll ![]() |

Hello Webiva!