Advanced count association broken after sequel upgrade - ruby

I created an association in order to reduce the number of queries in a construct like this
#user.all do |user|
puts "User ##{user.id}, post count: #{user.posts_count}"
end
My Model:
class User
one_to_many :posts
one_to_one :posts_count, :read_only => true, :key => :id,
:dataset => proc{Post.where(:user_id => :id, :deleted => false).select{count(Sequel.lit("*")).as(count)}},
:eager_loader => (proc do |eo|
eo[:rows].each{|p| p.associations[:posts_count] = nil}
Post.where(:user_id => eo[:id_map].keys, :deleted => false).
select_group(:user_id).
select_append{count(Sequel.lit("*")).as(count)}.
all do |t|
p = eo[:id_map][t.values.delete(:user_id)].first
p.associations[:posts_count] = t
end
end)
def posts_count
super.try(:[], :count) || 0
end
# ...
end
After upgrading to sequel 4.1.0 I get unitialized constant PostsCount, while in sequel 3.44 it worked fine. How can I fix this?

Adding a class attribute solves the issue. Include :class => "Post" like this:
one_to_one :posts_count, :read_only => true, :key => :id, :class => "Post",
#...

Related

Retrieve records which have many-to-many association, using ruby and datamapper

I'm learning Sinatra, and I have read datamapper documentation and found this n to n relationship example:
class Photo
include DataMapper::Resource
property :id, Serial
has n, :taggings
has n, :tags, :through => :taggings
end
class Tag
include DataMapper::Resource
property :id, Serial
has n, :taggings
has n, :photos, :through => :taggings
end
class Tagging
include DataMapper::Resource
belongs_to :tag, :key => true
belongs_to :photo, :key => true
end
What I understood from the code above is that one photo may have many or zero tags, and a tag may have many or zero photos. How do I retrieve a list of photos with the tags associated to it already loaded. I know datamapper uses the lazy approach, so it does not automatically loads the associated classes (in this case photo.tag). So this:
photos = Photo.all
would result in an array with Photo objects without the tags. Is there a way to automatically retrieve it or do I have to iterate over the array and set that manually?
Thanks in advance!
I also have a database which has similar relations. Author, Post, Tag are main models, and Subscribedtag and Tagging are built through has n, :through.
class Author
include DataMapper::Resource
property :id, Serial
property :email, String, :unique => true
property :password, String
property :first_name, String
property :last_name, String
property :bio, Text
property :phone, String, :unique => true
property :twitter, String, :unique => true
property :facebook, String, :unique => true
property :show_phone, Boolean, :default => false
property :show_facebook, Boolean, :default => false
property :show_twitter, Boolean, :default => false
property :is_admin, Boolean, :default => false
property :this_login, DateTime
property :last_login, DateTime
property :session_lasting, Integer, :default => 0
has n, :posts
has n, :subscribedtags
has n, :tags, :through => :subscribedtags
end
class Post
include DataMapper::Resource
property :id, Serial
property :title, String, :required => true
property :body, Text, :required => true
property :is_blog_post, Boolean, :default => true
property :viewed, Integer, :default => 0
property :featured, Boolean, :default => false
property :created_at, DateTime
property :updated_at, DateTime
belongs_to :author
belongs_to :category
has n, :comments
has n, :taggings
has n, :tags, :through => :taggings
validates_length_of :title, :min => 3
validates_length_of :body, :min => 20
validates_format_of :title, :with => /\w/
#some other methods
end
class Tag
include DataMapper::Resource
property :id, Serial
property :name, String, :unique => true
has n, :taggings
has n, :posts, :through => :taggings
has n, :subscribedtags
has n, :authors, :through => :subscribedtags
validates_length_of :name, :min => 1
validates_format_of :name, :with => /\w/
# some other methods
end
class Tagging
include DataMapper::Resource
belongs_to :tag, :key => true
belongs_to :post, :key => true
end
class Subscribedtag
include DataMapper::Resource
belongs_to :tag, :key => true
belongs_to :author, :key => true
end
The way you've defined models, allows you to write queries, like that.
2.2.0 :016 > kant = Tag.get(25) # getting tag instance with id 25 and assign it to variable named kant
=> #<Tag #id=25 #name="İmmanuil Kant">
2.2.0 :017 > kant.posts
=> #returns post instances which has this tag.
2.2.0 :018 > kant.posts.count # count of posts with this tag.
=> 2
2.2.0 :021 > kant.posts.first.author.first_name
=> "Ziya" # check my author class and first_name attribute.
Let's say I want to retrieve the tag instances which has no posts.
a simple ruby command.
2.2.0 :024 > Tag.each {|tnp| puts tnp.name if tnp.posts.count == 0}
Latın
Python
Ruby
Sosializm
Hegel
Or retrieving tags based on posts.
2.2.0 :034 > p = Post.get(9)
=> #<Post #id=9 #title="Why we use Lorem Ipsum" #body=<not loaded> #is_blog_post=false #viewed=0 #featured=false #created_at=#<DateTime: 2015-08-02T23:14:04+05:00 ((2457237j,65644s,0n),+18000s,2299161j)> #updated_at=#<DateTime: 2015-08-02T23:14:04+05:00 ((2457237j,65644s,0n),+18000s,2299161j)> #author_id=1 #category_id=1>
2.2.0 :035 > p.tags
=> [#<Tag #id=19 #name="Bundesliqa">]
retrieve posts which has no tag.
2.2.0 :043 > Post.each {|pnt| puts pnt.id if pnt.tags.count.zero?}
8 #post with id has no tags
2.2.0 :044 > Post.get(8).tags.count
=> 0
you can also query via other attributes.
2.2.0 :046 > Tag.first(:name => "Lorem").id
=> 30
iterate over results
2.2.0 :050 > Tag.first(:name => "Lorem").posts.each {|lorempost| puts lorempost.title} # printing post titles which are tagged with lorem.
Get'em all
qwerty
I also associated authors with tags through Subscribedtags model, which I can easily check which author is subscribed to which tag, and vice versa.
2.2.0 :055 > z = Author.get(1)
=> # returns details of author instance
2.2.0 :056 > z.tags
=> [#, #, #, #]
or querying via Subscribedtag
2.2.0 :057 > z.subscribedtags
=> [#<Subscribedtag #tag_id=2 #author_id=1>, #<Subscribedtag #tag_id=4 #author_id=1>, #<Subscribedtag #tag_id=25 #author_id=1>, #<Subscribedtag #tag_id=30 #author_id=1>]
you can also define your own functions to utilize querying. I've defined a subscribed_tags method which returns an array of subscribed tags' names.
2.2.0 :058 > z.subscribed_tags
=> ["Həyat", "Məstan", "İmmanuil Kant", "Lorem"]
If I want to retrieve the first_name attribute of a random author, who is subscribed to tag named "Lorem",
2.2.0 :062 > Tag.first(:name => "Lorem").authors.sample.first_name
=> "Ziya"
As an answer to your 2nd question, yes, most times you have to iterate.
Because Photos.all return a collection of Photo object instances. And this instances individually has tag attributes, not the array consists of Photo instances.
if you call p = Photo.all; print p.tags; it will return all tags associated with all photos, which may or may not be the thing you want.
Feel free to ask more questions, if these are not enough.

DateTime and DataMapper

Problem
I'm essentially trying to save +4 hours from now in DM. I think the DateTime is correct, but the DataMapper doesn't work properly. Any ideas?
Controller
p DateTime.now
t = DateTime.now + params[:hours] / 24
p t
reward = #player.work params[:hours]
action = Action.new(:player => #player, :type => 0, :completed_at => t, :reward => reward)
model
class Action
include DataMapper::Resource
property :id, Serial
property :type, Integer
property :completed_at, DateTime
property :reward, Integer
TYPES = {
0 => "Work",
1 => "Arena",
2 => "Quest"
}
validates_with_method :type, :method => :valid_type?
def valid_type?
if TYPES.key? self.type.to_i
true
else
[false, "Invalid action type."]
end
end
def get_type
case TYPES[self.type]
when "Work"
"you're working"
when "Arena"
"in the arena"
when "Quest"
"in a quest"
end
end
belongs_to :player
end
try:
DateTime.now + (params[:hours] / 24.0)

ActiveRecord STI delete doesn't work correctly

I want to store two models using active record, but delete doesn't work as expected.
Evaluation has id, name and description
and SqlEvaluation has additional two columns of query_string and database.
I want to use those two tables, and eval_typ_id is used to distinguish which subclass should be used: 1 for SqlEvaluation.
create table eval (
eval_id int,
eval_name varchar,
eval_desc varchar,
eval_typ_id int
);
create table sql_eval (
eval_id int
query_str varchar
database varchar
);
After some research, I used the following code, it works well except "delete", which didn't delete the row in sql_eval. I cannot figure out where is wrong?
require 'rubygems'
require 'active_record'
require 'logger'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(:adapter => "ibm_db",
:username => "edwdq",
:password => "edw%2dqr",
:database => "EDWV2",
:schema => "EDWDQ" )
class Eval < ActiveRecord::Base
set_table_name "eval"
set_primary_key :eval_id
TYPE_MAP = { 1 => 'SqlEval' }
class << self
def find_sti_class(type)
puts "#{type}"
super(TYPE_MAP[type.to_i])
end
def sti_name
TYPE_MAP.invert[self.name]
end
end
set_inheritance_column :eval_typ_id
end
class SqlEval < Eval
has_one :details, :class_name=>'SqlEvalDetails', :primary_key=>:eval_id, :foreign_key=>:eval_id, :include=>true, :dependent=>:delete
default_scope :conditions => { :eval_typ_id => 1 }
end
class SqlEvalDetails < ActiveRecord::Base
belongs_to :sql_eval, :class_name=>'SqlEval',
:conditions => { :eval_type_id => 1 }
set_table_name "sql_eval"
set_primary_key :eval_id
end
se = SqlEval.find(:last)
require 'pp'
pp se
pp se.details
# Eval.delete(se.eval_id)
se.delete
Sorry for messing the code. It is first time to post for me. Here is the code.
require 'rubygems'
require 'active_record'
require 'logger'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(:adapter => "ibm_db",
:username => "edwdq",
:password => "edw%2dqr",
:database => "EDWV2",
:schema => "EDWDQ" )
class Eval < ActiveRecord::Base
set_table_name "eval"
set_primary_key :eval_id
TYPE_MAP = { 1 => 'SqlEval' }
class << self
def find_sti_class(type)
puts "#{type}"
super(TYPE_MAP[type.to_i])
end
def sti_name
TYPE_MAP.invert[self.name]
end
end
set_inheritance_column :eval_typ_id
end
class SqlEval < Eval
has_one :details, :class_name=>'SqlEvalDetails', :primary_key=>:eval_id, :foreign_key=>:eval_id, :include=>true, :dependent=>:delete
default_scope :conditions => { :eval_typ_id => 1 }
end
class SqlEvalDetails < ActiveRecord::Base
belongs_to :sql_eval, :class_name=>'SqlEval',
:conditions => { :eval_type_id => 1 }
set_table_name "sql_eval"
set_primary_key :eval_id
end
se = SqlEval.find(:last)
e = Eval.where(:eval_id => 26)
require 'pp'
pp se
pp e
pp se.details
# Eval.delete(se.eval_id)
se.delete

how to autobuild an associated polymorphic activerecord object in rails 3

class ItemSource < ActiveRecord::Base
belongs_to :product, :polymorphic => true
end
class RandomProduct < ActiveRecord::Base
has_one :item_source, :as => :product, :autosave => true, :dependent => :destroy
end
What I'd like to do is is call:
a = RandomProduct.find(1)
a.item_source
and if item_source doesn't already exist (= nil), then build it automatically (build_item_source).
previously, I did this with alias_chain_method, but that's not supported in Rails 3.
oh, and I also tried this to no avail:
class RandomProduct < ActiveRecord::Base
has_one :item_source, :as => :product, :autosave => true, :dependent => :destroy
module AutoBuildItemSource
def item_source
super || build_item_source
end
end
include AutoBuildItemSource
end
In Rails 3, alias_method_chain (and alias_method, and alias) work fine:
class User < ActiveRecord::Base
has_one :profile, :inverse_of => :user
# This works:
#
# def profile_with_build
# profile_without_build || build_profile
# end
# alias_method_chain :profile, :build
#
# But so does this:
alias profile_without_build profile
def profile
profile_without_build || build_profile
end
end
But there's always accept_nested_attributes_for as an alternative, which calls build when profile_attributes are set. Combine it with delegate (optional) and you won't have to worry if the record exists or not:
class User < ActiveRecord::Base
has_one :profile, :inverse_of => :user
delegate :website, :to => :profile, :allow_nil => true
accepts_nested_attributes_for :profile
end
User.new.profile # => nil
User.new.website # => nil
u = User.new :profile_attributes => { :website => "http://example.com" }
u.profile # => #<Profile id: nil, user_id: nil, website: "http://example.com"...>
If the association is always created, delegation isn't necessary (but may be helpful, anyhow).
(Note: I set :inverse_of to make Profile.validates_presence_of :user work and to generally save queries.)
(Rails 4, FYI)
I personally prefer setting it up with after_initialize
after_initialize :after_initialize
def after_initialize
build_item_source if item_source.nil?
end
This also works well because you can automatically use forms with what would otherwise be an empty association (HAML because it's nicer):
= form_for #product do |f|
= f.fields_for :item_source do |isf|
= isf.label :prop1
= isf.text_field :prop1
If you didn't have the item_source built already, the label and text field wouldn't render at all.
How about creating the item_source when the RandomProduct is created:
class RandomProduct << ActiveRecord::Base
after_create :create_item_source
end
Of course, if you need to pass specific arguments to the item source, you could do something like this, instead:
class RandomProduct << ActiveRecord::Base
after_create :set_up_item_source
protected
def set_up_item_source
create_item_source(
:my => "options",
:go => "here"
)
end
end
Not that you really need a gem for this since it's so simple to do yourself, but here's a gem that makes it even easier to declare an auto-build:
https://github.com/TylerRick/active_record_auto_build_associations

Sequel::Model: Where the methods like create_table come from?

I am trying to understand how Sequel works. The example below inherit from Sequel::Model and calls set_schema, create_table, etc.
I was trying to find the documentation for these methods, but no luck on the rdoc for Sequel::Model: http://sequel.rubyforge.org/rdoc/classes/Sequel/Model.html
Where are these methods coming from and how does Sequel::Model make them available?
class Task < Sequel::Model
set_schema do
primary_key :id
varchar :title, :unique => true, :empty => false
boolean :done, :default => false
end
create_table unless table_exists?
if empty?
create :title => 'Laundry'
create :title => 'Wash dishes'
end
end
They're defined in Sequel::Plugins::Schema::ClassMethods (lib/sequel/plugins/schema.rb) and included when you call plugin :schema in your model.
http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/Schema/ClassMethods.html#M000110
http://sequel.rubyforge.org/rdoc/classes/Sequel/Model.html#M000130
The example in the question is incomplete and won't work unless a connection to a database is setup and the plugin :schema is called from the model.
irb(main):001:0> require "rubygems"
=> true
irb(main):002:0> require "sequel"
=> true
irb(main):003:0>
irb(main):004:0* # connect to an in-memory database
irb(main):005:0* DB = Sequel.sqlite
=> #<Sequel::SQLite::Database: "sqlite:/">
irb(main):006:0> class Task < Sequel::Model
irb(main):007:1> set_schema do
irb(main):008:2* primary_key :id
irb(main):009:2>
irb(main):010:2* varchar :title, :unique => true, :empty => false
irb(main):011:2> boolean :done, :default => false
irb(main):012:2> end
irb(main):013:1>
irb(main):014:1* create_table unless table_exists?
irb(main):015:1>
irb(main):016:1* if empty?
irb(main):017:2> create :title => 'Laundry'
irb(main):018:2> create :title => 'Wash dishes'
irb(main):019:2> end
irb(main):020:1> end
NoMethodError: undefined method `set_schema' for Task:Class
from (irb):7
irb(main):021:0> class Task < Sequel::Model
irb(main):022:1> plugin :schema
irb(main):023:1> set_schema do
irb(main):024:2* primary_key :id
irb(main):025:2>
irb(main):026:2* varchar :title, :unique => true, :empty => false
irb(main):027:2> boolean :done, :default => false
irb(main):028:2> end
irb(main):029:1>
irb(main):030:1* create_table unless table_exists?
irb(main):031:1>
irb(main):032:1* if empty?
irb(main):033:2> create :title => 'Laundry'
irb(main):034:2> create :title => 'Wash dishes'
irb(main):035:2> end
irb(main):036:1> end
=> #<Task #values={:title=>"Wash dishes", :done=>false, :id=>2}>
irb(main):037:0>

Resources