As my app is getting a bit big now, I'm trying to use namespacing to help organise my models a little better.
I've created a app/models/theme.rb file which will act as a gateway to the rest of the theme related models which will go in a theme subdirectory.
# app/models directory
theme.rb
theme/compiler.rb
theme/instance.rb
theme/revision.rb
Where instance.rb will start something like....
class Theme::Instance < ActiveRecord::Base
end
and theme.rb is simply...
class Theme
def initialize(args)
# some stuff here
end
end
But anytime I create a new model using a generator, it tries to overwrite a new theme.rb as per below.
rails g model theme::revision
#new theme.rb
module Theme
def self.table_name_prefix
'theme_'
end
end
I could just get rid of the module and copy the def self.table_name_prefix method to each class but that doesn't seem very DRY. I would like to use the table prefix as it keeps things more obvious in the DB. Is there a 'correct' Rails way of going about this that I've missed?
I guess the easiest way is to define Theme as a module that all classes extends
The Theme::Instance imply that the Instance class is contained in a Theme module.
As an alternative, you can create a ThemeUtils module that contains all common method that is included in each class, like a plugin in /lib
Related
I am fairly new to RoR. I have spent the afternoon reading about modules (used as concerns). I have yet to find a good article which describes the file path that using include or extend methods looks up (if include and extend are methods?).
The most specific example I have found was here: Ruby On Rails - Using concerns in controllers. This makes me feel that if I wanted to include the 'Bar' module in my Foo model I would create a concerns/ directory in my models/ directory, and create a 'Bar' module file in this folder.
# in models/concerns/bar.rb
modlue Bar
# do I need this???
extend ActiveSupport::Concern
def speak_bar
puts "model module bar!"
end
end
# in models/foo.rb
class Foo < ApplicationRecord
include Bar
end
# I could then perform:
Foo.new.speak_bar
=> "model module bar!"
And if I wanted to include a Bar module in my Foo controller I would do:
# in controllers/concerns/bar.rb
modlue Bar
# Again, do I need this???
extend ActiveSupport::Concern
def speak_bar
return "controller module bar!"
end
end
# in controllers/foo.rb
class FoosController < ApplicationController
include Bar
def make_bar
#bar = speak_bar
end
end
# I could then use #bar in my views (anywhere else?) as <%= #bar %> and get it to output
=> "controller module bar!"
Summary of questions:
Is this understanding set out above correct in terms of the file paths?
And do I need to use the extend ActiveSupport::Concern line in order to use this path system?
Are include and extend methods?
Thank you for your help.
You should always extend your concerns module with the supplied concerns base from Rails.
Pathing is usually app/models/concerns/file.rb for model concerns and app/controllers/file.rb for controllers and so on.
If you specifically have logic that crosses the controller and models separation, consider placing that in lib, and adding lib to your autoload path.
include and extend are methods. Most things (almost all) are objects in ruby. So almost all operations are methods on objects.
the file path that using include or extend Rails does some magic when starting to autoload a lot of things so you don't have to worry later when you call "Bar". This talk is really helpfull to understand WHY you can just do include Bar inside a rails model without much thinking https://www.youtube.com/watch?v=I0a5zv7uBHw
Usually, you want model related concerns inside /app/models/concerns and controller related concerns inside /app/controllers/concerns, but that's just for organization purposes, rails will autoload them even if you use /app/whatever/concerns, so be carefull about name collisions.
You DO need to extend ActiveSupport::Concern if you want to use the syntax sugar that Concerns provide, but at the end they are just modules that can be included. https://api.rubyonrails.org/classes/ActiveSupport/Concern.html check this examples, concerns are just a way to write modules to share behaviour with a more friendly syntax for common rails patterns.
extend is a method of Object https://docs.ruby-lang.org/en/2.6.0/Object.html#method-i-extend
include is a method of Module https://docs.ruby-lang.org/en/2.6.0/Module.html#method-i-include (and Module inherits extend from Object)
concerns are auto-loaded by rails by default starting from rails v4+. You can read the article written by DHH to get a fair idea of what concern does and what does it try to solve.
However, it gets pretty complicated in determining which scope you are in and what self is in the method. Check out this video by Ryan Bates regarding the problems with concerns.
To solve some parts of the problem, I generally nest the concern inside a folder and refer it by giving a class. For example
# app/models/concerns/user/authentication.rb
class User
module Authentication
extend ActiveSupport::Concern
# stuff
end
end
and include in the model like
# app/models/user.rb
include Authentication
In my opinion, the separation of concerns helps in isolating your methods. For example, you can create a Filterable concern in a similar way, and isolate it from your other models.
Rails 4.2
I've created a multi-step wizard for a User model i.e. a visitor to the app registers as a User over a 4 step form. Its working in it's current setup.
However, I've had to use require statements to include several of the wizard related ruby files. As a consequence these files are not auto loaded by Rails.
I'd like to refactor the relevant files so that they follow Rails conventions and are able to be auto loaded.
Current Structure - is working
app/wizards/user.rb User wizard model - runs validations on each step etc
module Wizard
module User
STEPS = %w[step1 step2 step3 step4].freeze
# omitted class implementations, not relevant
class Base
end
class Step1 < Base
end
class Step2 < Step1
end
class Step3 < Step2
end
class Step4 < Step3
end
end
end
app/controllers/user_wizards_controller.rb
# I have to require the file above, would like to avoid
require Rails.root.join('app', 'wizards', 'user.rb')
class UserWizardsController < ApplicationController
# I have to specify the template, would like to avoid
def step1
render 'wizards/users/step1'
end
# Notice how I have to refer to module/classes above.
def wizard_user_for_step(step)
raise InvalidStep unless step.in?(::Wizard::User::STEPS)
"Wizard::User::#{step.camelize}".constantize.new(session[:user_attributes])
end
end
app/views/wizards/users/step1.html.erb
app/views/wizards/users/step2.html.erb
Attempted Solution
Based on this statement by xfn
The directories in autoload_paths are considered to be root directories, they do not reflect namespacing. For example, the classes below app/models are not under a Models namespace, any namespace has to go within that directory.
The file app/services/doctor_finder.rb for example does not follow autoloading conventions because of that, since it defines Services::DoctorFinder rather than DoctorFinder. Therefore, that file cannot be autoloaded.
I'm going for...
Models
app/wizards/user/user.rb
app/wizards/user/base.rb
app/wizards/user/step1.rb
app/wizards/user/step2.rb
app/wizards/user/step3.rb
app/wizards/user/step4.rb
However, I'm not getting very far. Any ideas?
If you want to autoload these files move it into e.g services or steps directories like:
app/services/wizards/user/step1
and rails should autoload module:
module Wizards::User
class Step1
end
end
Depend on rails version you will need to add 'services' to autoload path.
Regards views:
render 'wizards/users/step1'
isn't bad and In my opinion could be consider as good practice.(using render method allow you to pass non global variables to view)
If you want to remove this line you should put views for UserWizardsController to user_wizards/step1.html.xxx
or if you want to have view in wizards/users/step1.html.xxx
you should scope your controller in that way:
module Wizards
class UsersController < ApplicationController
end
edn
I'm playing around with creating a simple plugin system for my app. Currently, i wan't to be able to extend my activerecord models from inside the engine files.
Let's say i have a following model:
# /my_app/app/models/post.rb
class Post < ActiveRecord::Base
end
What i want to achieve is to be able to put a file, for example, /my_app/example_engine/app/example_engine/models/post.rb which will add some methods to my Post class. I was trying to make that by putting a following content to that file:
# /my_app/example_engine/app/example_engine/models/post.rb
Post.class_eval do
def new_method
"hello"
end
end
But it seems that it's not a proper way of doing this cause it's not working. I probably lack some elementary knowledge about ruby classes or how rails works so i would be really thankful if anyone could help me with that.
Thanks in advance!
If you are ok with subclassing, I would recommend doing the following
Have your engine extend the base class under a namespace such as
module MyEngine
class Post < Post
.....
end
end
No need to eval, just inherit from the base class and extend it as you need.
i have the following function that i make use of in a lot of my models. i use MongoID for MongoDB wrapper
def make_slug
self.slug = self.name.downcase.gsub(/[^a-z1-9]+/, '').chomp('')
end
Which is the best place to place it than copy and paste it in all my models.
Also any recommendation for a good Slug Gem for Rails4?
All models are Inherited from ActiveRecord, you can open the eigenclass to add a singleton method there and use in all models.
The method I'll choose would be putting it under lib directory and require it in each model I need it.
I guess you could do a mixin/module, which you include in your models where you need the functionality. Like this:
Example of the module:
module SlugMaker
def make_slug
# Do your magic here
end
end
And then include it in your model:
class SuperAwesomeModel
include SlugMaker
def some_action
make_slug
end
end
I am using Ruby on Rails 3.2.2 and I would like to "extract" some methods from my models / classes. That is, in more than one class / model I have some methods (note: methods are related to user authorizations and are named the "CRUD way") that are and work practically the same; so I thought that a DRY approach is to put those methods in a "shared" module or something like that.
What is a common and right way to accomplish that? For example, where (in which directories and files) should I put the "shared" code? how can I include mentioned methods in my classes / models? what do you advice about?
Note: I am looking for a "Ruby on Rails Way to make things".
One popular approach is to use ActiveSupport concerns. You would then place the common logic typically under app/concerns/ or app/models/concerns/ directory (based on your preference). An illustrative example:
# app/concerns/mooable.rb
module Mooable
extend ActiveSupport::Concern
included do
before_create :say_moo
self.mooables
where(can_moo: true)
end
end
private
def say_moo
puts "Moo!"
end
end
And in the model:
# app/models/cow.rb
class Cow < ActiveRecord::Base
include Mooable
end
In order to make it work this way you have to add the following line to config/application.rb
config.autoload_paths += %W(#{config.root}/app/concerns)
More information:
http://chris-schmitz.com/extending-activemodel-via-activesupportconcern/
http://blog.waxman.me/extending-your-models-in-rails-3
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
My answer has nothing to do with RoR directly but more with Ruby.
Shraing common code may be done in various ways in Ruby. In my opinion the most obvious way is to create Ruby Modules that contain the code and then include them inside your class/model. Those shared modules are frequently under the lib directory of your app root. For example:
# lib/authorizable.rb
module Authorizable
def method1
#some logic here
end
def method2
#some more logic here
end
end
# app/models/user.rb
class User < ActiveRecord::Base
include Authorizable
end
The User class may now invoke method1 and method2 which belong to the Authorizable module. You can include this module in any other Ruby class you'd like, this way you DRY your code.