Tables in DataMapper w/ Rails not Inheriting Correctly - ruby-on-rails

I'm using DataMapper in Rails, replacing ActiveRecord and am trying to do some single table inheritance. The problem is, sqlite3 seems to be reading all of my inherited tables weird, and so I get some strange errors when trying to create an instance of that table in ruby.
I am using a model, ServerFile, to represent any uploaded or generic file that I want instanced in my database. I have another model, Upload, that extends that and represents an Upload from a user. I have two more models extending from ServerFile, Thumbnail, and UploadThumbnail, to represent a generic Thumbnail and a Thumbnail for an upload accordingly.
The error I keep getting is DataObjects::IntegrityError (server_files.upload_id may not be NULL) when I try to create an instance of an Upload like this:
upload = Upload.new(
filename: uploaded_io.original_filename,
path: path.to_s,
content_type: uploaded_io.content_type,
token: rand_token())
upload.member = #member
upload.title = params[:title]
upload.description = params[:description]
upload.save
And here are my models:
class ServerFile
include DataMapper::Resource
property :id, Serial
property :token, String, unique_index: true
property :filename, String
property :path, Text, unique: true
property :content_type, Text, length: 5..200
property :type, Discriminator
property :created_on, Date
property :created_at, DateTime
property :updated_on, Date
property :updated_at, DateTime
end
class Upload < ServerFile
property :title, String
property :description, Text
has n, :topics, through: Resource
has n, :subjects, through: Resource
has n, :downloads
has n, :comments, 'UploadComment'
has n, :ratings, 'UploadRating'
belongs_to :member
has 1, :thumbnail, 'UploadThumbnail', required: false
end
class Thumbnail < ServerFile
##IMAGE_EXTENSIONS = [:'png', :'jpg', :'jpeg', :'gif', :'svg', :'cgm']
validates_with_method :filename, :is_valid_image?
def is_valid_image?
##IMAGE_EXTENSIONS.each do |ext|
return true if /[\w\d\.\_\-]+\.#{ext.to_s}/ =~ #filename
end
[false, 'Invalide image type.']
end
end
class UploadThumbnail < Thumbnail
belongs_to :upload
end
And here is my sqlite schema for the table 'server_files' (and btw, when I list my tables, 'uploads' isn't listed among them):
sqlite> .schema server_files
CREATE TABLE "server_files" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "token" VARCHAR(50), "filename" VARCHAR(50), "path" TEXT, "content_type" TEXT, "type" VARCHAR NOT NULL, "created_on" DATE, "created_at" TIMESTAMP, "updated_on" DATE, "updated_at" TIMESTAMP, "title" VARCHAR(50), "description" TEXT, "member_id" INTEGER NOT NULL, "upload_id" INTEGER NOT NULL);
CREATE UNIQUE INDEX "unique_server_files_path" ON "server_files" ("path");
CREATE UNIQUE INDEX "unique_server_files_token" ON "server_files" ("token");

There is no need for an upload_id column on the server_files table. Because Upload inherits from ServerFile, this would essentially be self-referential. The column type with type Discriminator will be set to Upload in the database when the model is successfully saved. That being said, if you want to set a custom upload_id anyways, you can do it with a callback like this:
before :create, :generate_upload_id
def generate_upload_id
generated_upload_id = <do something>
attribute_set(:upload_id, generated_upload_id) unless upload_id
end
Otherwise, simply write a migration that removes the upload_id column from the server_files table
Source: http://datamapper.org/docs/misc.html

DataMapper for some reason just didn't like my superclass ServerFile. When I broke it up, it worked perfectly! (:
class Upload < ServerFile
include DataMapper::Resource
property :id, Serial
property :token, String, unique_index: true
property :filename, String
property :path, Text, unique: true
property :content_type, Text, length: 5..200
property :created_on, Date
property :created_at, DateTime
property :updated_on, Date
property :updated_at, DateTime
property :title, String
property :description, Text
has n, :topics, through: Resource
has n, :subjects, through: Resource
has n, :downloads
has n, :comments, 'UploadComment'
has n, :ratings, 'UploadRating'
belongs_to :member
has 1, :thumbnail, 'UploadThumbnail', required: false
end
class Thumbnail < ServerFile
include DataMapper::Resource
property :id, Serial
property :token, String, unique_index: true
property :filename, String
property :path, Text, unique: true
property :content_type, Text, length: 5..200
property :type, Discriminator
property :created_on, Date
property :created_at, DateTime
property :updated_on, Date
property :updated_at, DateTime
##IMAGE_EXTENSIONS = [:'png', :'jpg', :'jpeg', :'gif', :'svg', :'cgm']
validates_with_method :filename, :is_valid_image?
def is_valid_image?
##IMAGE_EXTENSIONS.each do |ext|
return true if /[\w\d\.\_\-]+\.#{ext.to_s}/ =~ #filename
end
[false, 'Invalide image type.']
end
end
class UploadThumbnail < Thumbnail
belongs_to :upload
end

Related

Why is Rails ignoring validation when I run create

The simplified example is that I have several classes inheriting from an Asset class, which is an ActiveRecord model. When I use create or create! on one of the subclasses both the db layer and ActiveRecord layer validations for one field are ignored.
The Asset table has a type field with null: false, the Asset model has validates_presence_of :type and also has attribute :type, default: self.name.
If I use new on a subclass, such as Item, it behaves as expected, I get an Item with the type field set to "Item". If I use create or create! with valid attributes, the defaults are not applied, validation is ignored and I get a persisted record with a type field of nil.
What's odd is that other validations are respected. If I try creating a new Item without a name attribute, validates_presence_of :name properly raises a validation error.
Here's some pared down code snippets for reference:
class CreateAssets < ActiveRecord::Migration[6.0]
def change
create_table :assets do |t|
t.string :type, null: false
t.string :name
# ...
end
end
end
class Asset < ApplicationRecord
enum type: {
Item: :Item,
Accessory: :Accessory,
Component: :Component,
Consumable: :Consumable,
}
attribute :type, default: self.name
validates_presence_of :name
validates_presence_of :type
end
class Item < Asset
# ...
end
i = Item.create({ ... })
i.type
#nil
i.persisted?
#true
i.valid?
#false
i = Item.new({ ... })
i.type
#"Item"
i.valid?
#true
'type' is a reserved column name in ActiveRecord because it is used to indicate Single Table Inheritance (STI) . If you are not intending to use STI you should pick a different column name otherwise the STI behaviors will interfere with what you're trying to do here.

Value Object enum not being properly validated

I have a few enums in my project that will be reused across multiple models, and a few of which will have their own internal logic, and so I've implemented them as value objects (as described here # section 5) but I can't seem to get ActiveRecord validations to work with them. The simplest example is the Person model with a Gender value object.
Migration:
# db/migrate/###_create_people.rb
class CreatePeople < ActiveRecord::Migration[5.2]
def change
create_table :people do |t|
t.string :name
t.integer :age
t.integer :gender
end
end
end
Model:
# app/models/person.rb
class Person < ApplicationRecord
validates :gender, presence: true
enum gender: Enums::Gender::GENDERS
def gender
#gender ||= Enums::Gender.new(read_attribute(:gender))
end
end
Value Object:
# app/models/enums/gender.rb
module Enums
class Gender
GENDERS = %w(female male other).freeze
def initialize(gender)
#gender = gender
end
def eql?(other)
to_s.eql?(other.to_s)
end
def to_s
#gender.to_s
end
end
end
The only problem is that despite the model being set to validate the presence of the gender attribute, it allows a Person to be saved with a gender of nil. I'm not sure why that is, so I'm not sure where to start trying to fix the problem.
So I figured it out myself. Big thanks to benjessop whose suggestion didn't work, but did set me on the right train of thought.
validates :gender, numericality: { integer_only: true, greater_than_or_equal_to: 0, less_than: Enums::Gender::GENDERS.count }
I'll probably write a custom validation to implement that logic into several different value object enums. Thanks again for those that tried to help.
In your model file person.rb:
enum gender: Enums::Gender::GENDERS
But, In your model file gender.rb:
the constant is GENDER
Change the line in person.rb to:
enum gender: Enums::Gender::GENDER
instead of
enum gender: Enums::Gender::GENDERS

How can I create unique relationships based on a model in neo4j.rb?

I have tried to use
has_many :in, :ratings, unique: true, rel_class: Rating
But that unique: true is ignored because I have a model class for the relationship.
How can I make sure that if my Users rate Articles, their rating gets updated instead of added. I'd prefer it if it produces a single query. ;-)
Article.rb:
class Article
include Neo4j::ActiveNode
property :title, type: String
property :body, type: String
property :created_at, type: DateTime
# property :created_on, type: Date
property :updated_at, type: DateTime
# property :updated_on, type: Date
has_many :in, :ratings, unique: true, rel_class: Rating
has_many :in, :comments, unique: true, type: :comment_on
has_one :in, :author, unique: true, type: :authored, model_class: User
end
User.rb:
class User
include Neo4j::ActiveNode
has_many :out, :articles, unique: true, type: :authored
has_many :out, :comments, unique: true, type: :authored
has_many :out, :ratings, unique: true, rel_class: Rating
# this is a devise model, so there are many properties coming up here.
Rating.rb
class Rating
include Neo4j::ActiveRel
property :value, type: Integer
from_class User
to_class :any
type 'rates'
property :created_at, type: DateTime
# property :created_on, type: Date
property :updated_at, type: DateTime
# property :updated_on, type: Date
end
Rating creation inside the article controller:
Rating.create(:value => params[:articleRating],
:from_node => current_user, :to_node => #article)
This has been resolved. You can ensure unique relationships while using an ActiveRel model by using the creates_unique keyword.
per https://stackoverflow.com/a/33153615
For now I found this ugly workaround..
def rate
params[:articleRating]
rel = current_user.rels(type: :rates, between: #article)
if rel.nil? or rel.first.nil?
Rating.create(:value => rating,
:from_node => current_user, :to_node => #article)
else
rel.first[:value] = rating
rel.first.save
end
render text: ''
end
EDIT: cleaner, but with two queries:
def rate
current_user.rels(type: :rates, between: #article).each{|rel| rel.destroy}
Rating.create(:value => params[:articleRating],
:from_node => current_user, :to_node => #article)
render text: ''
end

Ruby DataMapper, table inheritance

My tables repeat this line always
property :created_at, DateTime, :default => DateTime.now, :lazy => [:show]
property :updated_at, DateTime, :default => DateTime.now, :lazy => [:show]
How do I DRY this out? Do I inherit, or have a module, or something else?
It's like this:
Class Foo
include DataMapper::Resource
property :id, Int
property :created_at, DateTime, :default => DateTime.now, :lazy => [:show]
property :updated_at, DateTime, :default => DateTime.now, :lazy => [:show]
end
Class Bar
include DataMapper::Resource
property :id, Int
property :created_at, DateTime, :default => DateTime.now, :lazy => [:show]
property :updated_at, DateTime, :default => DateTime.now, :lazy => [:show]
end
these are repeated multiple times through each of the table
Well, now I understand what you are saying. Those are rails properties, which are created automatically. I'm not sure if there's a way of preventing this from happening, but those are definitely useful in many situations. I suggest you keep them, you will figure out their use as you learn more about Rails.
As for the views, you need to create a controller method and define a route to those methods inside config/routes.rb. I suggest you learn more about the MVC rails pattern. MVC is the core in which Rails is built upon.
http://guides.rubyonrails.org/ is a great website for learning rails. Try reading just a few articles and you'll be able to understand enough to build a full application in no time.
I'm doing something similar. I used inheritance like this:
require 'rubygems'
require 'dm-core'
require 'dm-migrations'
require 'dm-constraints'
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/development.db")
DataMapper::Property::String.length(255)
class BaseTable
include DataMapper::Resource
property :id, Serial
property :created_date, DateTime, :default => DateTime.now
property :created_by, Integer
property :updated_date, DateTime, :default => DateTime.now
property :updated_by, Integer
end
class User < BaseTable
property :userid, String
property :email, String
property :password, String
end
class Account < BaseTable
property :name, String
property :type, String
property :location, String
property :account_number, String
property :total_balance, Integer
property :cleared_balance, Integer
property :marked_as_cleared_balance, Integer
has n, :transactions, :constraint => :destroy
end
class Transaction < BaseTable
property :id, Serial
property :label, String
property :cleared, Boolean
property :date, Date
property :description, String
property :amount, Integer
property :note, String
property :balance, Integer
belongs_to :account
end
DataMapper.finalize
DataMapper.auto_upgrade!

Mongo + RoR Models. Stuck with NoMethodError

I have a problem with NoMethodError in one of my models.
In the log file, we have:
NoMethodError (undefined method `length=' for #<Book:0x000000083866b8>):
2013-03-28T10:25:19+00:00 app[web.1]: app/models/engine/book.rb:13:in `block in find_or_create_by_guide'
2013-03-28T10:25:19+00:00 app[web.1]: app/models/engine/book.rb:9:in `find_or_create_by_guide'
Let me go through all of the important files.
For a start, we have Mongo's document.rb:
class Guide::Document
include MongoMapper::Document
key :city, Integer
key :trips, Array
key :themes, Array
key :places, Array
key :pace, String
key :date_from, Time
key :date_to, Time
key :host, String
key :length, Integer
timestamps!
end
Then, the book model is called upon the guide document:
module ClassMethods
def find_or_create_by_guide(guide)
book = ::Book.find_or_create_by_document(guide.id.to_s) do |b|
b.city_id = guide.city
b.host = guide.host
b.pace = guide.pace || :normal
b.length = guide.length
end
later in the book.rb, I have the following line:
groups = sorted_points.in_groups_of(self.length.count, false)
Length.rb:
class Length < ActiveRecord::Base
belongs_to :book
attr_accessible :book_id
end
Book.rb:
attr_accessible :user_id, :country_id, :city_id, :hotel_id, :type, :price, :host, :pace, :created_at, :updated_at, :length
Finally, the migrations of Length:
class AddLengthColumnToBooks < ActiveRecord::Migration
def change
add_column :books, :length, :integer
end
end
Any hints or tips appreciated.
This is a mess. Once you want 'length' to be an attribute of Book, once you want Length to be a separate model which is in a relation with Book.
I see no point in having Length model.
Go with 'length' as a Book property.

Resources