I'm trying to implement shoulda unit tests on a rails 2.3.5 app using mongomapper.
So far I've:
Configured a rails app that uses mongomapper (the app works)
Added shoulda to my gems, and installed it with rake gems:install
Added config.frameworks -= [ :active_record, :active_resource ] to config/environment.rb so ActiveRecord isn't used.
My models look like this:
class Account
include MongoMapper::Document
key :name, String, :required => true
key :description, String
key :company_id, ObjectId
key :_type, String
belongs_to :company
many :operations
end
My test for that model is this one:
class AccountTest < Test::Unit::TestCase
should_belong_to :company
should_have_many :operations
should_validate_presence_of :name
end
It fails on the first should_belong_to:
./test/unit/account_test.rb:3: undefined method `should_belong_to' for AccountTest:Class (NoMethodError)
Any ideas why this doesn't work? Should I try something different from shoulda?
I must point out that this is the first time I try to use shoulda, and I'm pretty new to testing itself.
After studying shoulda more profoundly, I realized what was wrong.
Shoulda's macros (should_belong_to, should_have_many, should_validate_presence_of) are only available for ActiveRecord - after all they are defined on Shoulda::ActiveRecord::Macros.
If I were to use those, I would have to implement macros for Shoulda::MongoMapper::Macros. I'm not sure it is worth it.
I hope this helps anyone finding this post.
Related
I'm just starting to have a play with Rails (using Rspec and Shoulda Matchers) to build a demo blog.
I've literally just started and my first test is failing but I can't understand why.
I think I've set up everything correctly, but when I try to validate that a title is present on my Article model but it returns a failure
Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeDoesNotExistError:
The matcher attempted to set :title on the Article to nil, but that
attribute does not exist.
My model looks like this...
class Article < ApplicationRecord
# attr_accessor :title
validates_presence_of :title
end
and my test...
require 'rails_helper'
RSpec.describe Article do
it { should validate_presence_of :title }
end
If I uncomment out the attr_accessor then the test passes but I understand that it's not required with Rails.
What am I doing wrong?
The attr_accessor is not required, as long as your articles database table has a title column.
You can check that in db/schema.rb.
I usually include ActiveModel::Model into some PORO (for example for a FormObject::SignUp). I've read about the new Rails 5 ActiveRecord::Attribute API, and I thought I will be able to use it for simpler casting, but not luck.
For example, given
class FormObject::SignUp
include ActiveRecord::Model
include ActiveRecord::Attributes
attribute :birthday, :date
validates :birthday, presence: true
end
I got an NameError: undefined local variable or method `reload_schema_from_cache' for FormObjects::SignUp:Class exception when I try to instantiate it.
It is not expected to be used standalone? Thanks
Rails >=5.2
This is now possible in Rails 5.2.
As of Rails 5.2 (#30985), ActiveModel::Atrributes is now available to use in POROs (at least POROs that include ActiveModel::Model)...
Reference: https://github.com/rails/rails/issues/28020#issuecomment-456616836
Documentation: https://www.rubydoc.info/gems/activemodel/ActiveModel/Attributes#attribute_names-instance_method
Rails <5.2
Utilizing the gem ActiveModelAttributesis the only easy way I've been able to find to do this. Depending on your use case, it will probably make sense to use that gem or take a different approach.
Here is the gem: https://github.com/Azdaroth/active_model_attributes
Side note: I got feedback that link-only answers can disappear. If this link disappears, then this answer does indeed become invalid since that will likely mean the gem dosen't exist anymore.
ActiveModel::Attributes is available as of Rails 5.2 (PR).
Try this:
class FormObject::SignUp
include ActiveModel::Model
include ActiveModel::Attributes
attribute :birthday, :date
validates :birthday, presence: true
end
If you look at the documentation it seems that this module cannot be used stand-alone, as it makes a lot of assumptions (mostly about a schema-backed model).
Even if you try with the define_attribute method, you still need to provide implementation for other class methods, like attribute_types.
What's wrong with using ActiveModel::Model in Rails 5?
class Poro
include ActiveModel::Model
attr_accessor :foo
end
I've submitted an issue and they replied that it's not supported currently, but it will be in the future.
Link: https://github.com/rails/rails/issues/28020#event-963668657
I was trying to create a observer to do something when a product is created of update.
The product model is under the namespace "ecommerce", and the path is "app/models/ecommerce/product.rb"
class Ecommerce::Product
include Mongoid::Document
include Mongoid::Timestamps
field :market_price, type: Float, default: 0.0
field :price, type: Float
field :stock, type: Integer, default: 999
blah...blah...blah
belongs_to :shop, :class_name => "Ecommerce::Shop"
end
And then I made a observers folder under app, and made a observer class for the above model. The path is "app/observers/ecommerce/product_observer.rb"
class Ecommerce::ProductObserver < Mongoid::Observer
observe :ecommerce_product
def after_create(ecommerce_product)
# do something
end
end
In the application.rb, I have used a loop to load the observers to config.mongoid.observers like the code below (it's been tested, working fine here)
config.mongoid.observers = Dir["#{config.root}/app/observers/**/*.rb"].collect do |full_name|
File.basename(full_name,'.rb').to_sym
end
Finally I tried to restart the rails server, and the error below came up on the terminal
/Users/Ben/.rvm/gems/ruby-1.9.3-p385#opn/gems/activesupport-3.2.12/lib/active_support/inflector/methods.rb:230:
in `block in constantize': uninitialized constant ProductObserver (NameError)
I was just wondering is there anything I missed for observing the namespaced model?
Could anyone help? Many thanks!!!
Your call to File.basename is stripping off the ecommerce directory. So it's just left with 'product_observer' which becomes ProductObserver without the namespace.
I have friendly_id and ActiveScaffold installed for my Rails application.
Because not all of my models have unique name fields I have to use the Slugged Model to make it work. friendly_id does the job flawlessly I have friendly URLs and I can load the objects using the friendly id.
But when I want to create a new object with ActiveScaffold, it says the following error message:
ActiveScaffold::ReverseAssociationRequired
(Association slugs: In order to
support :has_one and :has_many where
the parent record is new and the child
record(s) validate the presence of the
parent, ActiveScaffold requires the
reverse association (the belongs_to).)
Of course I cannot create the belongs_to association in that side because it's created by the friendly_id module and every model which works slugged way should be included there.
The model looks like this:
class FooBar < ActiveRecord::Base
has_friendly_id :name, :use_slug => true, :approximate_ascii => true
end
In my ApplicationController:
class Admin::FooBarsController < Admin::ApplicationController
active_scaffold :foo_bar do |config|
config.list.columns = [ :id, :name ])
config.update.columns = [ :name ]
config.create.columns = config.update.columns
end
end
Is there a way to make this work?
The versions: friendly_id 3.2.0, ActiveScaffold latest in the rails-2.3 git branch.
UPDATE: Seems like it does not conflict in production mode.
calling
has_friendly_id :name, :cache_column => 'cached_slug', :use_slug => true
... creates a has_many and a has one associations pointing to a slug AR model which hasn't any polymorphic belongs to association properly defined.
So basically what you need to do to solve this error is to define the reverse associations in the controller of your parent model (the one who has friendly_id stuff)
active_scaffold :products do |config|
...
config.columns[:slug].association.reverse = :product
config.columns[:slugs].association.reverse = :product
end
and it works :-)
PS : I use friendly_id as gem and ActiveScaffold VHO master branch for rails 3
In the past I have the same problem , i have solved , but i dont remember my solution , lookin at my code the only relevant hack is to use friendly_id as plugin and load it at last with config.plugin in environemnt.rb
aviable_plugins = Dir.glob(RAILS_ROOT+"/vendor/plugins/*").collect {|i| i.split("/").last }
config.plugins = aviable_plugins + [:friendly_id] #friendly_id must be last
I'M NOT SURE ,sorry, but if you try let my know.
sorry for my english to
I want to create a Rails (2.1 and 2.2) model with ActiveRecord validations, but without a database table. What is the most widely used approach? I've found some plugins that claim to offer this functionality, but many of them don't appear to be widely used or maintained. What does the community recommend I do? Right now I am leaning toward coming up with my own solution based on this blog post.
There is a better way to do this in Rails 3: http://railscasts.com/episodes/219-active-model
This is an approach I have used in the past:
In app/models/tableless.rb
class Tableless < ActiveRecord::Base
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default,
sql_type.to_s, null)
end
# Override the save method to prevent exceptions.
def save(validate = true)
validate ? valid? : true
end
end
In app/models/foo.rb
class Foo < Tableless
column :bar, :string
validates_presence_of :bar
end
In script/console
Loading development environment (Rails 2.2.2)
>> foo = Foo.new
=> #<Foo bar: nil>
>> foo.valid?
=> false
>> foo.errors
=> #<ActiveRecord::Errors:0x235b270 #errors={"bar"=>["can't be blank"]}, #base=#<Foo bar: nil>>
There is easier way now:
class Model
include ActiveModel::Model
attr_accessor :var
validates :var, presence: true
end
ActiveModel::Model code:
module ActiveModel
module Model
def self.included(base)
base.class_eval do
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
end
end
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
end
end
http://api.rubyonrails.org/classes/ActiveModel/Model.html
I think the blog post you are linking is the best way to go. I would only suggest moving the stubbed out methods into a module not to pollute your code.
just create a new file ending in ".rb" following the conventions you're used to (singular for file name and class name, underscored for file name, camel case for class name) on your "models/" directory. The key here is to not inherit your model from ActiveRecord (because it is AR that gives you the database functionality).
e.g.: for a new model for cars, create a file called "car.rb" in your models/ directory and inside your model:
class Car
# here goes all your model's stuff
end
edit: btw, if you want attributes on your class, you can use here everything you use on ruby, just add a couple lines using "attr_accessor":
class Car
attr_accessor :wheels # this will create for you the reader and writer for this attribute
attr_accessor :doors # ya, this will do the same
# here goes all your model's stuff
end
edit #2: after reading Mike's comment, I'd tell you to go his way if you want all of the ActiveRecord's functionality but no table on the database. If you just want an ordinary Ruby class, maybe you'll find this solution better ;)
For the sake of completeness:
Rails now (at V5) has a handy module you can include:
include ActiveModel::Model
This allows you to initialise with a hash, and use validations amongst other things.
Full documentation is here.
There's a screencast about non-Active Record model, made up by Ryan Bates. A good place to start from.
Just in case you did not already watch it.
I have built a quick Mixin to handle this, as per John Topley's suggestion.
http://github.com/willrjmarshall/Tableless
What about marking the class as abstract?
class Car < ActiveRecord::Base
self.abstract = true
end
this will tell rails that the Car class has no corresponding table.
[edit]
this won't really help you if you'll need to do something like:
my_car = Car.new
Use the Validatable gem. As you say, there are AR-based solutions, but they tend to be brittle.
http://validatable.rubyforge.org/
Anybody has ever tried to include ActiveRecord::Validations and ActiveRecord::Validations::ClassMethods in a non-Active Record class and see what happens when trying to setup validators ?
I'm sure there are plenty of dependencies between the validation framework and ActiveRecord itself. But you may succeed in getting rid of those dependencies by forking your own validation framework from the AR validation framework.
Just an idea.
Update: oopps, this is more or less what's suggested in the post linked with your question. Sorry for the disturbance.
Do like Tiago Pinto said and just don't have your model inherit from ActiveRecord::Base. It'll just be a regular Ruby class that you stick in a file in your app/models/ directory. If none of your models have tables and you're not using a database or ActiveRecord at all in your app, be sure to modify your environment.rb file to have the following line:
config.frameworks -= [:active_record]
This should be within the Rails::Initializer.run do |config| block.
You ought to checkout the PassiveRecord plugin. It gives you an ActiveRecord-like interface for non-database models. It's simple, and less hassle than fighting ActiveRecord.
We're using PassiveRecord in combination with the Validatable gem to get the OP's desired behaviour.