diff --git a/spec/active_record_spec.cr b/spec/active_record_spec.cr index 7c195d2..e62d1cf 100644 --- a/spec/active_record_spec.cr +++ b/spec/active_record_spec.cr @@ -48,10 +48,22 @@ module ActiveRecord end end + class PostsAuthorIdIs < NullAdapter::Query + def call(params, fields) + return false unless fields.has_key?("author_id") && + params.has_key?("1") + actual = fields["author_id"].as(Int) + expected = params["1"].as(Int32) + actual == expected + end + end + NullAdapter.register_query("(number_of_dependents > :1) AND (number_of_dependents < :2)", LessAndMoreDependents.new) NullAdapter.register_query("number_of_dependents IN (:1, :2)", DependentsIn.new) + + NullAdapter.register_query("posts.author_id = :1", PostsAuthorIdIs.new) end class Example; end @@ -66,6 +78,8 @@ class Person < ActiveRecord::Model field number_of_dependents : Int field special_tax_group : Bool + has_many Post, criteria("posts.author_id") == criteria("people.id") + def get_tax_exemption return 0.0 if number_of_dependents < 2 0.17 @@ -94,6 +108,7 @@ class Post < ActiveRecord::Model field title : String field content : String field created_at : Time + field author_id : Int field_level :protected @@ -378,5 +393,45 @@ module ActiveRecord FakeAdapter.instance.table_name.should eq("example_models") end end + + describe "directive has_many" do + it "allows to join people with posts" do + # ARRANGE + person = new_person.create + first_post = Post.create({ + "title" => "My first post", + "content" => "Content", + "author_id" => person.id + }) + second_post = Post.create({ + "title" => "My second post", + "content" => "Content", + "author_id" => person.id + }) + + other_person = new_other_person.create + other_post = Post.create({ + "title" => "Other post", + "content" => "Content", + "author_id" => other_person.id + }) + + # ACT + people_joined_posts = Person.join(Post).all + actual = people_joined_posts.first.posts.map &.id + + # ASSERT + expected = [first_post, second_post].map &.id + actual.should eq(expected) + end + + pending "does only one query (an actual join)" do + + end + + pending "also allows to apply where after the join" do + + end + end end end diff --git a/src/directives/has_many.cr b/src/directives/has_many.cr new file mode 100644 index 0000000..ce99218 --- /dev/null +++ b/src/directives/has_many.cr @@ -0,0 +1,31 @@ +module ActiveRecord::Directives + module HasMany + macro has_many(foreign_model, query) + def self.join(t : {{foreign_model}}.class) + HasManyWrapper({{MACRO_CURRENT.last.id}}) + .new({{foreign_model}}.table_name_value, {{query}}) + end + + @cached_{{foreign_model.stringify.downcase.id}}s : Array({{foreign_model}})? + + def {{foreign_model.stringify.downcase.id}}s + @cached_{{foreign_model.stringify.downcase.id}}s ||= + fetch_{{foreign_model.stringify.downcase.id}}s + end + + def fetch_{{foreign_model.stringify.downcase.id}}s + query = ::Query::Equals[({{query}}).left, id] + {{foreign_model}}.where(query) + end + end + + class HasManyWrapper(T) + def initialize(@foreign_table_name : String, @query : ::Query::Query) + end + + def all + T.all + end + end + end +end diff --git a/src/directives/join.cr b/src/directives/join.cr new file mode 100644 index 0000000..49cf0cb --- /dev/null +++ b/src/directives/join.cr @@ -0,0 +1,7 @@ +module ActiveRecord::Directives + module Join + # macro def join(foreign_model) + # {{@type}}::HasMany_{{foreign_model}} + # end + end +end diff --git a/src/model.cr b/src/model.cr index 501b373..36cffde 100644 --- a/src/model.cr +++ b/src/model.cr @@ -1,5 +1,6 @@ require "./criteria_helper" require "./support" +require "./directives/*" require "pool/connection" @@ -15,6 +16,9 @@ module ActiveRecord include CriteriaHelper extend CriteriaHelper + include Directives::HasMany + extend Directives::Join + MACRO_CURRENT = [] of String DEFAULT_CONNECTION_POOL_CAPACITY = 1 @@ -126,7 +130,7 @@ module ActiveRecord @@adapter_name end - private def self.table_name_value + def self.table_name_value (@@table_name ||= Support.plural(name)).not_nil! end