Creating a gem with associated Rails models - ruby-on-rails

I would like to package a gem that includes ActiveRecord models that can be associated with models in the existing Rails application. I've been trying to follow the code for acts_as_taggable_on, but I'm running into some problems getting the associations to work.
My gem is called Kitchen. My model defined in the gem is Dish, and I want to add a polymorphic association (as Cook) to a model in the main application such as User.
In lib/kitchen.rb
require "active_record"
require "kitchen/dish.rb"
require "kitchen/cook.rb"
In lib/kitchen/dish.rb
module Kitchen
class Dish < ::ActiveRecord::Base
belongs_to :cook, :polymorphic => true
end
end
In lib/kitchen/cook.rb (lifting code from http://guides.rubyonrails.org/plugins.html#add-an-acts_as-method-to-active-record without much understanding)
module Kitchen
module Cook
extend ActiveSupport::Concern
included do
end
module ClassMethods
def acts_as_cook
class_eval do
has_many :dishes, :as => :cook
end
end
end
end
end
ActiveRecord::Base.send :include, Kitchen::Cook
Finally, I've migrated everything in my dummy app, and include the association in spec/dummy/app/models/user.rb
class User < ActiveRecord::Base
acts_as_cook
end
I'm getting an error any time I try to access user.dishes for a User instance:
NameError:
uninitialized constant User::Dish
Any idea what's missing?

Try this maybe:
has_many :dishes, :as => :cook, :class_name => 'Kitchen::Dish'

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

Undefined method belongs_to usign Rails concern, why?

I'm trying to extend a rails model from a gem.
Using concern I've been able to extend class methods but I cannot extend associations. included do returns undefined method belongs_to. I think Rails cannot load the class properly...
The model is in a engine and I'm trying to access it from my gem.
Here is the code:
# mygem/config/initializers/mymodel_extension.rb
require 'active_support/concern'
module MymodelExtension
extend ActiveSupport::Concern
# included do
# belongs_to :another
# end
class_methods do
def swear
return "I'm not doing it again"
end
end
end
class Myengine::Mymodel
include MymodelExtension
end
From command line:
Myengine::Mymodel.swear
# => "I'm not doing it again"
If I uncomment the included do I get this undefined method 'belongs_to' for Myengine::Mymodel:Class (NoMethodError) error.
Myengine::Mymodelclass should inherit from ActiveRecord::Base to have belongs_to method defined.
ActiveRecord::Base includes bunch of modules, one of which is Associations, where belongs_to association is defined.

Using ActiveRecord:Relation for specific classes, not all relations across ActiveRecord

I'm building a gem in which part of its purpose is to extend associations on a target Class. Although I can easily extend all associations by using something like :
ActiveRecord::Relation.send(:include, MyGem::ActiveRecord::RelationMethods)
This is too broad, and for a Rails App that may use this Gem, I don't want to extend associations for all Classes.
For better granularity, I want to provide the equivalent of :
class User < ActiveRecord::Base
has_many :messages, :extend => MyGem::ActiveRecord::RelationMethods
has_many :comments, :extend => MyGem::ActiveRecord::RelationMethods
end
By using
class User < ActiveRecord::Base
acts_as_my_fancy_gem
has_many :messages
has_many :comments
end
The problem I have is trying to conditionally extend associations within the Gem, when acts_as_my_fancy_gem is added to a class. This is the bare bones of it.
module MyGem
extend ActiveSupport::Concern
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_as_my_fancy_gem
include MyGem::InstanceMethods
end
end
module InstanceMethods
...
end
end
I've looked into reflections, but at this point can find a clear path, and have simply taking stabs in the dark to experiment.
UPDATE:
Currently, I can achieve this with each association by providing a class method like
class User < ActiveRecord::Base
has_many :messages
has_many :comments
fancy_extend :messages
end
module MyGem
extend ActiveSupport::Concern
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_as_my_fancy_gem
include MyGem::InstanceMethods
end
def fancy_extend *associations
class_eval do
associations.each do |association|
reflections[association].options[:extend] = MyGem::ActiveRecord::RelationMethods
end
end
end
end
module InstanceMethods
...
end
end
Adding this approach into the act_as_my_fancy method (which is where I would like to have it) gives me :
# NoMethodError: undefined method `options' for nil:NilClass
Is this rail4? I did not find the :extend option documented. It looks like rails 4 uses blocks to do that nowadays.
It could be as simple as this:
module Fancy
def has_many(name, scope = nil, options = {})
super(name, scope, options) do
def doit
"i did"
end
end
end
end
# in your model
extend Fancy
YourModel.your_relation.doit # => 'i did'

What files are loaded after modifying the controller file?

I have a users_manager engine which has a User model class.
In an other shopping engine, I add some associations in the User model with the code below, in shopping/lib/shopping.rb:
module Shopping
class Engine<Rails::Engine
initializer :shopping_append_user do
UsersManager::User.class_eval do
has_many :products,:class_name=>"Shopping::Product"
has_many :virtues,:class_name=>"Shopping::Virtue"
has_many :containers,:class_name=>"Shopping::Container"
has_many :concerns,:class_name=>"Shopping::Concern"
has_many :remarks,:class_name=>"Shopping::Remark"
has_many :praisings,:class_name=>"Shopping::Praising"
has_one :cart,:class_name=>"Shopping::Cart"
has_one :shop_information,:class_name=>"Shopping::ShopInformation"
has_many :comments,:class_name=>"Shopping::Comment"
has_many :created_orders,:class_name=>"Shopping::Order",:foreign_key=>"creator_id"
has_many :processing_orders,:class_name=>"Shopping::Order",:foreign_key=>"processor_id"
end
end
initializer :shopping_append_file do
Upload::File.class_eval do
has_many :image_uuids,:class_name=>"Shopping::ImageUuid"
end
end
end
def self.table_name_prefix
"shopping_"
end
end
After running rails server, the application works fine. However, after modifying one controller file, I browse the web page and it gives me the following message :
undefined method `products' for #<UsersManager::User:0x00000003022a58>
How does rails reload the file after modifying them? How can I make my engine work right?
My version of rails is 3.2.0.pre from github, Ruby is 1.9.0.
Your initializer isn't reloaded on every request, this means that your customizations on the UsersManager::User class are lost when it is reloaded.
You can do the following instead:
module Shopping
class Engine < Rails::Engine
config.to_prepare do
Shopping.customize_user
Shopping.customize_file
end
end
def self.customize_user
UsersManager::User.class_eval do
has_many :products,:class_name=>"Shopping::Product"
has_many :virtues,:class_name=>"Shopping::Virtue"
has_many :containers,:class_name=>"Shopping::Container"
has_many :concerns,:class_name=>"Shopping::Concern"
has_many :remarks,:class_name=>"Shopping::Remark"
has_many :praisings,:class_name=>"Shopping::Praising"
has_one :cart,:class_name=>"Shopping::Cart"
has_one :shop_information,:class_name=>"Shopping::ShopInformation"
has_many :comments,:class_name=>"Shopping::Comment"
has_many :created_orders,:class_name=>"Shopping::Order",:foreign_key=>"creator_id"
has_many :processing_orders,:class_name=>"Shopping::Order",:foreign_key=>"processor_id"
end
end
def self.customize_file
Upload::File.class_eval do
has_many :image_uuids,:class_name=>"Shopping::ImageUuid"
end
end
def self.table_name_prefix
"shopping_"
end
end
The config.to_prepare block is run once in production and before every request in development (source).

Rails/Ruby Mixin brings unwanted other class methods

Just hoping someone can explain this for me..
I have a Site class that imports a module:
Site
class Site < ActiveRecord::Base
include TrackableChanges
...
In trackable_changes.rb
I have this code..
module TrackableChanges
include ActiveSupport::Callbacks
def self.included(base)
# Initialize module.
base.has_many :change_requests, :as => :model, :dependent => :destroy
#Callbacks
base.before_save :before_save_change_request
base.after_save :after_save_change_request
base.before_destroy :before_destroy_change_request
Facility
end
...
The reference to Facility is really confusing me (I put a trivial reference in here..). Basically in Facility.rb I have this..
class Facility < ActiveRecord::Base
acts_as_citier
acts_as_citier looks a bit like this:
module Citier
def self.included(base)
# When a class includes a module the module’s self.included method will be invoked.
base.send :extend, ClassMethods
end
end
ActiveRecord::Base.send :include, Citier
Now.. Just by referencing Facility in my initial module it is going to the acts_as_citier gem and extending the ActiveRecord of my Site class. I want the acts_as_citier gem for my Facility but NOT for my Site.
Can anyone help stop this include trail bringing in this unwanted reference!?
EDIT
Seems like I can't reference the class Facility at all without it bringing in the ActiveRecord class additions that is defined in the Facility via it's reference to the gem act_as_citier
cities does this...
ActiveRecord::Base.send :include, Citier
class Facility < ActiveRecord::Base
#attr_accessible :name, :type, :facility_type_id, :accessibility_id, :tldc_id, :site_id, :tldc_approved
acts_as_citier if self.to_s == 'Facility' #Protects other classes from accidently getting the AR additions
Adding a condition to the include stops the acts_as_citier gem from extending any class that references 'Facility'.
I'm assuming the include on my Site class > runs through the module > which when it hits a reference to Facility class > runs through the Facility.rb > which runs through acts_as_citier and then hits the extend to active record line. It helped me remembering that every part of a .rb file is an executable.

Resources