Ruby on Rails: Fully functional tableless model - ruby-on-rails

After searching for a tableless model example I came across this code which seems to be the general consensus on how to create one.
class Item < ActiveRecord::Base
class_inheritable_accessor :columns
self.columns = []
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
def all
return []
end
column :recommendable_type, :string
#Other columns, validations and relations etc...
end
However I would also like it to function, as a model does, representing a collection of object, so that I can do Item.all.
The plan is to populate Items with files and each Item's properties will be extracted from the files.
However currently if I do Item.all I get a
Mysql2::Error Table 'test_dev.items' doesn't exist...
error.

I found an example at http://railscasts.com/episodes/219-active-model where I can use model features and then override static methods like all (should have thought of this before).
class Item
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
class << self
def all
return []
end
end
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end

Or you could do it like this (Edge Rails only):
class Item
include ActiveModel::Model
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
end
By simply including ActiveModel::Model you get all the other modules included for you. It makes for a cleaner and more explicit representation (as in this is an ActiveModel)

Related

using the same validator for different attributes Rails

I have a custom validator that I want to apply to several attributes in the same model
right now I the following the works just fine:
validates :first_name, validator_name: true
validates :age, validator_name: true
validates :gender, validator_name: true
But when I try:
validates :first_name, :age, :gender, validator_name: true
The validator will run for the first attribute (:first_name) but not the others. Is this possible to achieve? I spent hours googling this but haven't found any examples
module Person
class SomeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless can_do_something?(record, attribute)
#... more code
end
def can_do_something?(record, attribute)
anything_new = record.new_record? || record.attribute_changed?(attribute)
end
end
end
Not sure if this should just be a comment or if it constitutes an answer; however what you are requesting...
I have a custom validator that I want to apply to several attributes in the same model
...is how an EachValidator works.
So what you are describing...
The validator will run for the first attribute (:first_name) but not the others.
..cannot be accurate.
For Example:
require 'active_model'
class StartsWithXValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.match?(/^(?:\d+\s|^)X/)
record.errors.add attribute, "must start with X"
end
end
end
class Person
include ActiveModel::Model
include ActiveModel::Validations
attr_accessor :name, :city, :street
validates :name, :city, :street, starts_with_x: true
end
In this case all three attributes will be validated through the StartsWithXValidator.
e.g.
person = Person.new({name: 'Xavier', city: 'Xenia', street: '123 Xenial St'})
person.valid?
#=> true
person_2 = Person.new({name: 'Benjamin', city: 'Philadelphia', street: '700 Market St'})
person_2.valid?
#=> false
person_2.errors.full_messages
#=> ["Name must start with X", "City must start with X", "Street must start with X"]
Working Example
I think you can use a custom method validation:
validate :validate_person
def validate_person
[:first_name, :age, :gender].each do |attr|
validates attr, validator_name: true
end
end
Reference: https://guides.rubyonrails.org/active_record_validations.html#custom-methods

Active record Query methods for simple class

I have created class using Active Model,it is working as expected ,but am having few requirements
1) I want to use all method for that class.
2) Same way i want to use some query methods like where method for that class.
3) I want to create ActiveRecord::Relation so that i can do method chaining.
See my class:
require 'active_model'
require 'active_record'
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
m = Message.new(:email => "test",:name => "test")
puts m.name #=> test
puts m.class #=> Message
puts m.valid? #=> true
Message.all
Message.where(:name => "test")
some more methods:
where
limit
having
group
select
order
uniq
Could you please help me to achieve this or give some guide lines.

Rails validation from controller

There is a contact page, which offers to enter name, telephone, email and message, after that it sends to an administrator's email. There is no reason to store message in DB.
Question. How to:
Use Rails validations in controller, not using model at all, OR
Use validations in model, but without any DB relations
UPD:
Model:
class ContactPageMessage
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :telephone, :email, :message
validates :name, :telephone, :email, :message, presence: true
validates :email, email_format: { :message => "Неверный формат E-mail адреса"}
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
controller:
def sendmessage
cpm = ContactPageMessage.new()
if cpm.valid?
#settings = Setting.first
if !#settings
redirect_to contacts_path, :alert => "Fail"
end
if ContactPageMessage.received(params).deliver
redirect_to contacts_path, :notice => "Success"
else
redirect_to contacts_path, :alert => "Fail"
end
else
redirect_to contacts_path, :alert => "Fail"
end
end
end
you should use model without inheriting from ActiveRecord::Base class.
class ContactPageMessage
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :whatever
validates :whatever, :presence => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
Through this you will able to initialize new object and able to call validations on that object.
I think you have a different class name with same name, in your controller code, I can see this :
if ContactPageMessage.received(params).deliver
redirect_to contacts_path, :notice => "Success"
else
if this is your mailer class change its name to ContactPageMessageMailer. you will no loger get that error.
Hope it will help. Thanks
I would still advice you to use model, rails models doesn't have to inherit from ActiveRecord::Base.
For example:
class Contact
include ActiveModel::Validations
attr_accessor :name, :telephone, :email, :message
validates_presence_of :name, :telephone, :email, :message
validates_format_of :email, with: EMAIL_REGEXP
end
and you can use it in your controller with:
contact = Contact.new
# ...
if contact.valid?
# do something
else
# do something else
end
In your model you can add the below which will just set getter and setter method for message and you can have validation on message without having a column in the db
attr_accessor :message
validates :message, presence: true

Lowercase condition in "magic" methods for Model

A model Country has a attribute code which is automatically converted to lowercase by a before_save callback. Is it possible to force this behaviour on "magic" methods without rewriting large chunks of ActiveRecord::Base?
class Country < ActiveRecord::Base
attr_accessible :code
validates :code, :presence => true
validates_uniqueness_of :code, :case_sensitive => false
before_save do |country|
country.code.downcase! unless country.code.nil?
end
end
RSpec
describe Country do
describe 'data normalization'
before :each do
#country = FactoryGirl.create(:country, :code => 'DE')
end
# passes
it 'should normalize the code to lowercase on insert' do
#country.code.should eq 'de'
end
# fails
it 'should be agnostic to uppercase finds' do
country = Country.find_by_code('DE')
country.should_not be_nil
end
# fails
it 'should be agnostic to uppercase finds_or_creates' do
country = Country.find_or_create_by_code('DE')
country.id.should_not be_nil # ActiveRecord Bug?
end
end
This is what I came up with, altough I really hate that approach (as mentioned in the question). An easy alternative would be to set the column, table or whole database up to ignore case (but this is db dependendt).
class Country < ActiveRecord::Base
attr_accessible :code
validates :code, :presence => true
validates_uniqueness_of :code, :case_sensitive => false
before_save do |country|
country.code.downcase! unless country.code.nil?
end
class ActiveRecord::Base
def self.method_missing_with_code_finders(method_id, *arguments, &block)
if match = (ActiveRecord::DynamicFinderMatch.match(method_id) || ActiveRecord::DynamicScopeMatch.match(method_id))
attribute_names = match.attribute_names
if code_index = attribute_names.find_index('code')
arguments[code_index].downcase!
end
end
method_missing_without_code_finders(method_id, *arguments, &block)
end
class << self
alias_method_chain(:method_missing, :code_finders)
end
end
end

ActiveModel attributes

How do I get ActiveRecord attributes method functionality? I have this class:
class PurchaseForm
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name,
:surname,
:email
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
def initialize(attributes = {}, shop_name)
if not attributes.nil?
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
What I need to do, to have an attributes method to list all names and values from PurchaseForm object?
Here is the refactored variant:
class PurchaseForm
include ActiveModel::Model
def self.attributes
[:name, :surname, :email]
end
attr_accessor *self.attributes
# your validations
def to_hash
self.class.attributes.inject({}) do |hash, key|
hash.merge({ key => self.send(key) })
end
end
end
Now you can easily work with this class:
irb(main):001:0> a = PurchaseForm.new({ name: 'Name' })
=> #<PurchaseForm:0x00000002606b50 #name="Name">
irb(main):002:0> a.to_hash
=> {:name=>"Name", :surname=>nil, :email=>nil}
irb(main):003:0> a.email = 'user#example.com'
=> "user#example.com"
irb(main):004:0> a
=> #<PurchaseForm:0x00000002606b50 #name="Name", #email="user#example.com">
irb(main):005:0> a.to_hash
=> {:name=>"Name", :surname=>nil, :email=>"user#example.com"}
Even more, if you want to make this behaviour reusable, consider extraction of .attributes and #to_hash methods into separate module:
module AttributesHash
extend ActiveSupport::Concern
class_methods do
def attr_accessor(*args)
#attributes = args
super(*args)
end
def attributes
#attributes
end
end
included do
def to_hash
self.class.attributes.inject({}) do |hash, key|
hash.merge({ key => self.send(key) })
end
end
end
end
Now, just include it to your model and you're done:
class PurchaseForm
include ActiveModel::Model
include AttributesHash
attr_accessor :name, :surname, :email
# your validations
end
#instance_values could do the job:
class PurchaseForm
attr_accessor :name, :email
def attributes
instance_values
end
end
Output sample:
purchase.attributes #=> {"name"=>"John", "email"=>"john#example.com"}
I've managed to solve problem with this code:
class PurchaseForm
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :attributes,
:name,
:surname,
:email
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
def initialize(attributes = {})
#attributes = attributes
end
def persisted?
false
end
end
Let's try this
self.as_json
=> {:name=>"Name", :surname=>nil, :email=>"user#example.com"}
would it not be better to use
include ActiveModel::Serialization
def attributes
JSON.parse(self.to_json)
end

Resources