Forums » Ruby

Capítulo 7 - Active Record

    • 467 posts
    23 de julho de 2013 01:59:27 ART
    Capítulo 7

    Active Record

    "Não se deseja aquilo que não se conhece"

    Nesse capítulo começaremos a desenvolver um sistema utilizando Ruby on Rails com recursos mais avançados.

    7.1 - Motivação

    Queremos criar um sistema de qualificação de restaurantes. Esse sistema terá clientes que qualificam os restaurantes visitados com uma nota, além de informar quanto dinheiro gastaram. Os clientes terão a possibilidade de deixar comentários para as qualificações feitas por eles mesmos ou a restaurantes ainda não visitados. Além disso, os restaurantes terão pratos, e cada prato a sua receita.

    O site http://www.tripadvisor.com possui um sistema similar para viagens, onde cada cliente coloca comentários sobre hotéis e suas visitas feitas no mundo inteiro.

    7.2 - Exercícios: Controle de Restaurantes

    1. Crie um novo projeto chamado vota_prato:

      1. No terminal, garanta que não está no diretoria do projeto anterior.
      2. Digite o comando rails new vota_prato -d mysql
      3. Observe o log de criação do projeto:console.png

    Nova editora Casa do Código com livros de uma forma diferente

    Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não conhecem programação para revisar os livros tecnicamente a fundo. Não têm anos de experiência em didáticas com cursos.
    Conheça a Casa do Código, uma editora diferente, com curadoria da Caelume obsessão por livros de qualidade a preços justos.

    Casa do Código, ebook com preço de ebook.

    7.3 - Modelo - O "M" do MVC

    Models são os modelos que serão usados nos sistemas: são as entidades que serão armazenadas em um banco. No nosso sistema teremos modelos para representar um Cliente, um Restaurante e uma Qualificação, por exemplo.

    O componente de Modelo do Rails é um conjunto de classes que usam oActiveRecord, uma classe ORM que mapeia objetos em tabelas do banco de dados. O ActiveRecord usa convenções de nome para determinar os mapeamentos, utilizando uma série de regras que devem ser seguidas para que a configuração seja a mínima possível.

    ORM

    ORM (Object-Relational Mapping) é um conjunto de técnicas para a transformação entre os modelos orientado a objetos e relacional.

    7.4 - ActiveRecord

    É um framework que implementa o acesso ao banco de dados de forma transparente ao usuário, funcionando como um Wrapper para seu modelo. Utilizando o conceito de Conventions over Configuration, o ActiveRecord adiciona aos seus modelos as funções necessárias para acessar o banco.

    ActiveRecord::Base é a classe que você deve estender para associar seu modelo com a tabela no Banco de Dados.

    7.5 - Rake

    Rake é uma ferramenta de build, escrita em Ruby, e semelhante ao make e aoant, em escopo e propósito.

    Rake tem as seguintes funcionalidades:

    • Rakefiles (versão do rake para os Makefiles) são completamente definidas em sintaxe Ruby. Não existem arquivos XML para editar, nem sintaxe rebuscada como a do Makefile para se preocupar.
    • É possível especificar tarefas com pré-requisitos.
    • Listas de arquivos flexíveis que agem como arrays, mas sabem como manipular nomes de arquivos e caminhos (paths).
    • Uma biblioteca de tarefas pré-compactadas para construir rakefiles mais facilmente.

    Para criar nossas bases de dados, podemos utilizar a rake task db:create. Para isso, vá ao terminal e dentro do diretório do projeto digite:

    $ rake db:create
    

    Rails criará dois databases no mysql: vota_prato_development,vota_prato_test.

    show-databases.png

    rake db:create:all

    Uma outra tarefa disponível em uma aplicação Rails e a rake db:create:all. Além de criar os dois databases já citados, ela também é responsável por criar o database vota_prato_production. No ambiente de desenvolvimento, é bem comum trabalharmos apenas com os databases de teste e desenvolvimento.

    Para ver todas as tasks rake disponíveis no seu projeto podemos usar o comando (na raiz do seu projeto):

    $ rake -T
    

    rake-t.png

    Já conhece os cursos online Alura?

    Alura oferece dezenas de cursos online em sua plataforma exclusiva de ensino que favorece o aprendizado com a qualidade reconhecida da Caelum. Você pode escolher um curso nas áreas de Java, Ruby, Web, Mobile, .NET e outros, ou fazer a assinatura semestral que dá acesso a todos os cursos.

    Conheça os cursos online da Caelum.

    7.6 - Criando Modelos

    Agora vamos criar o modelo do Restaurante. Para isso, temos um gerador específico para model através do comando rails generate model restaurante.

    Repare que o Rails gerou uma série de arquivos para nós.

    console-model-restaurante.png

    7.7 - Migrations

    Migrations ajudam a gerenciar a evolução de um esquema utilizado por diversos bancos de dados. Foi a solução encontrada para o problema de como adicionar uma coluna no banco de dados local e propagar essa mudança para os demais desenvolvedores de um projeto e para o servidor de produção.

    Com as migrations, podemos descrever essas transformações em classes que podem ser controladas por sistemas de controle de versão (por exemplo, git) e executá-las em diversos bancos de dados.

    Sempre que executarmos a tarefa Generator -> model, o Rails se encarrega de criar uma migration inicial, localizado em db/migrate.

    ActiveRecord::Migration é a classe que você deve estender ao criar uma migration.

    Quando geramos nosso modelo na seção anterior, Rails gerou para nós uma migration (db/migrate/<timestamp>_create_restaurantes.rb). Vamos agora editar nossa migration com as informações que queremos no banco de dados.

    Queremos que nosso restaurante tenha um nome e um endereço. Para isso, devemos acrescentar as chamadas de método abaixo:

    t.string :nome, limit: 80
    t.string :endereco
    

    Faça isso dentro do método change da classe CreateRestaurantes. Sua migration deverá ficar como a seguir:

    class CreateRestaurantes < ActiveRecord::Migration
      def change
        create_table :restaurantes do |t|
          t.string :nome, limit: 80
          t.string :endereco
          t.timestamps
        end
      end
    end
    

    Supondo que agora lembramos de adicionar a especialidade do restaurante. Como fazer? Basta usar o outro gerador (Generator) do rails que cria migration. Por exemplo:

    $ rails generate migration add_column_especialidade_to_restaurante especialidade
    

    Um novo arquivo chamado<timestamp>_add_column_especialidade_to_restaurante.rb será criado peloRails, e o código da migração gerada será como a seguir:

    class AddColumnEspecialidadeToRestaurante < ActiveRecord::Migration
      def change
        add_column :restaurantes, :especialidade, :string
      end
    end
    

    Note que através do nome da migração e do parâmetro que passamos no gerador, o nome da coluna que queremos adicionar, nesse caso especialidade, oRails deduziu que a migração é para adicionar a coluna e já fez o trabalho braçal para você!

    Vamos apenas adicionar mais alguns detalhes a essa migração, vamos limitar o tamanho da string que será armazenada nesse campo para 40 caracteres:

    class AddColumnEspecialidadeToRestaurante < ActiveRecord::Migration
      def change
        add_column :restaurantes, :especialidade, :string, limit: 40
      end
    end
    

    7.8 - Exercícios: Criando os modelos

    1. Crie nosso banco de dados

      1. Entre no terminal, no diretório do projeto
      2. execute o comando:
      $ rake db:create
      

      No terminal, acesse o mysql e verifique que os databases foram criados:

      $ rails dbconsole
      mysql> show databases;
      mysql> quit
      
    2. Crie o modelo do Restaurante

      1. Novamente no Terminal, no diretório do projeto
      2. execute rails generate model restaurante
    3. Edite seu script de migração do modelo "restaurante" para criar os campos nome e endereço:

      1. Abra o arquivo db/migrate/<timestamp>_create_restaurantes.rb
      2. Adicione as linhas:
      t.string :nome, limit: 80
      t.string :endereco
      

      O código final de sua migration deverá ser como o que segue:

      class CreateRestaurantes < ActiveRecord::Migration
        def change
          create_table :restaurantes do |t|
            t.string :nome, limit: 80
            t.string :endereco
            t.timestamps
          end
        end
      end
      
    4. Migre as tabelas para o banco de dados:

      1. Vá ao Terminal
      2. Execute a tarefa db:migrate:
      $ rake db:migrate
      

      Verifique no banco de dados se as tabelas foram criadas:

      $ rails dbconsole
      mysql> use vota_prato_development;
      mysql> desc restaurantes;
      mysql> quit
      
    5. Adicione a coluna especialidade ao nosso modelo "restaurante":

      1. Vá novamente ao Terminal e digite:
        $ rails generate migration add_column_especialidade_to_restaurante especialidade
        
      2. Abra o arquivodb/migrate/<timestamp>_add_column_especialidade_to_restaurante.rb
      3. Altere o limite de caracteres da coluna especialidade como a seguir:
        add_column :restaurantes, :especialidade, :string, limit: 40
        
        Seu arquivo ficará assim:
        class AddColumnEspecialidadeToRestaurante < ActiveRecord::Migration
          def change
            add_column :restaurantes, :especialidade, :string, limit: 40
          end
        end
        
      4. Para efetivar a mudança no banco de dados execute a seguinte tarefa:
        rake db:migrate
        
        A saída do comando será semelhante a seguinte:
        ==  CreateRestaurantes: migrating =============================================
        -- create_table(:restaurantes)
           -> 0.7023s
        ==  CreateRestaurantes: migrated (0.7025s) ====================================
        
        ==  AddColumnEspecialidadeToRestaurante: migrating ============================
        -- add_column(:restaurantes, :especialidade, :string, {:limit=>40})
           -> 0.9402s
        ==  AddColumnEspecialidadeToRestaurante: migrated (0.9404s) ===================
        
      5. Olhe novamente no banco de dados, veja que as tabelas foram realmente criadas.
        $ rails dbconsole
        mysql> use vota_prato_development;
        mysql> show tables;
        
        E o resultado será semelhante ao seguinte:
        | Tables_in_vota_prato_development |
        
        | restaurantes                     |
        | schema_migrations                |
        
        2 rows in set (0.00 sec)
        
    6. (Opcional) Utilizamos o método add_column na nossa migration para adicionar uma nova coluna. O que mais poderia ser feito? Abra a documentação e procure pelo módulo ActiveRecordConnectionAdaptersSchemaStatements.

    Você não está nessa página a toa

    Você chegou aqui porque a Caelum é referência nacional em cursos de Java, Ruby, Agile, Mobile, Web e .NET.
    Faça curso com quem escreveu essa apostila.

    Consulte as vantagens do curso Desenv. Ágil para Web com Ruby on Rails.

    7.9 - Manipulando nossos modelos pelo console

    Podemos utilizar o console para escrever comandos Ruby, e testar nosso modelo. A grande vantagem disso, é que não precisamos de controladores ou de uma view para testar se nosso modelo funciona de acordo com o esperado e se nossas regras de validação estão funcionando. Outra grande vantagem está no fato de que se precisarmos manipular nosso banco de dados, ao invés de termos de conhecer a sintaxe sql e digitar a query manualmente, podemos utilizar código ruby e manipular através do nosso console.

    Para criar um novo restaurante, podemos utilizar qualquer um dos jeitos abaixo:

    r = Restaurante.new
    r.nome = "Fasano"
    r.endereco = "Av. dos Restaurantes, 126"
    r.especialidade = "Comida Italiana"
    r.save
    
    r = Restaurante.new do |r|
      r.nome = "Fasano"
      r.endereco = "Av. dos Restaurantes, 126"
      r.especialidade = "Comida Italiana"
    end
    r.save
    

    Uma outra forma possível para a criação de objetos do Active Record é usando um hash como parâmetro no construtor. As chaves do hash precisam coincidir com nomes das propriedades as quais queremos atribuir os valores. Veja o exemplo a seguir:

    r = Restaurante.new nome: "Fasano", 
                        endereco: "Av. dos Restaurantes, 126", 
                        especialidade: "Comida Italiana"
    r.save
    

    Porém o Active Record tem uma restrição quanto ao uso dessa funcionalidade. É preciso que você especifique exatamente quais são as propriedades que podem ter seu valor atribuído a partir do hash.

    É preciso dizer isso explicitamente através da invocação do métodoattr_accessible. Vamos editar a classe Restaurante para que o código fique como o seguinte:

    class Restaurante < ActiveRecord::Base
      attr_accessible :nome, :endereco, :especialidade
    end
    

    Pronto! Agora você já pode criar objetos com as propriedades populadas através de um hash. Além disso, também pode invocar um método de classe emRestaurante que cria um objeto e já armazena os dados no banco, sem precisar da invocação ao método save, veja:

    Restaurante.create nome: "Fasano",
                       endereco: "Av. dos Restaurantes, 126", 
                       especialidade: "Comida Italiana"
    

    mass assignment

    Essa ideia de precisar especificar quais campos podem ser atribuídos através de um hash é referida por muitos como "mass assignment whitelist". A ideia é que o programador deve decidir quais propriedades de um objeto podem ser atribuídas diretamente.

    Em versões anteriores do framework, toda propriedade era passível de atribuição, a não ser que o programador se lembrasse de adicionar uma invocação ao método attr_accessible. O problema disso é que, se por algum descuido, o programador esquecer de estabelecer exatamente quais propriedades estão abertas a atribuição, algumas falhas de segurança podem ocorrer.

    Atualmente o padrão é justamente o inverso. Nenhuma propriedade pode ser atribuída diretamente a partir dos valores de um hash, caso queira isso, é preciso especificar através da invocação do attr_accessible.

    Você pode ler mais a respeito nesse post:http://blog.caelum.com.br/seguranca-de-sua-aplicacao-e-os-frameworks-ataque-ao-github/

    Note que o comando save efetua a seguinte ação: se o registro não existe no banco de dados, cria um novo registro; se já existe, atualiza o registro existente.

    Existe também o comando save!, que tenta salvar o registro, mas ao invés de apenas retornar "false" se não conseguir, lança a exceção RecordNotSaved.

    Para atualizar um registro diretamente no banco de dados, podemos fazer:

    Restaurante.update(1, {nome: "1900"})
    

    Para atualizar múltiplos registros no banco de dados:

    Restaurante.update_all("especialidade = 'Massas'")
    

    Ou, dado um objeto r do tipo Restaurante, podemos utilizar:

    r.update_attribute(:nome, "1900")
    
    r.update_attributes nome: "1900", especialidade: "Pizzas"
    

    Existe ainda o comando update_attributes!, que chama o comando save! ao invés do comando save na hora de salvar a alteração.

    Para remover um restaurante, também existem algumas opções. TodoActiveRecord possui o método destroy:

    restaurante = Restaurante.first
    restaurante.destroy
    

    Para remover o restaurante de id 1:

    Restaurante.destroy(1)
    

    Para remover os restaurantes de ids 1, 2 e 3:

    restaurantes = [1,2,3]
    Restaurante.destroy(restaurantes)
    

    Para remover todos os restaurantes:

    Restaurante.destroy_all
    

    Podemos ainda remover todos os restaurantes que obedeçam determinada condição, por exemplo:

    Restaurante.destroy_all(especialidade: "italiana")
    

    Os métodos destroy sempre fazem primeiro o find(id) para depois fazer odestroy(). Se for necessário evitar o SELECT antes do DELETE, podemos usar o método delete():

    Restaurante.delete(1)
    

    7.10 - Exercícios: Manipulando registros

    Teste a manipulação de registros pelo console.

    1. Edite o arquivo localizado em app/models/restaurante.rb. Na declaração da classe invoque o método attr_accessible como no exemplo abaixo:

      class Restaurante < ActiveRecord::Base
        attr_accessible :nome, :endereco, :especialidade
      end
      
    2. Insira um novo restaurante

      1. Para ter acesso ao Console, basta digitar rails console no Terminalconsole-view.png
      2. Digite:
        r = Restaurante.new nome: "Fasano", 
                            endereco: "Av. dos Restaurantes, 126", 
                            especialidade: "Comida Italiana"
        
      3. Olhe seu banco de dados:
        $ rails dbconsole
        mysql> select * from restaurantes;
        
      4. Volte para o Console e digite:
        r.save
        
      5. Olhe seu banco de dados novamente:
        $ rails dbconsole
        mysql> select * from restaurantes;
        
    3. Atualize seu restaurante

      1. Digite:
        r.update_attributes nome: "1900"
        
      2. Olhe seu banco de dados novamente:
        $ rails dbconsole
        mysql> select * from restaurantes;
        
    4. Vamos remover o restaurante criado:

      1. Digite
        Restaurante.destroy(1)
        
      2. Olhe seu banco de dados e veja que o restaurante foi removido
        $ rails dbconsole
        mysql> select * from restaurantes;
        

    7.11 - Exercícios Opcionais

    1. Teste outras maneiras de efetuar as operações do exercício anterior.

    Seus livros de tecnologia parecem do século passado?

    Conheça a Casa do Código, uma nova editora, com autores de destaque no mercado, foco em ebooks (PDF, epub, mobi), preçosimbatíveis e assuntos atuais.
    Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente para livros de tecnologia no Brasil. Conheça os títulos e a nova proposta, você vai gostar.

    Casa do Código, livros para o programador.

    7.12 - Finders

    O ActiveRecord possui o método "find" para realizar buscas. Esse método, aceita os seguintes parâmetros:

    Restaurante.all   # retorna todos os registros
    Restaurante.first # retorna o primeiro registro
    Restaurante.last  # retorna o último registro
    

    Ainda podemos passar para o método find uma lista com os id's dos registros que desejamos:

    r = Restaurante.find(1)
    varios = Restaurante.find(1,2,3)
    

    Além desses, podemos definir condições para nossa busca (como o SELECT do MySQL). Existem diversas formas de declararmos essas condições:

    Restaurante.where("nome = 'Fasano' and especialidade = 'massa'")
    Restaurante.where(["nome = ? and especialidade = ?",
        'Fasano', 'massa'])
    Restaurante.where(["nome = :nome and especialidade = :especialidade", {
          nome: "Fasano", especialidade: "Massa"}])
    Restaurante.where({nome: "Fasano", especialidade: "massa" })
    

    Essas quatro formas fazem a mesma coisa. Procuram por registros com o camponome = "Fasano" e o campo especialidade = "massa".

    Existem ainda os chamados dynamic finders:

    Restaurante.where(["nome = ? AND especialidade = ?",
        "Fasano", "italiana"])
    

    poderia ser escrito como:

    find_all_by_nome_and_especialidade("Fasano", "italiana")
    

    Temos ainda o "find_or_create_by", que retorna um objeto se ele existir, caso contrário, cria o objeto no banco de dados e retorna-o:

    Restaurante.find_or_create_by_nome("Fasano")
    

    Para finalizar, podemos chamar outros métodos encadeados para fazer querys mais complexas:

    • .order - define a ordenação. Ex: "created_at DESC, nome".
    • .group - nome do atributo pelo qual os resultados serão agrupados. Efeito idêntico ao do comando SQL GROUP BY.
    • .limit - determina o limite do número de registros que devem ser retornados
    • .offset - determina o ponto de início da busca. Ex: para offset = 5, iria pular os registros de 0 a 4.
    • .include - permite carregar relacionamentos na mesma consulta usando LEFT OUTER JOINS.

    Exemplo mais completo:

    Restaurante.where('nome like :nome', {nome: '%teste%'}).
      order('nome DESC').limit(20)
    

    Para Sabe Mais - Outras opções para os finders

    Existem mais opções, como o ":lock", que podem ser utilizadas, mas não serão abordadas nesse curso. Você pode consultá-las na documentação da API do Ruby on Rails.

    7.13 - Exercícios: Buscas dinâmicas

    1. Vamos testar os métodos de busca:

      1. Abra o console (rails console no Terminal)
      2. Digite:
        Restaurante.first
        
      3. Aperte enter
      4. Digite:
        Restaurante.all
        
      5. Aperte enter
      6. Digite:
        Restaurante.find(1)
        
      7. Aperte enter
      8. Digite:
        Restaurante.where(["nome = ? AND especialidade = ?",
                                             "Fasano", "Comida Italiana"])
        
      9. Aperte enter
      10. Digite:
        Restaurante.find_all_by_nome_and_especialidade("Fasano",
                                            "Comida Italiana")
        
      11. Aperte enter
      12. Digite:
        Restaurante.order("especialidade DESC").limit(1)
        
      13. Aperte enter

    7.14 - Validações

    Ao inserir um registro no banco de dados é bem comum a entrada de dados inválidos.

    Existem alguns campos de preenchimento obrigatório, outros que só aceitem números, que não podem conter dados já existentes, tamanho máximo e mínimo etc.

    Para ter certeza que um campo foi preenchido antes de salvar no banco de dados, é necessário pensar em três coisas: "como validar a entrada?", "qual o campo a ser validado?" e "o que acontece ao tentar salvar uma entrada inválida?".

    Para validar esses registros, podemos implementar o método validate em qualquer ActiveRecord, porém o Rails disponibiliza alguns comandos prontos para as validações mais comuns. São eles:

    • validates_presence_of: verifica se um campo está preenchido;
    • validates_size_of: verifica o comprimento do texto do campo;
    • validates_uniqueness_of: verifica se não existe outro registro no banco de dados que tenha a mesma informação num determinado campo;
    • validates_numericality_of: verifica se o preenchimento do campo é numérico;
    • validates_associated: verifica se o relacionamento foi feito corretamente;
    • etc...

    Todos estes métodos disponibilizam uma opção (:message) para personalizar a mensagem de erro que será exibida caso a regra não seja cumprida. Caso essa opção não seja utilizada, será exibida uma mensagem padrão.

    Toda mensagem de erro é gravada num hash chamado errors, presente em todo ActiveRecord.

    Além dos validadores disponibilizados pelo rails, podemos utilizar um validador próprio:

    validate :garante_alguma_coisa
    
    def garante_alguma_coisa
      errors.add_to_base("Deve respeitar nossa regra") unless campo_valido?
    end
    

    Repare que aqui, temos que incluir manualmente a mensagem de erro padrão do nosso validador.

    Se quisermos que o nome do nosso restaurante comece com letra maiúscula, poderíamos fazer:

    validate :primeira_letra_deve_ser_maiuscula
    
    private
    def primeira_letra_deve_ser_maiuscula
      errors.add("nome",
          "primeira letra deve ser maiúscula") unless nome =~ /[A-Z].*/
    end
    

    Além dessas alternativas, o Rails 3 trouxe uma nova maneira de criar validações que facilita os casos onde temos vários validadores para o mesmo campo, como por exemplo:

    validates :nome, presence: true, uniqueness: true, length: {maximum: 50}
    

    equivalente a:

    validates_presence_of :nome
    validates_uniqueness_of :nome
    validates_length_of :nome, :maximum => 50
    

    Modificadores de acesso

    Utilizamos aqui, o modificador de acesso private. A partir do ponto que ele é declarado, todos os métodos daquela classe serão privados, a menos que tenha um outro modificador de acesso que modifique o acesso a outros métodos.

    Validadores prontos

    Esse exemplo poderia ter sido reescrito utilizando o validador"validates_format_of", que verifica se um atributo confere com uma determinada expressão regular.

    Encoding de arquivos no Ruby e as classes de Model

    Devido ao encoding dos arquivos no Ruby 1.9 ser como padrão ASCII-8BIT, ao utilizarmos algum carácter acentuado no código fonte, é necessário indicarmos um encoding que suporte essa acentuação. Geralmente, esse encoding será o UTF-8. Portanto, temos que adicionar no começo do arquivo fonte a linha:

    #encoding: utf-8
    

    Agora é a melhor hora de aprender algo novo

    Se você gosta de estudar essa apostila aberta da Caelum, certamente vai gostar dos novos cursos online que lançamos na plataforma Alura. Você estuda a qualquer momento com aqualidade Caelum.

    Conheça a assinatura semestral.

    7.15 - Exercícios: Validações

    1. Para nosso restaurante implementaremos a validação para que o campo nome, endereço e especialidade não possam ficar vazios, nem que o sistema aceite dois restaurantes com o mesmo nome e endereço.

      1. Abra o modelo do restaurante (app/models/restaurante.rb)
      2. inclua as validações:
        validates_presence_of :nome, message: "deve ser preenchido"
        validates_presence_of :endereco, message: "deve ser preenchido"
        validates_presence_of :especialidade, message: "deve ser preenchido"
          
        validates_uniqueness_of :nome, message: "nome já cadastrado"
        validates_uniqueness_of :endereco, message: "endereço já cadastrado"
        
      3. Por utilizarmos acentuação em nosso arquivo de modelo, adicione a diretiva (comentário) que indica qual o encoding em que o Ruby deve interpretar no início do seu arquivo.
        #encoding: utf-8
        
    2. Inclua a validação da primeira letra maiúscula:

      validate :primeira_letra_deve_ser_maiuscula
      
      private
      def primeira_letra_deve_ser_maiuscula
        errors.add(:nome,
            "primeira letra deve ser maiúscula") unless nome =~ /[A-Z].*/
      end
      
    3. Agora vamos testar nossos validadores:

      1. Abra o Terminal
      2. Entre no Console (rails console)
      3. Digite:
        r = Restaurante.new nome: "fasano", 
              endereco: "Av. dos Restaurantes, 126"
        r.save
        
      4. Verifique a lista de erros, digitando:
        r.valid?          # verifica se objeto passa nas validações
        r.errors.empty?   # retorna true/false indicando se há erros ou não
        r.errors.count    # retorna o número de erros
        r.errors[:nome]   # retorna apenas o erro do atributo nome
          
        r.errors.each {|field, msg| puts "#{field} - #{msg}"}
        

    7.16 - Pluralização com inflections

    Repare que o Rails pluralizou o termo restaurante automaticamente. Por exemplo, o nome da tabela é restaurantes. O Rails se baseia nas regras gramaticais da lingua inglesa para fazer essa pluralização automática.

    Apesar de restaurante ter sido plurificado corretamente, outros termos comoqualificacao por exemplo, será plurificado para qualificacaos, ao invés dequalificacoes. Podemos fazer o teste no console do Rails, a String tem os métodospluralize e singularize para fazer, respectivamente, pluralização e singularização do termo:

    "qualificacao".pluralize # => "qualificacaos"
    "qualificacoes".singularize # => "qualificacoe"
    

    Outro termo onde encontraremos problema é em "receita":

    "receita".pluralize # => "receita"
    "receitas".singularize # => "receita"
    

    Apesar da singularização estar correta, a pluralização não ocorre, isso acontece por que de acordo com as regras gramaticais do inglês, a palavra receita seria plural de receitum. Testando no console obteremos o seguinte resultado:

    "receitum".pluralize # => "receita"
    

    Ou seja, teremos que mudar as regras padrões utilizadas pelo Rails. Para isso, iremos editar o arquivo config/initializers/inflections.rb, adicionando uma linha indicando que a pluralização das palavras qualificacao e receita é, respectivamente, qualificacoes e receitas:

    activesupport::inflector.inflections do |inflect|
      inflect.irregular 'qualificacao', 'qualificacoes'
      inflect.irregular 'receita', 'receitas'
    end
    

    Feito isso, podemos testar nossas novas regras utilizando o console:

    "qualificacao".pluralize # => "qualificacoes"
    "qualificacoes".singularize # => "qualificacao"
    "receita".pluralize # => "receitas"
    "receitas".singularize # => "receita"
    

    7.17 - Exercícios - Completando nosso modelo

    1. Vamos corrigir a pluralização da palavra 'receita'

      1. Abra o arquivo "config/initializers/inflections.rb"
      2. Adicione as seguintes linhas ao final do arquivo:
        ActiveSupport::Inflector.inflections do |inflect|
          inflect.irregular 'receita', 'receitas'
        end
        

        brazilian-rails

        Existe um plugin chamado Brazilian Rails que é um conjunto de gems para serem usadas com Ruby e com o Ruby on Rails e tem como objetivo unir alguns recursos úteis para os desenvolvedores brasileiros.

      1. Execute o console do Rails:
        $ rails console
        
      2. No Console do Rails digite:
        "receita".pluralize
        
    2. Vamos criar o nosso modelo Cliente, bem como sua migration:

      1. No terminal, digite:
        $ rails generate model cliente
        
        model-cliente.png
      2. Abra o arquivo "db/migrate/<timestamp>_create_clientes.rb"
      3. Edite método change para que ele fique como a seguir:
        create_table :clientes do |t|
          t.string :nome, limit: 80
          t.integer :idade
          t.timestamps
        end
        
        migration-cliente.png
      4. Vamos efetivar a migration usando o comando:
        $ rake db:migrate
        
      5. Olhe no console o que foi feitoconsole-clientes.png
      6. Olhe no banco de dados!table-clientes.png
    3. Configure as propriedades adequadas para possibilitar o mass assignment na classe Cliente. Edite a definição criada no arquivo app/models/cliente.rb para adicionar a chamada ao attr_accessible:

      class Cliente < ActiveRecord::Base
        attr_accessible :nome, :idade
      end
      
    4. Vamos agora fazer as validações no modelo "cliente":

      1. Abra o arquivo "app/models/cliente.rb"
      2. Adicione as seguintes linhas:
        validates_presence_of :nome, message: " - deve ser preenchido"
        
        validates_uniqueness_of :nome, message: " - nome já cadastrado"
        
        validates_numericality_of :idade, greater_than: 0, 
                                          less_than: 100,
                                          message: " - deve ser um número entre 0 e 100"
        
        model2-cliente.png
    5. Vamos criar o modelo Prato, bem como sua migration:

      1. Vamos executar o generator de model do rails novamente.
      2. rails generate model prato no Terminal
      3. Abra o arquivo "db/migrate/<timestamp>_create_pratos.rb"
      4. Adicione as linhas:
        t.string :nome, limit: 80
        
      5. Volte para o Terminal
      6. execute rake db:migrate
      7. Olhe no console o que foi feito
      8. Olhe no banco de dados!
    6. Vamos definir que o nome de um prato que pode ser atribuído via hash como parâmetro no construtor, edite o arquivo app/models/prato.rb e adicione a invocação ao attr_accessible:

      attr_accessible :nome
      
    7. Vamos agora fazer as validações no modelo "prato". Ainda no arquivoapp/models/prato.rb adicione o seguinte código:

      validates_presence_of :nome, message: " - deve ser preenchido"
      
      validates_uniqueness_of :nome, message: " - nome já cadastrado"
      
    8. Vamos criar o modelo Receita, bem como sua migration:

      1. Vá ao Terminal
      2. Execute rails generate model receita
      3. Abra o arquivo "db/migrate/<timestamp>_create_receitas.rb"
      4. Adicione as linhas:
        t.integer :prato_id
        t.text :conteudo
        
      5. Volte ao Terminal
      6. Execute rake db:migrate
      7. Olhe no console o que foi feito
      8. Olhe no banco de dados!
    9. Complete a classe Receita definindo que prato_id e conteudo serão passíveis de atribuição via mass assignment, lembra como se faz? Edite a classe Receitasdefinida em app/models/receita.rb e adicione a seguinte linha:

      class Receita
        attr_accessible :prato_id, :conteudo
      end
      
    10. Vamos agora fazer as validações no modelo "receita":

      1. Abra o arquivo "app/models/receita.rb"
      2. Adicione as seguintes linhas:
        validates_presence_of :conteudo, message: " - deve ser preenchido"
        

    Você pode também fazer o curso RR-71 dessa apostila na Caelum

    Querendo aprender ainda mais sobre a linguagem Ruby e o framework Ruby on Rails? Esclarecer dúvidas dos exercícios? Ouvir explicações detalhadas com um instrutor?
    A Caelum oferece o curso RR-71 presencial nas cidades de São Paulo, Rio de Janeiro e Brasília, além de turmas incompany.

    Consulte as vantagens do curso Desenv. Ágil para Web com Ruby on Rails.

    7.18 - Exercícios - Criando o Modelo de Qualificação

    1. Vamos corrigir a pluralização da palavra 'qualificacao'

      1. Abra o arquivo "config/initializers/inflections.rb"
      2. Adicione a seguinte inflection:
        inflect.irregular 'qualificacao', 'qualificacoes'
        
      1. Execute o console do Rails:
        $ rails console
        
      2. No Console do Rails digite:
        "qualificacao".pluralize
        
    2. Vamos continuar com a criação do nosso modelo Qualificacao e sua migration.

      1. No Terminal digite
      2. rails generate model qualificacaomodel-qualificacao.png
      3. Abra o arquivo "db/migrate/<timestamp>_create_qualificacoes.rb"
      4. Adicione as linhas:
        t.integer :cliente_id
        t.integer :restaurante_id
        t.float :nota
        t.float :valor_gasto
        
      5. Adicione ainda as seguintes linhas depois de "create_table": CUIDADO! Essas linhas não fazem parte do create_table, mas devem ficar dentro do método change.
        add_index(:qualificacoes, :cliente_id)
        add_index(:qualificacoes, :restaurante_id)
        
        migration-qualificacao.png
      6. Volte ao Terminal
      7. Execute a task para rodar as migrações pendentes:
        $ rake db:migrate
        
      8. Olhe no console o que foi feitoconsole-qualificacao.png
      9. Olhe no banco de dadostable-qualificacao.png
    3. Configure as propriedades de uma qualificação utilizando o attr_accessible:

      attr_accessible :nota, :valor_gasto, :cliente_id, :restaurante_id
      
    4. Vamos agora fazer as validações no modelo "qualificacao":

      1. Abra o arquivo "app/models/qualificacao.rb"
      2. Adicione as seguintes linhas:
        validates_presence_of :nota, message: " - deve ser preenchido"
        validates_presence_of :valor_gasto, message: " - deve ser preenchido"
          
        validates_numericality_of :nota, greater_than_or_equal_to: 0,
                                        less_than_or_equal_to: 10,
                                        message: " - deve ser um número entre 0 e 10"
        
        validates_numericality_of :valor_gasto, greater_than: 0,
                                                message: " - deve ser um número maior que 0"
        
        model2-qualificacao.png

    7.19 - Relacionamentos

    relacionamentos.png

    Para relacionar diversos modelos, precisamos informar ao Rails o tipo de relacionamento entre eles. Quando isso é feito, alguns métodos são criados para podermos manipular os elementos envolvidos nesse relacionamento. Os relacionamentos que Rails disponibiliza são os seguintes:

    • belongs_to - usado quando um modelo tem como um de seus atributos o id de outro modelo (many-to-one ou one-to-one).Quando dissermos que uma qualificação belongs_to um restaurante, ainda ganharemos os seguintes métodos:
      • Qualificacao.restaurante (similar ao Restaurante.find(restaurante_id))
      • Qualificacao.restaurante=(restaurante) (similar ao qualificacao.restaurante_id = restaurante.id)
      • Qualificacao.restaurante? (similar ao qualificacao.restaurante == algum_restaurante)
    • has_many - associação que provê uma maneira de mapear uma relação one-to-many entre duas entidades.Quando dissermos que um restaurante has_manyqualificações, ganharemos os seguintes métodos:
      • Restaurante.qualificacoes (semelhante ao Qualificacao.find :all, conditions: ["restaurante_id = ?", id])
      • Restaurante.qualificacoes<<
      • Restaurante.qualificacoes.delete
      • Restaurante.qualificacoes=
    • has_and_belongs_to_many - associação muitos-para-muitos, que é feita usando uma tabela de mapeamento.Quando dissermos que um pratohas_and_belongs_to_many restaurantes, ganharemos os seguintes métodos:
      • Prato.restaurantes
      • Prato.restaurantes<<
      • Prato.restaurantes.delete
      • Prato.restaurantes=
      Além disso, precisaremos criar a tabela pratos_restaurantes, com as colunasprato_id e restaurante_id. Por convenção, o nome da tabela é a concatenação do nome das duas outras tabelas, seguindo a ordem alfabética.
    • has_one - lado bidirecional de uma relação um-para-um.Quando dissermos que um prato has_one receita, ganharemos os seguintes métodos:
      • Prato.receita, (semelhante ao Receita.find(:first, conditions: "prato_id = id"))
      • Prato.receita=

    7.20 - Para Saber Mais: Auto-relacionamento

    Os relacionamentos vistos até agora foram sempre entre dois objetos de classes diferentes. Porém existem relacionamentos entre classes do mesmo tipo, por exemplo, uma Categoria de um site pode conter outras categorias, onde cada uma contém ainda suas categorias. Esse relacionamento é chamado auto-relacionamento.

    No ActiveRecord temos uma forma simples para fazer essa associação.

    class Category < ActiveRecord::Base
      has_many :children, class_name: "Category",
                          foreign_key: "father_id"
      belongs_to :father, class_name: "Category"
    end
    

    Tire suas dúvidas no novo GUJ Respostas

    O GUJ é um dos principais fóruns brasileiros de computação e o maior em português sobre Java. A nova versão do GUJ é baseada em uma ferramenta de perguntas e respostas (QA) e tem uma comunidade muito forte. São mais de 150 mil usuários pra ajudar você a esclarecer suas dúvidas.

    Faça sua pergunta.

    7.21 - Para Saber Mais: Cache

    Todos os resultados de métodos acessados de um relacionamento são obtidos de um cache e não novamente do banco de dados. Após carregar as informações do banco, o ActiveRecord só volta se ocorrer um pedido explicito. Exemplos:

    restaurante.qualificacoes             # busca no banco de dados
    restaurante.qualificacoes.size        # usa o cache
    restaurante.qualificacoes.empty?      # usa o cache
    restaurant                  
    • 9 posts
    12 de maio de 2020 08:06:51 ART

    Infrastructure quality in Italy is rated to be at 3.78. It indicates a good quality - roads, railroad, ports and other facilities are adapted and regularly maintained to handle high levels of traffic at all times, as well as most probably there are special facilities for handling high intensity and/or special traffic or vehicles (e.g. motorways a.k.a. autobahns and deepwater ports).

    In Italy, 100% of the population has access to electricity. There are 25,662,000 internet hosts in Italy. Italy has 129 airports nationwide.

    The logistics performance index of Italy is 3.69. It indicates a satisfactory performance - in general, traffic is handeled well, some flaws in certain areas are possible, but overall the logistics system performs reliably and is ready to handle predictable amounts of traffic.

    • 125842 posts
    • 125842 posts
    5 de novembro de 2020 13:28:31 ART
    инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинйоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфоинфо инфоинфоинфоинфоtuchkasинфоинфо
    • 125842 posts
    • 125842 posts
    3 de março de 2021 11:24:47 ART
    http://audiobookkeeper.ru href="http://cottagenet.ru">http://cottagenet.ru href="http://eyesvision.ru">http://eyesvision.ru href="http://eyesvisions.com">http://eyesvisions.com href="http://factoringfee.ru">http://factoringfee.ru href="http://filmzones.ru">http://filmzones.ru href="http://gadwall.ru">http://gadwall.ru href="http://gaffertape.ru">http://gaffertape.ru href="http://gageboard.ru">http://gageboard.ru href="http://gagrule.ru">http://gagrule.ru href="http://gallduct.ru">http://gallduct.ru href="http://galvanometric.ru">http://galvanometric.ru href="http://gangforeman.ru">http://gangforeman.ru href="http://gangwayplatform.ru">http://gangwayplatform.ru href="http://garbagechute.ru">http://garbagechute.ru href="http://gardeningleave.ru">http://gardeningleave.ru href="http://gascautery.ru">http://gascautery.ru href="http://gashbucket.ru">http://gashbucket.ru href="http://gasreturn.ru">http://gasreturn.ru href="http://gatedsweep.ru">http://gatedsweep.ru href="http://gaugemodel.ru">http://gaugemodel.ru href="http://gaussianfilter.ru">http://gaussianfilter.ru href="http://gearpitchdiameter.ru">http://gearpitchdiameter.ru href="http://geartreating.ru">http://geartreating.ru http://generalizedanalysis.ru href="http://generalprovisions.ru">http://generalprovisions.ru href="http://geophysicalprobe.ru">http://geophysicalprobe.ru href="http://geriatricnurse.ru">http://geriatricnurse.ru href="http://getintoaflap.ru">http://getintoaflap.ru href="http://getthebounce.ru">http://getthebounce.ru href="http://habeascorpus.ru">http://habeascorpus.ru href="http://habituate.ru">http://habituate.ru href="http://hackedbolt.ru">http://hackedbolt.ru href="http://hackworker.ru">http://hackworker.ru href="http://hadronicannihilation.ru">http://hadronicannihilation.ru href="http://haemagglutinin.ru">http://haemagglutinin.ru href="http://hailsquall.ru">http://hailsquall.ru href="http://hairysphere.ru">http://hairysphere.ru href="http://halforderfringe.ru">http://halforderfringe.ru href="http://halfsiblings.ru">http://halfsiblings.ru href="http://hallofresidence.ru">http://hallofresidence.ru href="http://haltstate.ru">http://haltstate.ru href="http://handcoding.ru">http://handcoding.ru href="http://handportedhead.ru">http://handportedhead.ru href="http://handradar.ru">http://handradar.ru href="http://handsfreetelephone.ru">http://handsfreetelephone.ru href="http://hangonpart.ru">http://hangonpart.ru href="http://haphazardwinding.ru">http://haphazardwinding.ru http://hardalloyteeth.ru href="http://hardasiron.ru">http://hardasiron.ru href="http://hardenedconcrete.ru">http://hardenedconcrete.ru href="http://harmonicinteraction.ru">http://harmonicinteraction.ru href="http://hartlaubgoose.ru">http://hartlaubgoose.ru href="http://hatchholddown.ru">http://hatchholddown.ru href="http://haveafinetime.ru">http://haveafinetime.ru href="http://hazardousatmosphere.ru">http://hazardousatmosphere.ru href="http://headregulator.ru">http://headregulator.ru href="http://heartofgold.ru">http://heartofgold.ru href="http://heatageingresistance.ru">http://heatageingresistance.ru href="http://heatinggas.ru">http://heatinggas.ru href="http://heavydutymetalcutting.ru">http://heavydutymetalcutting.ru href="http://jacketedwall.ru">http://jacketedwall.ru href="http://japanesecedar.ru">http://japanesecedar.ru href="http://jibtypecrane.ru">http://jibtypecrane.ru href="http://jobabandonment.ru">http://jobabandonment.ru href="http://jobstress.ru">http://jobstress.ru href="http://jogformation.ru">http://jogformation.ru href="http://jointcapsule.ru">http://jointcapsule.ru href="http://jointsealingmaterial.ru">http://jointsealingmaterial.ru href="http://journallubricator.ru">http://journallubricator.ru href="http://juicecatcher.ru">http://juicecatcher.ru href="http://junctionofchannels.ru">http://junctionofchannels.ru http://justiciablehomicide.ru href="http://juxtapositiontwin.ru">http://juxtapositiontwin.ru href="http://kaposidisease.ru">http://kaposidisease.ru href="http://keepagoodoffing.ru">http://keepagoodoffing.ru href="http://keepsmthinhand.ru">http://keepsmthinhand.ru href="http://kentishglory.ru">http://kentishglory.ru href="http://kerbweight.ru">http://kerbweight.ru href="http://kerrrotation.ru">http://kerrrotation.ru href="http://keymanassurance.ru">http://keymanassurance.ru href="http://keyserum.ru">http://keyserum.ru href="http://kickplate.ru">http://kickplate.ru href="http://killthefattedcalf.ru">http://killthefattedcalf.ru href="http://kilowattsecond.ru">http://kilowattsecond.ru href="http://kingweakfish.ru">http://kingweakfish.ru href="http://kinozones.ru">http://kinozones.ru href="http://kleinbottle.ru">http://kleinbottle.ru href="http://kneejoint.ru">http://kneejoint.ru href="http://knifesethouse.ru">http://knifesethouse.ru href="http://knockonatom.ru">http://knockonatom.ru href="http://knowledgestate.ru">http://knowledgestate.ru href="http://kondoferromagnet.ru">http://kondoferromagnet.ru href="http://labeledgraph.ru">http://labeledgraph.ru href="http://laborracket.ru">http://laborracket.ru href="http://labourearnings.ru">http://labourearnings.ru http://labourleasing.ru href="http://laburnumtree.ru">http://laburnumtree.ru href="http://lacingcourse.ru">http://lacingcourse.ru href="http://lacrimalpoint.ru">http://lacrimalpoint.ru href="http://lactogenicfactor.ru">http://lactogenicfactor.ru href="http://lacunarycoefficient.ru">http://lacunarycoefficient.ru href="http://ladletreatediron.ru">http://ladletreatediron.ru href="http://laggingload.ru">http://laggingload.ru href="http://laissezaller.ru">http://laissezaller.ru href="http://lambdatransition.ru">http://lambdatransition.ru href="http://laminatedmaterial.ru">http://laminatedmaterial.ru href="http://lammasshoot.ru">http://lammasshoot.ru href="http://lamphouse.ru">http://lamphouse.ru href="http://lancecorporal.ru">http://lancecorporal.ru href="http://lancingdie.ru">http://lancingdie.ru href="http://landingdoor.ru">http://landingdoor.ru href="http://landmarksensor.ru">http://landmarksensor.ru href="http://landreform.ru">http://landreform.ru href="http://landuseratio.ru">http://landuseratio.ru href="http://languagelaboratory.ru">http://languagelaboratory.ru href="http://largeheart.ru">http://largeheart.ru href="http://lasercalibration.ru">http://lasercalibration.ru href="http://laserlens.ru">http://laserlens.ru href="http://laserpulse.ru">http://laserpulse.ru http://laterevent.ru href="http://latrinesergeant.ru">http://latrinesergeant.ru href="http://layabout.ru">http://layabout.ru href="http://leadcoating.ru">http://leadcoating.ru href="http://leadingfirm.ru">http://leadingfirm.ru href="http://learningcurve.ru">http://learningcurve.ru href="http://leaveword.ru">http://leaveword.ru href="http://machinesensible.ru">http://machinesensible.ru href="http://magneticequator.ru">http://magneticequator.ru href="http://magnetotelluricfield.ru">http://magnetotelluricfield.ru href="http://mailinghouse.ru">http://mailinghouse.ru href="http://majorconcern.ru">http://majorconcern.ru href="http://mammasdarling.ru">http://mammasdarling.ru href="http://managerialstaff.ru">http://managerialstaff.ru href="http://manipulatinghand.ru">http://manipulatinghand.ru href="http://manualchoke.ru">http://manualchoke.ru href="http://medinfobooks.ru">http://medinfobooks.ru href="http://mp3lists.ru">http://mp3lists.ru href="http://nameresolution.ru">http://nameresolution.ru href="http://naphtheneseries.ru">http://naphtheneseries.ru href="http://narrowmouthed.ru">http://narrowmouthed.ru href="http://nationalcensus.ru">http://nationalcensus.ru href="http://naturalfunctor.ru">http://naturalfunctor.ru href="http://navelseed.ru">http://navelseed.ru http://neatplaster.ru href="http://necroticcaries.ru">http://necroticcaries.ru href="http://negativefibration.ru">http://negativefibration.ru href="http://neighbouringrights.ru">http://neighbouringrights.ru href="http://objectmodule.ru">http://objectmodule.ru href="http://observationballoon.ru">http://observationballoon.ru href="http://obstructivepatent.ru">http://obstructivepatent.ru href="http://oceanmining.ru">http://oceanmining.ru href="http://octupolephonon.ru">http://octupolephonon.ru href="http://offlinesystem.ru">http://offlinesystem.ru href="http://offsetholder.ru">http://offsetholder.ru href="http://olibanumresinoid.ru">http://olibanumresinoid.ru href="http://onesticket.ru">http://onesticket.ru href="http://packedspheres.ru">http://packedspheres.ru href="http://pagingterminal.ru">http://pagingterminal.ru href="http://palatinebones.ru">http://palatinebones.ru href="http://palmberry.ru">http://palmberry.ru href="http://papercoating.ru">http://papercoating.ru href="http://paraconvexgroup.ru">http://paraconvexgroup.ru href="http://parasolmonoplane.ru">http://parasolmonoplane.ru href="http://parkingbrake.ru">http://parkingbrake.ru href="http://partfamily.ru">http://partfamily.ru href="http://partialmajorant.ru">http://partialmajorant.ru href="http://quadrupleworm.ru">http://quadrupleworm.ru http://qualitybooster.ru href="http://quasimoney.ru">http://quasimoney.ru href="http://quenchedspark.ru">http://quenchedspark.ru href="http://quodrecuperet.ru">http://quodrecuperet.ru href="http://rabbetledge.ru">http://rabbetledge.ru href="http://radialchaser.ru">http://radialchaser.ru href="http://radiationestimator.ru">http://radiationestimator.ru href="http://railwaybridge.ru">http://railwaybridge.ru href="http://randomcoloration.ru">http://randomcoloration.ru href="http://rapidgrowth.ru">http://rapidgrowth.ru href="http://rattlesnakemaster.ru">http://rattlesnakemaster.ru href="http://reachthroughregion.ru">http://reachthroughregion.ru href="http://readingmagnifier.ru">http://readingmagnifier.ru href="http://rearchain.ru">http://rearchain.ru href="http://recessioncone.ru">http://recessioncone.ru href="http://recordedassignment.ru">http://recordedassignment.ru href="http://rectifiersubstation.ru">http://rectifiersubstation.ru href="http://redemptionvalue.ru">http://redemptionvalue.ru href="http://reducingflange.ru">http://reducingflange.ru href="http://referenceantigen.ru">http://referenceantigen.ru href="http://regeneratedprotein.ru">http://regeneratedprotein.ru href="http://reinvestmentplan.ru">http://reinvestmentplan.ru href="http://safedrilling.ru">http://safedrilling.ru href="http://sagprofile.ru">http://sagprofile.ru http://salestypelease.ru href="http://samplinginterval.ru">http://samplinginterval.ru href="http://satellitehydrology.ru">http://satellitehydrology.ru href="http://scarcecommodity.ru">http://scarcecommodity.ru href="http://scrapermat.ru">http://scrapermat.ru href="http://screwingunit.ru">http://screwingunit.ru href="http://seawaterpump.ru">http://seawaterpump.ru href="http://secondaryblock.ru">http://secondaryblock.ru href="http://secularclergy.ru">http://secularclergy.ru href="http://seismicefficiency.ru">http://seismicefficiency.ru href="http://selectivediffuser.ru">http://selectivediffuser.ru href="http://semiasphalticflux.ru">http://semiasphalticflux.ru href="http://semifinishmachining.ru">http://semifinishmachining.ru href="http://spicetrade.ru">http://spicetrade.ru href="http://spysale.ru">http://spysale.ru href="http://stungun.ru">http://stungun.ru href="http://tacticaldiameter.ru">http://tacticaldiameter.ru href="http://tailstockcenter.ru">http://tailstockcenter.ru href="http://tamecurve.ru">http://tamecurve.ru href="http://tapecorrection.ru">http://tapecorrection.ru href="http://tappingchuck.ru">http://tappingchuck.ru href="http://taskreasoning.ru">http://taskreasoning.ru href="http://technicalgrade.ru">http://technicalgrade.ru href="http://telangiectaticlipoma.ru">http://telangiectaticlipoma.ru http://telescopicdamper.ru href="http://temperateclimate.ru">http://temperateclimate.ru href="http://temperedmeasure.ru">http://temperedmeasure.ru href="http://tenementbuilding.ru">http://tenementbuilding.ru href="http://tuchkas.ru/">tuchkashttp://ultramaficrock.ru href="http://ultraviolettesting.ru">http://ultraviolettesting.ru
    • 4052 posts
    21 de junho de 2021 06:17:11 ART

    Xo สล็อตออนไลน์ โปรสล็อต XO เกมออนไลน์ทำเงินยอดฮิตเกมสล็อต xopg.net คือเกมทำเงิน reeffutures2018 ผ่านทางออนไลน์อย่างหนึ่ง ที่เล่นง่าย และได้เงินไว แถมยังลงทุนด้วยเงินน้อย mavoixtavoie ทำเงินได้ตลอดเวลา ซึ่งหลายคนอาจได้เคยเห็นรีวิวเรื่องของ สล็อต xo สล็อตออนไลน์ ไว้มากมาย เทคนิคสล็อต ทั้งเรื่องการเล่นแล้วได้เงิน herbalpertpresents และเล่น สล็อต แล้วไม่ได้เงิน นั่นเองค่ะ ซึ่งการที่คุณจะเล่นได้เงินหรือไม่ได้เงินนั้น essentialsforasoul ส่วนหนึ่งก็เป็นในเรื่องของดวงเข้ามาเกี่ยวด้วย northbristol เพราะสล็อตเป็นเกมออนไลน์เสี่ยงโชค ทดลองเล่น xo เกมหนึ่งซึ่งจะมีสูตร หรือเทคนิคเข้ามาช่วย gclub เพื่อโกงดวงอยู่เสมอซึ่งในเว็บของเรา สมัคร xo ก็มีมาแนะนำไว้ให้เห็นกันมากมายหลายสูตร

    • 28538 posts
    1 de novembro de 2022 10:44:39 ART

    I should assert barely that its astounding! The blog is informational also always fabricate amazing entitys. 사설토토

    • 28538 posts
    8 de novembro de 2022 11:42:01 ART

    It's superior, however , check out material at the street address. 안전놀이터

    • 28538 posts
    19 de novembro de 2022 03:03:22 ART

    I recommend only good and reliable information, so see it: 벳삼

    • 28538 posts
    25 de novembro de 2022 11:01:15 ART

    I recommend only good and reliable information, so see it: 꽁머니사이트

    • 28538 posts
    13 de dezembro de 2022 16:42:21 ART

    It is especially decent, though look into the tips during this home address. 링크찾기

    • 28538 posts
    5 de janeiro de 2023 11:15:06 ART

    Youre so cool! I dont suppose Ive read anything such as this before. So nice to discover somebody by original applying for grants this subject. realy thanks for beginning this up. this website is one thing that is required on-line, a person after a little originality. helpful problem for bringing new things for the world wide web! 사설토토

    • 28538 posts
    7 de fevereiro de 2023 05:28:39 ART

    I do trust all the ideas you’ve introduced on your post. They’re really convincing and will definitely work. Nonetheless, the posts are very quick for starters. May just you please extend them a bit from subsequent time? Thanks for the post. 토토커뮤니티

    • 28538 posts
    16 de fevereiro de 2023 18:06:19 ART

    An impressive share, I just now given this onto a colleague who was doing a little analysis on this. And hubby actually bought me breakfast since I ran across it for him.. smile. So let me reword that: Thnx to the treat! But yeah Thnkx for spending enough time go over this, Personally i think strongly regarding this and love reading more on this topic. When possible, as you grow expertise, does one mind updating your site with an increase of details? It is extremely useful for me. Big thumb up with this short article! 꽁머니

    • 141 posts
    27 de fevereiro de 2023 07:03:02 ART

    Although, regardless how positive you actually are in treating an actual platform, at some point there are an incident wherever you should want to do some people regular supervision; and in addition dependant on your real age and in addition wellbeing, and so the body-weight using the caravan it’s really a totally hard do physical exercise. caravan touch up paint brown puffer phone case

    • 28538 posts
    1 de março de 2023 09:20:47 ART

    Glad to see you’re on top of things. You sound like you understand what you’re talking about! Thanks and good luck! x 먹튀검증

    • 3 posts
    2 de março de 2023 08:06:39 ART

    Merely wanna state that this is very beneficial , Thanks for taking your time to write this. black air pod pros

    • 1 posts
    9 de março de 2023 16:46:29 ART

    I believe other website owners should take this site as an example , very clean and wonderful user genial style . croxyproxy YouTube

    • 36 posts
    12 de março de 2023 12:43:41 ART

    Hi there! This is kind of off topic but I need some advice from an established blog. Is it very hard to set up your own blog? I’m not very techincal but I can figure things out pretty quick. I’m thinking about setting up my own but I’m not sure where to start. Do you have any points or suggestions? Thank you Cas9 ELISA kit

    • 36 posts
    12 de março de 2023 14:04:09 ART

    dude this just inspired a post of my own, thanks Sliding Glass Door Roller Replacement Palm Beach County FL

    • 314 posts
    14 de março de 2023 10:18:34 ART

    Unquestionably believe that which you stated. Your favorite justification appeared to be on the internet the easiest thing to be aware of. I say to you, I certainly get irked while people consider worries that they just don’t know about. You managed to hit the nail upon the top and also defined out the whole thing without having side effect , people can take a signal. Will probably be back to get more. Thanks unblocked games 66 ez

    • 141 posts
    16 de março de 2023 07:33:21 ART

    I genuinely treasure your work , Great post. cats with big noses

    • 141 posts
    16 de março de 2023 07:33:23 ART

    I genuinely treasure your work , Great post. cats with big noses

    • 314 posts
    18 de março de 2023 11:48:43 ART

    I wanted to thank you a lot more for this amazing web-site you have created here. It truly is full of useful tips for those who are genuinely interested in this subject, specifically this very post. Your all actually sweet plus thoughtful of others as well as reading your site posts is a wonderful delight in my experience. And what a generous reward! Mary and I will certainly have fun making use of your guidelines in what we must do in a few weeks. Our checklist is a mile long which means that your tips might be put to great use. Cas9 ELISA kit