I'm working on an infrastructure for Rails application and I'm trying to take something out of someones existing project.
I am sort of new to rails but I read the guides on plugins and engines ect..
So I have a gemified Engine, containing some module. I have a model say SharedPost trying to extend said module and I'm getting the uninitialized constant error
uninitialized constant Stake::SharedPost
Here's some of what my engine looks like:
#file: lib/stake/shared_post.rb
module Stake
module SharedPost
...
end
end
#file: lib/stake/engine.rb
module Stake
class Engine < ::Rails::Engine
isolate_namespace Stake
end
end
And in the main app I've got
#file: Gemfile
...
gem 'stake'
...
#file: config/routes.rb
Pop::Application.routes.draw do
root :to => 'home#index'
mount Stake::Engine, :at => '/stake'
end
#file: app/models/posted.rb
class Posted < ActiveRecord::Base
extend Stake::SharedPost
...
end
end
The main application will load, though with no available data on it.
While I try to run
rake db:seed
for example when trying to load the Posted model I get the error uninitialized constant Stake::SharedPost
What am I missing to get access to my gem's namespaced modules?
EDIT:
I've read into the acts_as pattern and that doesn't seem to be the cleanest way of doing things, plus I'm not sure how to implement that onto my engine. Is there another solution?
In lib/stake.rb are you including the lib/stake/shared_post.rb file?
It should look something like this:
# file lib/stake.rb
require "stake/shared_post.rb"
module stake
....
end
Related
I'm trying to make a Ruby on Rails engine, and I want the initializer to be able to have access to the helpers and models.
I'll write below an example, part of the code, and the error that I have. It may not be the recommended way, because I can see that in some cases I'm repeating myself, but it's the first engine I make.
file lib/my_engine/engine.rb
module MyEngine
require 'my_engine/functions'
class Engine < ::Rails::Engine
isolate_namespace MyEngine
config.autoload_paths += %W( #{config.root}/lib )
end
class GlobalVars
attr_accessor :foo
def initialize
#foo = MyEngine::Functions.new
end
end
class << self
mattr_accessor :GLOBAL
mattr_accessor :USER_CONFIG
self.GLOBAL = MyEngine::GlobalVars.new
# add default values of more config vars here
self.USER_CONFIG = 'default config'
end
def self.setup(&block)
yield self
end
end
file lib/my_engine/functions.rb
module MyEngine
require '../../app/helpers/my_engine/options_helper'
class Functions
include MyEngine::OptionsHelper
attr_accessor :some_link
def initialize
#some_link = get_option('dummy')
end
end
end
There is also a controller named OptionsController in app/controllers/my_engine, and OptionsHelper in app/helpers/my_engine/options_helper.rb:
module MyEngine
module OptionsHelper
def get_option(name)
MyEngine::Option.new
end
end
end
When I try to run the dummy application, this error occurs:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
If I change to just Option.new, I have this error:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::OptionsHelper::Option (NameError)
For ::MyEngine::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
For ::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant Option (NameError)
The dummy application has nothing in it. All helpers and models defined above are in the engine.
Before this, I had other errors because it couldn't access the helper, or the Functions class. I had to add require and include to make it work even if they are placed in the same directory. Also, to work, I had to move GlobalVars from its own file inside engine.rb.
Can somebody show me what I'm doing wrong?
After I used required for every class, I ended with ActiveRecord::ConnectionNotEstablished, and it seems that not everything is loaded and available at that point when the GLOBAL object is created.
So I moved the code that was using the models in a separate init method. Then, I added an after initialize event:
config.after_initialize do
MyEngine.GLOBAL.init
end
I see a possible problem: because you are inside module MyEngine it might be possible that actually rails is looking for MyEngine::MyEngine::Option, so I see two approaches:
just write Option: this will look for MyEngine::Option
write ::MyEngine::Option this will look in the global namespace and find MyEngine::Option
Secondly, if that does not help, even though your path seems correct, but you can always explicitly require "my_engine/option" at the top of the file. I am not entirely sure the autoloading in an engine works in quite the same way, and I tend to, in my engine file, require almost everything (to make sure it works).
In my engine.rb I do
require_relative '../../app/models/my_engine/option'
maybe this will help, but it is not a nice solution.
I want to create a rails 5+ gem that must create a migration as it would have to use a model and save/access data in DB.
My gem has some simple classes I stored in lib/ but it must use some that should access DB and this is what I don't know how to do.
Language should update itself in a DB
Languages should list all existing Languages and create a new Language
Basically in a rails app, I whould create the migrations and models, but I couldn't find tutorials about how to do that, and the rails doc did not help me.
Any help is appreciated.
Take a look at Rails Engines.
With an engine you can define generators, models, routes, controllers, etc and hook into a host rails app.
# routes
MyCustomGem::Engine.routes.draw do
resources :languages
end
# controllers
module MyCustomGem
class LanguagesController < ApplicationController
...
end
end
When installing this in a rails app, you'll use
$ bin/rails my_custom_gem:install:migrations
And that will create the migration in the host app.
If you don’t want to copy the migrate file, you can execute migrate directly in the application as follows
module MyEngine
class Engine < ::Rails::Engine
isolate_namespace MyEngine
initializer :append_migrations do |app|
unless app.root.to_s.match root.to_s
config.paths["db/migrate"].expanded.each do |expanded_path|
app.config.paths["db/migrate"] << expanded_path
end
end
end
end
end
When installing this in a rails app
bin/rails db:migrate
I'm new in Ruby and RoR and I'd like to create an admin section for a demo app.
From a little research I've done I've found two different options for creating an admin section. Example:
# config/routes.rb
namespace :admin do
resources :users
end
# app/controllers/admin_controller.rb
class AdminController < ApplicationController
before_filter :authorized?
...
end
Which of the two options is the most proper way to define controllers for the admin section, or they are both equally same?
# app/controllers/admin/users_controller.rb
# I think this is what rails generates if I run the "rails g controller admin::users" command
class Admin::UsersController < AdminController
...
end
# or instead
module Admin
class UsersController < AdminController
....
end
end
Both approaches yield to the same result, which is an UsersController which inherits from AdminController and is found in the Admin module (namespace).
Admin::MyClass is just a shortcut for module Admin ... class MyClass, but...
I would however prefer the explicit nested code (with module Admin on its own line), because it does make a different if the Admin-module has never been defined before. This probably won't happen to you when hacking with standard rails, but can happen when you write ruby code outside of rails.
See these examples:
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
will lead to
i.rb:1:in `<main>': uninitialized constant I (NameError)
if you never declared the I and nested Am modules before in your code.
Whereas
module I
module Am
class AClass
end
end
end
i = I::Am::AClass.new
puts i.inspect
will work:
#<I::Am::AClass:0x00000001d79898>
because the modules are created along the path to AClass (at least this is how I think about it).
If you ever run in that problem and want to save whitespaces (because you will usually indent stuff in a module definition), there are some idioms to use. The one that solves the problem in the most obvious way (again, to me) is the following:
# Predefine modules
module I ; module Am ; end ; end
# Just a shortcut for:
# module I
# module Am
# end
# end
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
#<I::Am::AClass:0x000000024194a0>
Just found that the nature of your question (it is not about an Admin-Interface, more about Module-Syntax) is also nicely discussed here Ruby (and Rails) nested module syntax . And I would love to see a ruby-bug report/feature-request on this :)
You can also use the administration framework for Ruby on Rails applications like
ActiveAdmin https://github.com/activeadmin/activeadmin
OR
Railsadmin
https://github.com/sferik/rails_admin
We have a concern we want to use as a mixin for our User classes. This concern is found in our separate rails engine that we use for multiple products.
Everything in this engine, we keep in the same module, which we will call MyEngine.
module MyEngine
module EngineUser
extend ActiveSupport::Concern
end
end
And we are tring to include it like any other concern in our Rails Application:
class User < ActiveRecord::Base
include MyEngine::EngineUser
# ...
end
This causes an error where it says: (formatted some for readability)
/Users/foo/.rvm/gems/ruby-2.1.5/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:495:in `load_missing_constant':
Unable to autoload constant EngineUser,
expected
/Users/foo/Perforce/engine_folder/app/models/concerns/engine_user.rb
to define it
Which..... is the right file... <.<
If I remove the module MyEngine around the class, and form the include in User, it works just fine.
I know rails does so autoloading behind the scenes, but why isn't this working? It knows that file has the class... if I move it to engine/app/concerns it says it cant find it there. So frustrating.
This should solve your problem. I had the same issue recently..
Take a look at the additional module, Concerns, I've added.
module MyEngine
module Concerns
module EngineUser
extend ActiveSupport::Concern
end
end
end
# lib/my_engine/engine.rb
module MyEngine
class Engine < ::Rails::Engine
config.autoload_paths += %W(#{MyEngine::Engine.root}/app/models/my_engine/concerns/engine_user.rb)
isolate_namespace MyEngine
end
end
# app/models/user.rb
class User < ActiveRecord::Base
include MyEngine::Concerns::EngineUser
# ...
end
Here is the RailsGuide post that led me to the answer.
So the answer from Justin Licata is probably the "correct" answer, but the solution that I ended up going with was as follows:
in engine_folder/lib/my_engine.rb, which is what is included as part of loading the engine, I just used the line:
Dir[File.dirname(__FILE__) + "/my_engine/concerns/**/*.rb"].each { |file| require file[0..-4]}
It certainly has some smell to it, I admit, but it lets us follow our own conventions and structure, and allows us to add folder hierarchy as needed without worrying about rails autoloading issues.
Thanks for the answers!
Using Rails 3.2.2 and Ruby 1.9.2.
I have a rails mountable engine EngineA that declares a User class inheriting form ActiveRecord::Base. I have another engine EngineB that wants to inject functionality into EngineA::User. Right now what I have done is shown below:
Method 1:
#EngineA app/models/engine_a/user.rb
module EngineA
class User < ActiveRecord::Base
has_attached_file :avatar
has_many :somethings
end
end
#EngineB lib/engine_b/user.rb
module EngineB
module User
def self.extended obj
obj.class_eval do
has_many :something_elses
end
end
end
end
EngineA::User.extend EngineB::User
This gives me an uninitialized constant EngineA::User error. Even when I require that specific file I run into the problem of EngineA needing paperclip so that has_attached_file is understood. That road ended when I realized I would have to know and require the dependencies for EngineA inside EngineB.
Method 2:
I used the same code as before except I removed the last line EngineA::User.extend EngineB::User from the EngineB user.rb file. I then moved that call to an initializer inside EngineB.
#EngineB config/initializers/my_mixin.rb
EngineA::User.extend EngineB::User
This worked perfectly!!! Except in development mode when I would change code and the models would refresh. The only thing that was refreshed was the EngineA::User and not the mixin that I had put as an initializer. So once I changed code, I lost all of my extended functionality.
I'm not even positive this is the most 'efficient' way to do this... any help would be greatly appreciated. Thanks in advance.
According to the configuration documentation, you can use an ActionDispatch callback to load items. These callbacks will run when at every request if cache_classes is set to false, like in development mode.
Inside of your EngineB.rb file, you might try something like this:
if Rails.env.development?
ActionDispatch::Callbacks.to_prepare do
load "#{File.expand_path(File.dirname(__FILE__))}/../config/initializers/my_mixin.rb"
end
end