ActiveSupport::Concern and extending mongoid model - ruby-on-rails

I am using mongoid with rails 3 and have come lately to a very tough
problem and I need an advice.
I am working on a CMS and one of the ideas was that CMS would provide
some basic models definitions and end user would, if needed, extend
basic class with its own definitions and controls and save them in different collections (tables).
class DcPage
include Mongoid::Document
field a ....
belongs_to b ....
validates a ....
end
class MyPage < DcPage
field c ....
validates c ....
end
Up until last version of mongoid this worked (with little hack) and data
would be saved to my_pages collection. Because of some problem, mongoid no
longer support this behaviour and data always gets saved to dc_pages
collection.
When explaining my problem, mongoid team suggested that I use
ActiveSupport::Concern and provided me with an example. Which works
perfectly OK if extended class is defined in same source file. Which
btw. never happens in praxis.
module CommonBehaviour
extend ActiveSupport::Concern
included do
field :subject, type: String, default: ''
# ...
end
end
class DcPage
include Mongoid::Document
include CommonBehaviour
end
class MyPage
include Mongoid::Document
include CommonBehaviour
end
So far I have found out that it works if I require basic source file in
my second file. Which looks like this:
require '/some/path/to/my/gem/app/models/dc_page.rb
Can you can see my pain now. Basic source file would of course be backed into
gem and therefor becomes a moving target.
Please help me with better solution.
by
TheR

The reason this doesn't work is because this is the pattern for single table inheritance.
You would need to turn off table inheritance in order for this to work.
However, the suggestion from the mongoid devs is the correct route to go in this case.
It looks like you just need to require your module/classes correctly.

Related

Restricting Rails engine functionality to a certain class rather than ActiveRecord::Base

I've created a rails engine which contains some common functionality I need when creating new users. For example, there's a before_validation(on: :create) hook that populates a certain field with something to ensure no user can be created without this field having something in it. It looks similar to:
module OfflineUser
extend ActiveSupport::Concern
included do
before_validation(on: :create) do
self.member_number = rand(0..100000)
end
end
end
ActiveRecord::Base.send(:include, OfflineUser)
If I include the engine into another project and do User.create it correctly populates a member_number field for me. However, because it's added the methods to ActiveRecord::Base, it also tries to populate that field in every model I try to run create on! How can I restrict this functionality to only the User model or other model of my choosing rather than globally on every model. Thanks.
By including it in the specific class:
class User < ActiveRecord::Base
include OfflineUser
end
Get rid of the your last line where you include the module in ActiveRecord::Base.

Rails: Using different model fields in ActiveRecord Concern

Quite new to Rails and have run into an issue I just can't seem to figure out.
I have 2 models, User & Post. Users will have a "name" attribute, Posts will have a "title" attribute.
In both cases, I would like to also maintain a slug that will, on before_save, convert the appropriate column to a "sluggified" version and store that as the slug. I've already got the logic I want in place and have had this working, however, I'd like to abstract the behavior into a Concern.
I cannot seem to figure out a way to set this up - mostly because of the dynamic nature of the source field. I'd like to be able to do something like the following:
class User < ActiveRecord::Base
include Sluggable
act_as_slug :name
end
class Post < ActiveRecord::Base
include Sluggable
act_as_slug :title
end
Unfortunately, no matter what I've tried on the implementation of the concern, I've run into walls.
While I'd like to know what type of implementation is possible either way, I'd also be interested in hearing if this is a good use case for concerns or not?
This seems to work, in the event anyone else is looking for an answer (definitely open to better suggestions from those with more experience). The models look as suggested in the original post.
module Sluggable
extend ActiveSupport::Concern
included do
before_save :generate_slug
class_attribute :sluggable_attribute
def generate_slug
self.sluggify(self.class.sluggable_attribute)
end
def sluggify(attribute)
# Sluggify logic goes here
end
end
module ClassMethods
def acts_as_slug(value)
self.sluggable_attribute = value
end
end
end

Calling class methods from mixed in module in ruby

The scenario: I have a couple of ActiveRecord models in my rails system that all need to be controlled via an access control list. I have a nice little ACL implementation that does what I want, but right now the check-access calls are all duplicated in each controlled object type (document, user, etc).
My intuition is to pull that shared code into a module and use it with a mixin. I'm not sure this is possible (or what the right syntax is), because the mixed-in module has calls to ActiveRecord::Base methods - there's scope and has_many definitions.
The example of what I'd like to accomplish is here:
class Document < ActiveRecord::Base
include Controlled
end
module Controlled
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end
And then I'd have a few other models that derive from ActiveRecord::Base that include Controlled.
It's the has_many and scope calls in the module that are causing heartache - I can't call them from within the mixed-in module, apparently this context doesn't have access to the outer class methods.
Any advice is welcome.
You are correct in that you can't just call class methods from the module like that.
Nowadays the boilerplate code required to do this has been wrapped into ActiveSupport::Concern; it does exactly what you want.
[EDIT]: I also suggest you should study the boilerplate code itself, as it's pretty short and readable and a good example of Ruby metaprogramming.
Aha, this is clearly a ruby newbie failure here - I need to put the has_many and other one-off calls inside an included block. It seems like ActiveSupport::Concern is precisely the right thing to use here:
module Controlled
extend ActiveSupport::Concern
included do
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
end
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end

Two Mongoid models stored in one MongoDB collection

This is something I want to do but I can't figure out if it is possible.
I would have two Ruby classes (SuperNes and MegaDrive) that include the same Module (Console).
So I assume there will be common attributes and a few particular ones. I would like to store it in the same MongoDB Collection (with the store_in helper).
How will I ensure that, for example, SuperNes.all will return only SuperNes data and not MegaDrive ?
Thank you for your answers and for the time you spent reading me !
May be you can try inheritance in mongoid ,
class Console
include Mongoid::Document
end
class SuperNes < Console
end
class MegaDrive < Console
end

Rails inheritance for classes of same behaviour but different attributes

I have been researching on the best approach for my problem which I originally had implemented as a single table inheritance but am deeply concerned about the scalability, as potentially will have thousands of columns in the table.
So the problem is I would like to have products which the methods of each are exactly the same the only difference being the attributes each one contains. It seems that in this situation that mutli-class inheritance (not supported natively in rails?) would be the best approach or some sort of polymorphic associations.
I want to work towards the following
#product.rb
Class Product < ActiveRecord::Base
attr_accessible :title .....
def to_s # some arbitrary method used by all extending classes
....
end
end
#book.rb
class Book < Product
attr_accessible :author...
end
So I want the book to inherit the methods from product and not for the product to know about the attributes required by each subclass. And if possible get all of the products through one query.
I need to know the best way of approaching this, and if I am doing it completely wrong, please note the code written above is just for example to simplify my problem.
What you can do is create a module and include it in several different models.
First, create a file in your lib directory
i.e.) my_module.rb
module MyModule
def full_name
"#{first_name} #{last_name}"
end
end
Then, make sure the module is loaded when your Rails App starts:
In config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Finally, include it in your models:
i.e.) app/models/thing.rb
class Thing < ActiveRecord::Base
attr_accessible :first_name, :last_name
include AdditionMod
end
You can test it in the console:
#thing = Thing.create(first_name: "Awesome", last_name: "Module")
#thing.full_name
=> "Awesome Module"
Found out that I can use H-store in conjunction with postgres that allows me to have a column that contains a schema less hash that can be used with the power of postgres (for an example take a look at http://hstoredemo.herokuapp.com/)

Resources