I have an existing simple rails app that I'm trying to modify.
I'm working on getting an ActiveModel model to work in this app (existing models all use dynamoid). I built the class and was able to get it to successfully populate a show view.
However, everything blows up when I try to switch to an edit view.
I believe the fix for this is to add include ActiveModel::API inside the class.
At least that is what the docs seem to suggest.
However, when I do that, I get
NameError (uninitialized constant ActiveModel::API)
I found the right gem and installed it gem install activemodel and then tried again, but even with the right gem, the same error occurs. I've tried restarting the rails app - no luck. I've tried adding in a require 'active_model' at the top of the file (as suggested in some probably unrelated github issue) - no luck. I dug through the gemfile.lock and found that it already required an older version of activemodel, so I uninstalled the newer version and installed the older version, restarted rails again, but no luck. Searching for the error message results in red herrings - usually dealing with class-referencing issues within an app rather than with referencing a gem.
If it matters here are my versions:
ruby: 2.7.4
activemodel: 6.1.3.1
rails: 6.1.3.1
Here is what the beginning of my class looks like:
# app/models/complex_model.rb
require 'active_model'
class ComplexModel
include ActiveModel::API
My question: How can I get past this uninitialized constant issue?
You're using the docs for the wrong version of Rails.
ActiveModel::API was introduced in Rails 7. And frankly there is not a whole lot going on if you strip the comments out:
module ActiveModel
module API
extend ActiveSupport::Concern
include ActiveModel::AttributeAssignment
include ActiveModel::Validations
include ActiveModel::Conversion
included do
extend ActiveModel::Naming
extend ActiveModel::Translation
end
def initialize(attributes = {})
assign_attributes(attributes) if attributes
super()
end
# Indicates if the model is persisted. Default is +false+.
#
# class Person
# include ActiveModel::API
# attr_accessor :id, :name
# end
#
# person = Person.new(id: 1, name: 'bob')
# person.persisted? # => false
def persisted?
false
end
end
end
What they did is basically shuffle the parts out of the ActiveModel::Model module into yet another layer. ActiveModel::Model consists of parts that where abstracted out of ActiveRecord in Rails 5 and made usable for non-database backed models and as a foundation blocks for building things like Object Relational Managers and even API client data wrappers.
The idea is that ActiveModel::API will contain just the bare necissities needed to interact with stuff like the polymorphic routing and the I18n module while ActiveModel::Model will at some point include ActiveModel::Attributes which is incompatible with ORMs that provide their own attributes implementation such as ActiveRecord.
Whatever you're doing in Rails 6 you can probally solve it simply including ActiveModel::Model and ActiveModel::Attributes.
Related
On a basic setup with latest Rails (7.0) and PaperTrail gem (13.0) any customizations to PaperTrail's version class are not regarded.
Following the official Readme it is supposed to work like this:
# app/models/paper_trail/version.rb
module PaperTrail
class Version < ActiveRecord::Base
include PaperTrail::VersionConcern
before_validation :print_something
def print_something
puts '!!!'
end
end
end
However, although version records are saved to the database nothing gets printed. If I open rails console and paste the above code, then saving a user (or any other record with has_paper_trail) it does work. Why is this code not loaded properly in the first place? What am I missing?
The title is my question.
devise provide us many useful methods like current_user, authenticate_user!, and so on. I want to know why is it possible to use them without including any module like below.
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
Those method's definition is here
Somebody please help me!
The answer is devise included its helper methods into ActionController on behalf of you when Rails on_load
# devise/rails.rb
module Devise
class Engine < ::Rails::Engine
# ...
initializer "devise.url_helpers" do
Devise.include_helpers(Devise::Controllers)
end
# ...
end
# devise.rb
# ...
def self.include_helpers(scope)
ActiveSupport.on_load(:action_controller) do
include scope::Helpers if defined?(scope::Helpers)
include scope::UrlHelpers
end
ActiveSupport.on_load(:action_view) do
include scope::UrlHelpers
end
end
# ...
I saw many 3rd gems using on_load to include their methods (or themselves) into Rails core, maybe it's a typical way to do that (allows Rails to lazily load a lot of components and thus making the app boot faster). If you install some gems and you could use their methods on your model/controller/view then those gems did the same thing devise did above.
About those methods current_user, authenticate_user! ... they are dynamic methods devise will generate when it did include scope::Helpers into Rails (see code above and the link).
So one very good thing about rails is the fact that you get a lot of things for free out of the box. One of these things at the top level is autoloading.
So in the case of Devise. When you install Devise and run the generate command, you get a devise.rb file inside of your config/initializers/ folder. This file is always autoloaded by rails on server startup and reload. That's how all these methods are able to be use these devise methods without importing anything.
Read more at rails docs
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.
I have a model BusinessLog:
class BusinessLog < ActiveRecord::Base
include BusinessLogging
default_scope { where(business_id: Business.current.id) }
end
it performs operations in a default scope and includes a concern BusinessLog:
class BusinessLog < ActiveRecord::Base
module BusinessLogging
extend ActiveSupport::Concern
module ClassMethods
def log!(options)
create! business_id: options[:business_id], user_id: options[:user_id], action: options[:action]
end
end
end
end
this log! method was being called by a sidekiq worker. Eventually I discovered I couldn't log for several business because of BusinessLog default scope. So I removed the BusinessLog default scope, but it was somehow stuck.
Whenever I tried to perform operations through the module (eg: BusinessLog.log!(options)), it was still working with a default scope (even thought I already removed it). But when I performed operations in the model itself (eg: BusinessLog.create!(options)), it was working as expected, without the -already gone- default scope.
I tried a lot of things to make it work, but only one thing worked: Renamed the module and everything worked perfectly again.
It's worth saying that this just happened on my production environment.
Has anyone been through this? Should I post it as an issue on github? How could I give more information about this -bug-?
I'm using Rails 4.0.1 and ruby 2.0.0
Separate REST JSON API server and client?
I am looking for advice on how to consume my own API (like Twitter is said to) for an app that I plan on making.
I would like to have a REST API that I can then use for an web app, Android app and some analytics/dashboard apps.
Rails has a respond_with option, and I have seen some apps that have an html and json option, but I think that is a less than stellar way of doing things and json is data and html is for presentation, and you are not making use of your json API essentially
It seems stupid but how exactly would I use my REST api from Rails if I want to do a server side html solution? Using something like HTTParty seems like a lot of work, is there a way to access the API more directly (in ASP MVC for example you can instantiate a controller class and then call its methods for example.)
You can use HTTParty and create client model classes that resemble a rails models by including ActiveModel modules.
activeresource gem was used in previous rails versions, but they deprecated it in favor of HTTParty+ActiveModel like solutions.
Update
I have crafted this example (from memory) with the basic ideas, is not a full implementation but I think you will get the idea.
class Post
# Attributes handling
include Virtus
# ActiveModel
include ActiveModel::Validations
extend ActiveModel::Naming
include ActiveModel::Conversion
# HTTParty
include HTTParty
# Virtus attributes
attribute :id, Integer
attribute :title, String
attribute :content, Text # not sure about this one, read virtus doc
# Validations
validates :title, presence: true
def save
return false unless valid?
if persisted?
self.class.put("/posts/#{id}", attributes)
else
self.class.post("/posts", attributes)
end
end
# This is needed for form_for
def persisted?
# If we have an id we assume this model is saved
id.present?
end
def decorate
#decorator ||= PostDecorator.decorate(self)
end
end
gems needed:
httparty
activemodel (present in rails)
virtus
draper