Rails namespace class same as existing active_record model - ruby-on-rails

I'm having a bit trouble with the namespaces in Rails 4.
I have ActiveRecord models Shop, Order, and OrderItem
# model/shop.rb
class Shop < ActiveRecord::Base
# model/order.rb
class Order < ActiveRecord::Base
has_many :order_items
# model/order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :orderable, polymorphic: true
belongs_to :order
I'm replicating the relationship between Order and OrderItem in a namespace like this
# model/shop/order.rb
class Shop::Order
attr_accessor :order_items
def initialize
self.order_items = []
self.order_items << Shop::OrderItem.new
end
# model/shop/order_item.rb
class Shop::OrderItem
attr_accessor :orderable_type, :orderable_id
def initialize(params = {})
if params
self.orderable_type = params['orderable_type'] if params['orderable_type']
self.orderable_id = params['orderable_id'] if params['orderable_id']
end
end
def price
orderable.price
end
def orderable
orderable_type.constantize.find_by(id: orderable_id)
end
def to_h
Hash[
orderable_type: self.orderable_type,
orderable_id: self.orderable_id,
price: self.price
]
end
end
So my problem is that when I initialize Shop::Order.new, sometimes its order_items is an array of OrderItems instead of Shop::OrderItems, and when I test it in the controller, if I type Shop::OrderItem, it will return OrderItem.
I'm wondering if Shop::OrderItem wasn't initialized before OrderItem and cause the issue?

You are running into a namespace collision. Depending on where the code is executing, Shop could be the ActiveRecord model that you've defined in models/shop.rb, or it could be the module namespace that you've defined under models/shops/*.rb. Not only will this cause unpredictable execution, it's also confusing to read.
I recommend using a module namespace other than "Shop". Even calling it "MyShop" would be an improvement. However you'll probably still run into naming collisions between Shop and MyShop::Shop. You should probably rename the Shop class under the MyShop module to avoid this:
For example:
# model/my_shop/my_order.rb
class MyShop::MyOrder
# ...
end
# model/my_shop/my_order_item.rb
class MyShop::MyOrderItem
# ...
end
Having said all that, I feel like you're setting yourself up for a world of hurt. This problem might be better solved using service objects. Google up "Rails Service Objects" for some really good examples.

Related

Is there a way to populate the inverse relationship when using ActiveRecord builder methods

I wanted to use the ActiveRecord association builder methods to build an in memory object. I expected the has_many association of the starship object to contain the crew member object.
Is this possible, or an expected limitation of ActiveRecord?
require 'active_record'
require 'minitest/autorun'
ActiveRecord::Base.establish_connection(
adapter: "sqlite3",
database: ":memory:",
)
ActiveRecord::Schema.define do
create_table(:starships)
create_table(:crew_members) { |t| t.references :starship }
end
class Starship < ActiveRecord::Base
has_many :crew_members, inverse_of: :starship
end
class CrewMember < ActiveRecord::Base
belongs_to :starship, inverse_of: :crew_members
end
class BuilderTests < MiniTest::Test
# fails
def test_build_association_populates_both_sides_of_the_relationship
refute_predicate CrewMember.new.build_starship.crew_members, :empty?
end
# fails
def test_create_association_populates_both_sides_of_the_relationship
refute_predicate CrewMember.create.create_starship.crew_members, :empty?
end
# passes
def test_association_is_not_empty_when_saved_and_reloaded
crew_member = CrewMember.create
starship = crew_member.create_starship
assert crew_member.changed?
assert_predicate starship.crew_members.reload, :empty?
crew_member.save!
refute_predicate starship.crew_members.reload, :empty?
end
end
My use case is kinda flimsy, and this was more a curiosity I observed and wondered if it was possible, or simply wishful behaviour.
build_association does not yet save the object, you must use create_association if you want to achieve that, or manually saving the crew_member.starship instance. So the main reason you get to see crew_member.starship is because it is in memory, but since it wasn't persisted yet, there is no connection between the two. More on association docs

how to conditionally include associations in a Rails Active Model Serializer v0.8

I have used AMS (0.8) with Rails 3.2.19 but one place where I really struggle with them is how to control whether serializers include their associations or not. I obviously use AMS to build JSON
Api's. Sometimes a serializer is the leaf or furthest out element and sometimes it's the top level and needs to include associations. My question is what is the best way to do this or is the solution I do below work (or is best solution).
I have seen some of the discussions but I find them very confusing (and version based). It's clear that for Serializer attributes or associations, there is an an include_XXX? method for each and you can return either a truthy or falsey statement here.
Here's my proposed code - it's a winemaker that has many wine_items. Is this how you would do this?
Model Classes:
class WineItem < ActiveRecord::Base
attr_accessible :name, :winemaker_id
belongs_to :winemaker
end
class Winemaker < ActiveRecord::Base
attr_accessible :name
has_many :wine_items
attr_accessor :show_items
end
Serializers:
class WinemakerSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :wine_items
def include_wine_items?
object.show_items
end
end
class WineItemSerializer < ActiveModel::Serializer
attributes :id, :name
end
and in my controller:
class ApiWinemakersController < ApplicationController
def index
#winemakers=Winemaker.all
#winemakers.each { |wm| wm.show_items=true }
render json: #winemakers, each_serializer: WinemakerSerializer, root: "data"
end
end
I ran into this issue myself and this is the cleanest solution so far (but I'm not a fan of it).
This method allows you to do things like:
/parents/1?include_children=true
or using a cleaner syntax like:
/parents/1?include=[children], etc...
# app/controllers/application_controller.rb
class ApplicationController
# Override scope for ActiveModel-Serializer (method defined below)
# See: https://github.com/rails-api/active_model_serializers/tree/0-8-stable#customizing-scope
serialization_scope(:serializer_scope)
private
# Whatever is in this method is accessible in the serializer classes.
# Pass in params for conditional includes.
def serializer_scope
OpenStruct.new(params: params, current_user: current_user)
end
end
# app/serializers/parent_serializer.rb
class ParentSerializer < ActiveModel::Serializer
has_many :children
def include_children?
params[:include_children] == true
# or if using other syntax:
# params[:includes].include?("children")
end
end
Kinda hackish to me, but it works. Hope you find it useful!

STI with many types

I have a rails 4 app with STI models:
# models/person.rb
def Person < ActiveRecord::Base
end
# models/director.rb
def Director < Person
end
# models/actor.rb
def Director < Person
end
But because one person can be an actor and an director simultaneously, I want STI with many types like:
person = Person.first
person.type = "Director, Actor"
person.save
Actor.first.id => 1
Director.first.id => 1
Is there mechanism in rails or gem for realize this?
Rails does not support this and I'm not aware of any gems that support this as described (i.e. multiple subclass names in the type column).
There is gem at https://github.com/mhuggins/multiple_table_inheritance which uses separate tables for the subclasses and you can always use mixins as an alternative to inheritance.
I believe the more Rails idiomatic way to do something similar would be via scopes, which would allow you to do:
person = Person.first
person.position = 'Director, Actor'
person.save
person.directors.first.id => 1
person.actors.first.id => 1
And you would just have to define a pair of scopes in your Person class:
scope :actors, -> { where('position like ?', '%Actor%') }
scope :directors, -> { where('position like ?', '%Director%') }
You would lose the ability to do person.is_a? with this, but Ruby doesn't really do multiple inheritance in such a way as to allow #is_a? to return true when passed sibling classes anyway. You can also get effectively similar functionality with a simple test method:
def is_actor?
self.position =~ /Actor/
end
def is_director?
self.position =~ /Director/
end
EDIT: I haven't done a lot of Rails 4, so my scope syntax MAY not be right, I just glanced at the docs. The principle should be sound, though.
Thank to all answerers above!
I found solution that most appropriate for me:
I've created hmt association Person-ProfessionsPerson-Profession and leave descendants for Person class (Director and Actor).
# models/profession.rb
Profession < ActiveRecord::Base
has_many :professions_people, dependent: :destroy
has_many :people, through: :professions_people
end
# models/person.rb
def Person < ActiveRecord::Base
has_many :professions_people, dependent: :destroy
has_many :professions, through: :professions_people
end
# models/director.rb
def Director < Person
include PeopleFromProfession
end
# models/actor.rb
def Actor < Person
include PeopleFromProfession
end
I've seed 2 professions with column "class_type" (which should not change in app's work) "Actor" and "Director"
I've also add concern PeopleFromProfession for share some code:
# models/concerns/actor.rb
module PeopleFromProfession
extend ActiveSupport::Concern
included do
default_scope { includes(:professions).where(professions: {class_type: self.name}) }
after_create :create_join_table_record
end
module ClassMethods
def model_name
Person.model_name
end
end
private
def create_join_table_record
self.professions << Profession.where(class_type: self.class.name).first
end
end
default_scope is for scoping only people with specific profession, create_join_table_record callback is monkey-patch for create missed join table record.
Class method model_name was overwriting for purposes, that covered here Best practices to handle routes for STI subclasses in rails
If you will find some problems in that approach, please tell me.

How can I extend an object returned from an ActiveRecord association at runtime?

I have a model as follows:
class Property < ActiveRecord::Base
has_and_belongs_to_many :property_values
end
What I would like to do is to extend any value returned by a find on the property_values extension with a module that is determined by an attribute of the Property object. I've attempted something like this:
class Property < ActiveRecord::Base
has_and_belongs_to_many :property_values, :extend => PropertyUtil::Extensible
def enrich(to_extend)
modules.split(/\s*,\s*/).each do |mod|
to_extend.extend(Properties.const_get(mod.to_sym))
end
end
end
module PropertyUtil
module Extensible
def self.extended(mod)
mod.module_eval do
alias old_find find
end
end
def find(*args)
old_find(*args).map{|prop| proxy_owner.enrich(prop)}
end
end
end
Where all modules that may be selected are defined in the Properties module. In attempting to run with this code, though, there are a couple of problems; first, to my surprise, none of the dynamic finders (property_values.find_by_name, etc.) appear to delegate to find; second, something with how I've done the aliasing leads to a stack overflow when I try to run the find directly.
Is there a way to do what I'm attempting? What method can I alias and override such that all results returned by the association extension, irrespective of how they are retrieved, are extended with the appropriate modules?
Thanks, Kris
I never tried to do this but you may want to try the following (I just changed how the aliases are done):
class Property < ActiveRecord::Base
has_and_belongs_to_many :property_values, :extend => PropertyUtil::Extensible
def enrich(to_extend)
modules.split(/\s*,\s*/).each do |mod|
to_extend.extend(Properties.const_get(mod.to_sym))
end
end
end
module PropertyUtil
module Extensible
def self.extended(mod)
mod.module_eval do
alias_method :old_find, :find
alias_method :find, :new_find
end
end
def new_find(*args)
old_find(*args).map{|prop| proxy_owner.enrich(prop)}
end
end
end
If it does not work here is another idea you may wanna try:
class Value < ActiveRecord::Base
self.abstract_class = true
end
class ExtendedValue < Value
end
class ExtendedValue2 < Value
end
class Property < ActiveRecord::Base
has_and_belongs_to_many :property_values, :class_name => 'ExtendedValue'
has_and_belongs_to_many :property_values_extended, :class_name => 'ExtendedValue'
has_and_belongs_to_many :property_values_extended2, :class_name => 'ExtendedValue2'
end
The idea is to have one hatbm association per "type" (if you can group your extensions that way) and use the one you want at a given time, if you can do what you want that way I am also pretty sure it will have a smaller impact performance than patching every returned object after activerecord returned them.
I am kinda curious at what you are trying to achieve with this :)
It is much easier to simply use classes to change the functionality. You can have classes of PropertyValues with the appropriate behavior and use either STI (Single Table Inheritance) to instantiate the appropriate instance or you can over-ride the 'instantiate' ActiveRecord class method to set the class using the #becomes instance method:
class PropertyValue < AR:Base
def self.instantiate(record)
property_value = super
case property_value.sub # criteria for sub_class
when 'type1' then property_value.becomes(Type1)
when 'type2' then property_value.becomes(Type2)
end
end
end
class Type1 < PropertyValue
def some_method
# do Type1 behavior
end
end
class Type2 < PropertyValue
def some_method
# do Type2 behavior
end
end
I have found that using classes and inheritance provides much cleaner, simpler code and is easier to test.
I ended up using an after_find call on the value class to resolve this problem. This is a pretty suboptimal solution, because it means that the module information ends up needing to be duplicated between the property referent and the value, but it's workable, if less than exactly performant. The performance hit ended up being large enough that I had to cache a bunch of data in the database with the results of computations over large numbers of properties, but this turned out not to be all bad, in that it simplified the process for extraction of report data considerably.
In the end, here are some bits of what I ended up with:
module Properties::NamedModules
def modules
(module_names || '').split(/\s*,\s*/).map do |mod_name|
Property.const_get(mod_name.demodulize.to_sym)
end
end
end
module Properties::ModularProperty
def value_structure
modules.inject([]){|m, mod| m + mod.value_structure}.uniq
end
end
module Properties::Polymorphic
include NamedModules, ModularProperty
def morph
modules.each {|mod| self.extend(mod) unless self.kind_of?(mod)}
end
end
class Property < ActiveRecord::Base
include Properties::NamedModules, Properties::ModularProperty
has_and_belongs_to_many :property_values, :join_table => 'property_value_selection'
def create_value(name, value_data = {})
property_values.create(
:name => name,
:module_names => module_names,
:value_str => JSON.generate(value_data)
)
end
end
class PropertyValue < ActiveRecord::Base
include Properties::Polymorphic
has_and_belongs_to_many :properties, :join_table => 'property_value_selection'
after_find :morph
end

Rails extending ActiveRecord::Base

I've done some reading about how to extend ActiveRecord:Base class so my models would have some special methods. What is the easy way to extend it (step by step tutorial)?
There are several approaches :
Using ActiveSupport::Concern (Preferred)
Read the ActiveSupport::Concern documentation for more details.
Create a file called active_record_extension.rb in the lib directory.
require 'active_support/concern'
module ActiveRecordExtension
extend ActiveSupport::Concern
# add your instance methods here
def foo
"foo"
end
# add your static(class) methods here
class_methods do
#E.g: Order.top_ten
def top_ten
limit(10)
end
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
Create a file in the config/initializers directory called extensions.rb and add the following line to the file:
require "active_record_extension"
Inheritance (Preferred)
Refer to Toby's answer.
Monkey patching (Should be avoided)
Create a file in the config/initializers directory called active_record_monkey_patch.rb.
class ActiveRecord::Base
#instance method, E.g: Order.new.foo
def foo
"foo"
end
#class method, E.g: Order.top_ten
def self.top_ten
limit(10)
end
end
The famous quote about Regular expressions by Jamie Zawinski can be re-purposed to illustrate the problems associated with monkey-patching.
Some people, when confronted with a problem, think “I know, I'll use
monkey patching.” Now they have two problems.
Monkey patching is easy and quick. But, the time and effort saved is always extracted back
sometime in the future; with compound interest. These days I limit monkey patching to quickly prototype a solution in the rails console.
You can just extend the class and simply use inheritance.
class AbstractModel < ActiveRecord::Base
self.abstract_class = true
end
class Foo < AbstractModel
end
class Bar < AbstractModel
end
You can also use ActiveSupport::Concern and be more Rails core idiomatic like:
module MyExtension
extend ActiveSupport::Concern
def foo
end
module ClassMethods
def bar
end
end
end
ActiveRecord::Base.send(:include, MyExtension)
[Edit] following the comment from #daniel
Then all your models will have the method foo included as an instance method and the methods in ClassMethods included as class methods. E.g. on a FooBar < ActiveRecord::Base you will have: FooBar.bar and FooBar#foo
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
With Rails 4, the concept of using concerns to modularize and DRY up your models has been in highlights.
Concerns basically allow you to group similar code of a model or across multiple models in a single module and then use this module in the models. Here is a example:
Consider a Article model, a Event model and a Comment Model. A article or A event has many comments. A comment belongs to either article or event.
Traditionally, the models may look like this:
Comment Model:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Article Model:
class Article < ActiveRecord::Base
has_many :comments, as: :commentable
def find_first_comment
comments.first(created_at DESC)
end
def self.least_commented
#return the article with least number of comments
end
end
Event Model
class Event < ActiveRecord::Base
has_many :comments, as: :commentable
def find_first_comment
comments.first(created_at DESC)
end
def self.least_commented
#returns the event with least number of comments
end
end
As we can notice, there is a significant piece of code common to both Event and Article Model. Using concerns we can extract this common code in a separate module Commentable.
For this create a commentable.rb file in app/model/concerns.
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
# for the given article/event returns the first comment
def find_first_comment
comments.first(created_at DESC)
end
module ClassMethods
def least_commented
#returns the article/event which has the least number of comments
end
end
end
And Now your models look like this :
Comment Model:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Article Model:
class Article < ActiveRecord::Base
include Commentable
end
Event Model
class Event < ActiveRecord::Base
include Commentable
end
One point I will like to highlight while using Concerns is that Concerns should be used for 'domain based' grouping rather than 'technical' grouping. For example, a domain grouping is like 'Commentable', 'Taggable' etc. A technical based grouping will be like 'FinderMethods', 'ValidationMethods'.
Here is a link to a post that I found very useful for understanding concerns in Models.
Hope the writeup helps :)
Step 1
module FooExtension
def foo
puts "bar :)"
end
end
ActiveRecord::Base.send :include, FooExtension
Step 2
# Require the above file in an initializer (in config/initializers)
require 'lib/foo_extension.rb'
Step 3
There is no step 3 :)
Rails 5 provides a built-in mechanism for extending ActiveRecord::Base.
This is achieved by providing additional layer:
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
# put your extensions here
end
and all models inherit from that one:
class Post < ApplicationRecord
end
See e.g. this blogpost.
With Rails 5, all models are inherited from ApplicationRecord & it gives nice way to include or extend other extension libraries.
# app/models/concerns/special_methods.rb
module SpecialMethods
extend ActiveSupport::Concern
scope :this_month, -> {
where("date_trunc('month',created_at) = date_trunc('month',now())")
}
def foo
# Code
end
end
Suppose the special methods module needs to be available across all models, include it in application_record.rb file. If we wants to apply this for a particular set of models, then include it in the respective model classes.
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include SpecialMethods
end
# app/models/user.rb
class User < ApplicationRecord
include SpecialMethods
# Code
end
If you want to have the methods defined in the module as class methods, extend the module to ApplicationRecord.
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
extend SpecialMethods
end
Hope it help others !
Just to add to this topic, I spent a while working out how to test such extensions (I went down the ActiveSupport::Concern route.)
Here's how I set up a model for testing my extensions.
describe ModelExtensions do
describe :some_method do
it 'should return the value of foo' do
ActiveRecord::Migration.create_table :test_models do |t|
t.string :foo
end
test_model_class = Class.new(ActiveRecord::Base) do
def self.name
'TestModel'
end
attr_accessible :foo
end
model = test_model_class.new(:foo => 'bar')
model.some_method.should == 'bar'
end
end
end
I have
ActiveRecord::Base.extend Foo::Bar
in an initializer
For a module like below
module Foo
module Bar
end
end

Resources