24 Eylül 2011 Cumartesi

Ruby On Rails Bölüm 2 İlişkisel Veritabanı

Merhaba,

http://volkanaltan.blogspot.com/2011/09/ruby-on-rails-bolum-1.html Burda yapılanları unutup tekrar projemizi oluşturup alanları yeniden gönül rahalığı ile doğru bir şekilde oluşturalım. İlk yazı başlangıç problemleriyle geçen bir yazı olduğu için kod yazmanın tadına varamadık burda ilkinden edindiğimiz tecrübeler ile daha hızlı ilerleyeceğiz.

Kaynak kod: cms

İşlemlerin detayları hakkında bilgimiz olduğu için (hangi dizinden yapıldığı gibi) sadece komutları topladım.
Ayrıca burda fazladan tablolar ve güncelleştirmeler olabilir bu sebepten dökümana sadık kalmanızı tavsiye ediyorum.
rails new cms -d mysql
#bir önceki yazıdan database.yml dosyası bilgilerini alıp buraya girdim
rails generate scaffold Content site_id:integer parent_id:integer category_id:integer title:string description:text status:boolean
rails generate scaffold ContentCategory title:string status:boolean
rails g scaffold Site parent_id:integer title:string url:string folder_name:string host_name:string status:boolean
rails g scaffold Language code:string
rails g scaffold ContentTranslation language_id:integer content_id:integer field_name:string content:text status:boolean
rake db:create
rake db:migrate
rails server


Yukardaki komutları çalıştırdığınızda ContentCategory isimli bir tablo değil content_categories isimli bir tablo oluştuğunu göreceksiniz. Çoğul isimlendirmeyi sizin yerinize Rails hallediyor.

Yukarda yapmaya çalıştığımız sistemi biraz anlatmaya çalışayım böylece ne yaptığımızdan haberimiz olsun :)

1. Yapacağımız sistem de tek kod altında çalışan bir sürü site olacak.
2. Her sitenin kendine ait birden fazla domaini olabilir.
3. Her site içeriğinin (content) birden fazla içerik kategorisiyle ilişkisi olabilir. (ContentCategory)
Ama bu ilişkileri şimdilik ayrı bir tabloda tutmayacağım.
4. Her içerik birden fazla dile sahip olabilir. Bunun için ContentTranslate var. field_name alanına title gibi bir field, content alanında ise bu field a ait içerik bilgisi barındırılacak. Her içeriğin birden fazla field_name ve content alanı olabilecek.
Ayrıca Dil tanımlamasıda burda yapılacak. Yani field_name'i title olan bir kayıt için Türkçe ve İngilizce olarak 2 kayıt girilecek.


Aşağıdaki şekilde browser ile data girişi yapabiliyor olmamız gerek. Sorun varsa ilk makaleye dönüp burayı kontrol edin.
http://localhost:3000/sites http://localhost:3000/sites/new
http://localhost:3000/contents http://localhost:3000/contents/new
http://localhost:3000/content_translates ..
http://localhost:3000/translates ..

Önce veritabanı ilişkileri olmaksızın direk console üzerinden data girişi yapalım.

[volkan@volkans-MacBook]~/Development/rubyonrails/cms% rails console
Loading development environment (Rails 3.1.0)
ruby-1.9.2-p290 :001 >


ruby-1.9.2-p290 :002 > site1 = Site.create(:title => "deneme sitesi", :url =>"deneme.com", :folder_name => "denemecom", :host_name => "deneme.com,test.deneme.com", :status => true)
(0.3ms) BEGIN
SQL (0.4ms) INSERT INTO `sites` (`created_at`, `folder_name`, `host_name`, `parent_id`, `status`, `title`, `updated_at`, `url`) VALUES ('2011-09-27 18:05:27', 'denemecom', 'deneme.com,test.deneme.com', NULL, 1, 'deneme sitesi', '2011-09-27 18:05:27', 'deneme.com')
(0.7ms) COMMIT
=> #


Oluşturduğumuz kayda erişip bir kaç ufak deneme yapalım.
ruby-1.9.2-p290 :005 > site1.title
=> "deneme sitesi"
ruby-1.9.2-p290 :006 > site1.title.length
=> 13
ruby-1.9.2-p290 :007 > site1.title.reverse
=> "isetis emened"
ruby-1.9.2-p290 :008 >


İçerik girişi

ruby-1.9.2-p290 :008 > content1 = Content.create(:site_id => 1, :category_id => 1, :title => "ilk icerik", :description => "aciklama")
(0.2ms) BEGIN
SQL (0.3ms) INSERT INTO `contents` (`category_id`, `created_at`, `description`, `parent_id`, `site_id`, `title`, `updated_at`) VALUES (2, '2011-09-27 18:13:25', 'aciklama', NULL, 2, 'ilk icerik', '2011-09-27 18:13:25')
(0.6ms) COMMIT
=> #


Şimdi site_id 1 olan kayıtları çağıralım.
ruby-1.9.2-p290 :008 > Site.find(1).contents
Site Load (0.5ms) SELECT `sites`.* FROM `sites` WHERE `sites`.`id` = 1 LIMIT 1
NoMethodError: undefined method `contents' for #


Ama görüldüğü üzere hataların ardı arkası kesilmedi. Çünkü şu anda bu ilişkiler tanımlanmamış.
önce exit yapıp console dan çıkalım.

ilk olarak bu dosyayı düzenleyelim.
app/models/site.rb

sites tablosuna bağlı birden fazla content olabilir. bunu has_many komutu ile tanımlıyoruz. Aynı zamanda özel olarak tanımlama yapabiliriz mesela Sites'a bağlı content çağrıldığında sadece şunları çağır gibi.
Bu özel tanımlama için conditions komutu kullanılıyor.

:conditions => ['status = ?', true]


class Site < ActiveRecord::Base   has_many :contents,            :foreign_key => "site_id"
end



app/models/content.rb

Bu dosyada ise ihtiyaçları tanımlıyoruz. Yani bu tablo şuna ihtiyaç duyar gibi.

class Content < ActiveRecord::Base   belongs_to :sites   has_many :content_translations   end 


Burda aynı zamanda çeviri tablosunuda tanımladık.

Şimdi tekrar console geçip aynı komutu yazalım.

-------------------------------Ara kod---------------------------------
Ben burda contents için status alanını oluşturmamıştım o nedenle hata verdi. Tabi yukarda var çünkü güncelledim :) Kısa güncelleştirmeler için şöyle yapıyorum.
rails g migration AlterContent

Dosyanın içini şu şekilde doldurdum
class AlterContent < ActiveRecord::Migration   def up     add_column :contents, :status, :boolean   end    def down     remove_colmn :contents, :status, :boolean   end end 


rake db:migrate

-------------------------------Ara kod Bitiş---------------------------------
ruby-1.9.2-p290 :001 > Site.find(1).contents
Site Load (0.5ms) SELECT `sites`.* FROM `sites` WHERE `sites`.`id` = 1 LIMIT 1
Content Load (0.4ms) SELECT `contents`.* FROM `contents` WHERE `contents`.`site_id` = 1 AND (status = 1)
=> []


Görüldüğü üzere Status = 1 eklenmiş. Bunu gördük site.rb dosyasından bu kısmı kaldırıyorum.
çıkıp tekrar girmemiz gerek. (Artık çıkıp tekrar girme gerektiği konusunu hatırlatmayacağım bunu aklınızda tutun :) )
ruby-1.9.2-p290 :001 > Site.find(1).contents
Site Load (0.5ms) SELECT `sites`.* FROM `sites` WHERE `sites`.`id` = 1 LIMIT 1
Content Load (0.4ms) SELECT `contents`.* FROM `contents` WHERE `contents`.`site_id` = 1
=> [#]


İlişkisini tanımladığımız tablolarda tek hamleyle aşağıdaki gibi istediğimiz kadar kayıt oluşturabiliriz.
Buda işleri hızlandırmada bize yardımcı olacak bir unsur.
ruby-1.9.2-p290 :003 > site = Site.create(:title => "site 4")
ruby-1.9.2-p290 :004 > content = site.contents.create(:title => "icerik 4")
(0.2ms) BEGIN
SQL (0.4ms) INSERT INTO `contents` (`category_id`, `created_at`, `description`, `parent_id`, `site_id`, `status`, `title`, `updated_at`) VALUES (NULL, '2011-09-27 19:28:03', NULL, NULL, 4, NULL, 'icerik 4', '2011-09-27 19:28:03')
(0.9ms) COMMIT
=> #

ruby-1.9.2-p290 :007 > content.content_translations.create(:language_id => 1, :field_name => "[title-1]", :content => "title1 in basligi")


Sanırım olayın mantığı anlaşıldı. Aynı şekilde hiç yeni site oluşturmadan kayıt çekip onun üstünden gitmek istersek
aşağıdaki komut işimizi görecek. Gerisi yukardaki gibi devam ediyor.
ruby-1.9.2-p290 :008 > site2 = Site.find(2)


Kaynak kod: cms

19 Eylül 2011 Pazartesi

Ruby On Rails Bölüm 1



ROR a başlayan biri olarak sanırım karşılaştığım şeyleri yazsam hem bana hemde başkalarına oldukça faydası olacak.
Yeni bir dil öğrenmenin adımlarını hızlıca ve gereksiz detaylara girmeden anlatmaya çalışacağım. Yazıda ayrıca yaptığım hataları sizinde yapmanız sağlanıyor böylece çözümüde aramaya yardım ediyorum çünkü ben o hatayı yaptım başkasıda yapabilir veya bana denk gelen bir taş olabilir(belki bana göre taştır)

Kullandığım işletim sistemi Mac OS Lion

Ruby : 1.9.2p290
Rails : 3.1.0
gem : 1.8.10
IDE : Netbeans


https://github.com/joshfng/railsready

Burda kurulum işlemlerini yaptıktan sonra logout yapıp yeni console ile rvm enter dediğinizde komutu bulamıyorsa PATH lerin eklendiği dosyaya elle müdahale etmeniz gerekiyor demektir.

~/.bash_profile de eklenen satırları ~/.zshrc içine atmam gerekti.


Sonrası burada : https://rvm.beginrescueend.com/rubies/default/
Böylece ruby versyon olayını istediğiniz gibi değiştirebileceksiniz.

Burda ana hedef Rails framework u kullanılarak bir içerik yönetim sistemi yazma.


[volkan@volkans-MacBook]~/Development/rubyonrails% rails new cms -d mysql


Şimdi bir sürü gereksinim kurup ayarlama yapılacak eğer bir hata meydana gelirse projeyi silip yeniden bu adıma dönün sorun büyük ihtimalle çözülebilir.

Veritabanı mysql olan bir cms projesi oluşturduk.
İpucu: Veritabanı ayarları için cms/config/database.yml

Şimdi projeyi aktif edelim. Bunun için proje içine girip şu komutu yazıyoruz.


[volkan@volkans-MacBook]~/Development/rubyonrails/cms% rails server


Browser dan http://localhost:3000/ dediğimizde işlem tamam.
Şimdi projemizi geliştirelim.

Database oluşturalım


~/Development/rubyonrails/cms% rake db:create


Öncelikle veritabanı yapısını oluşturalım. Tablo ve Field isimlendirmede şuralara göz atabilirsiniz.
http://itsignals.cascadia.com.au/?p=7

Alanlara vereceğiniz tipler için ise şurası iyi bir kaynak: http://www.orthogonalthought.com/blog/index.php/2007/06/mysql-and-ruby-on-rails-datatypes/


rails generate scaffold Content site_id:integer parent_id:integer category_id:integer title:string description:text

rails g scaffold Site parent_id:integer name:string title:string url:string status:boolean

rails g scaffold ContentTranslate translate_id:integer content_id:integer field:string content:text status:boolean

rails g scaffold Translate code:string


Şimdi bu tabloları db ye aktaralım. Eğer DB yoksa aşağıdaki komut eksik olur, öncesinde rake db:create çalıştırmak gerek.


rake db:migrate


Sonradan güncelleme yapmak istersek direk dosyalardan bunu yapıp tekrar migrate komutunu çalıştırmalıyız.
cms/db/migrate/20110920185806_create_contents.rb
Ama bu işe yaramayacaktır :) Nedeni aşağıda yazıyor.

db oluşturduktan sonra kurulacak ilişkileri anlamak için
http://guides.rubyonrails.org/association_basics.html
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Oluşturulan tabloda güncelleme yapmak için.


rails g migration FixColumnName


gibi bir isimle işlem dosyası oluşturuyoruz.


class FixColumnName < ActiveRecord::Migration def up change_table :volkans do |t| t.rename :content_id, :cmd_id end end def down end end
rake db:migrate


ile değişikliklerin db ye gitmesini sağlıyoruz. Yeni kolon eklemek ve daha fazla örnek için: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
http://stackoverflow.com/questions/1992019/how-to-rename-a-database-column-in-rails-using-migration

İpucu: Db de işlem yapmak için oluşturduğunuz dosya bir kere eşitleme yaptıktan sonra aynı komutla bir daha çalışmıyor.
Çünkü schema_migrations tablosunda işlemleri tutup dosyaların kullanılıp kullanılmadığını tutuyor. Böylece size eski sürümlere dönme imkanı tanyor. Yenilik için yeniden dosya oluşturmak gerek. içine up ve down isimli iki tanımlama yapıp istersek sadece up veya downu çalıştırabiliriz. Zaten varsayılan olarak up çalışıyor.

Örnek :

rake db:migrate:down VERSION=20110921190908


İlla son değişikliği yaptım dosya çalışsın istiyorsanız tablosundan 20110920205424 şu biçimdeli ilgili dosyanın bilgisini silin ve tekrar
rake db:migrate yazın. Bu kezde daha önceden yapılan değişiklikler için hata verecektir...


O nedenle en güzeli değişiklikler için yeni dosya oluşturmak.

Bu işlemlerden sonra oluşturduğumuz tablolara hızlıca data girmek için web sayfamızdan tablo adı ile giriş yapabiliyoruz.

http://localhost:3000/contents dediğimizde çalışacaktır.

http://localhost:3000/content_translate yazıp "new Content" oluşturmak istediğimizde hata veriyor.

"field_changed? is defined by ActiveRecord"

Şu hatanın sebebini bulmam biraz uzun sürdü ama sonunda buldum :) ROR tarafından reserve edilmiş isimlerden biri olan "field"
adını kullandığımız için bu hata geliyormuş. Bu konuya dikkat etmek gerektiğini hatırlatmakta fayda var. Ben field yerine field_name kullandım. Ama bu hatayı almanız için yukardaki kodu güncellemedim :)

Not: http://oldwiki.rubyonrails.org/rails/pages/ReservedWords

Projede değişiklik olduğu zaman silip yeniden oluşturmak düzenlemekten daha kolay hem başlarda olduğumuz için hemde
komutlar notlarım arasında (yukarda olduğundan yazmak kolay oluyor.)

Yeri gelmişken ROR da Kod tekrar etmeme muhabbetine girmekte fayda var. Amaç kısa kod yazarak çok iş yapmak bunun bir örneğini veritabanı dosyamızdaki ayarlarda yapalım.

config/database.yml dosyasında normalde geliştirme ortamları tanımlanıp tek tek bütün bilgiler giriliyor ama aşağıdaki gibi yaparsak ana tanımlamayı yapıp sadece değişen kısmı yeniden tanımlayarak bir nevi üzerine yazarak işimizi hızlandırabiliriz.


common: &shared
adapter: mysql2
encoding: utf8
reconnect: false
pool: 5
socket: /Applications/MAMP/tmp/mysql/mysql.sock
username: root
password: root

development:
database: cms_development
<<: *shared

test:
database: cms_test
<<: *shared

production:
database: cms_production
<<: *shared


1. Bölüm bu kadar. İnşallah devamı gelicek...


Kullanabileceğiniz kaynaklar:
http://www.quora.com/Ruby-on-Rails/
http://railscasts.com/
http://www.buildingwebapps.com/

Not: Az kalsın Onur Özgür ÖZKAN'a teşekkür etmeyi unutuyordum neyseki uyumadan önce hatırladım ve bu notu ekledim.