Rails Model without a table - ruby

I want to create a select list for lets say colors, but dont want to create a table for the colors. I have seen it anywhere, but can't find it on google.
My question is: How can I put the colors in a model without a database table?
Or is there a better rails way for doing that?
I have seen someone putting an array or a hash directly in the model, but now I couldn't find it.

class Model
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :whatever
validates :whatever, :presence => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
attr_accessor will create your attributes and you will create the object with initialize() and set attributes.
The method persisted will tell there is no link with the database. You can find examples like this one:
http://railscasts.com/episodes/219-active-model?language=en&view=asciicast
Which will explain you the logic.

The answers are fine for 2013 but since Rails 4 all the database independent features of ActiveRecord are extracted into ActiveModel. Also, there's an awesome official guide for it.
You can include as many of the modules as you want, or as little.
As an example, you just need to include ActiveModel::Model and you can forgo such an initialize method:
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
Just use:
attr_accessor :name, :age

The easiest answer is simply to not subclass from ActiveRecord::Base. Then you can just write your object code.

What worked for me in Rails 6:
class MyClass
include ActiveModel::Model
attr_accessor :my_property
end

If the reason you need a model without an associated table is to create an abstract class real models inherit from - ActiveRecord supports that:
class ModelBase < ActiveRecord::Base
self.abstract_class = true
end

If you want to have a select list (which does not evolve) you can define a method in your ApplicationHelper that returns a list, for example:
def my_color_list
[
"red",
"green",
"blue"
]
end

Related

Bind ERB Template from more than one class

I am trying to interpolate ERB template from multiple objects in Ruby. It works fine if the source of variables is one class. Whats the best way of doing the interpolation when the ERB contains variables present in different classes.
Here is a strip down version of what I am trying to achieve:
#!/usr/bin/ruby
require 'erb'
require 'pp'
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
#first_name = first_name
#last_name = last_name
end
end
class Animal
attr_reader :animal_type
def initialize(type)
#animal_type = type
end
end
person = Person.new("John", "Doe")
animal = Animal.new("doggie")
template = "<%=first_name%> <%=last_name%> has a <%=type%>"
puts ERB.new(template).result(person.instance_eval { binding })
The above fail with undefined local variable or method 'type' which is correct, since that attribute belongs to object of Animal class.
One work around I have found is to create hashes and use merge to collapse them in to one, but that would be mean a lot of changes to the existing code. Is there a better way to achieve this?
You can use openstruct to make merging attributes a little more friendly so do don't have to rewrite your templates as much.
# in config/application.rb
require 'ostruct'
# in the file where you compile ERB
person = OpenStruct.new Person.new("John", "Doe").attributes
person.type = animal.type
With OpenStruct your object will be a hash but methods like person.first_name will still work. And you can add arbitrary key-vals, so you can make person.type return any value you want.

How do I stub a Class object in Rspec?

I am trying hard to enforce encapsulation (but am probably not doing very well), and want to test the code in Rspec. The Customer class will take a class Object (as klass) when it is instantiated in a factory class. Through an as yet non-existent UI, the Customer will create an Order.
My current test is as follows. I just want to confirm that the order is the Order class.
describe 'Customer' do
let(:customer){Customer.new}
let(:customer_with_instantiation){Customer.new(:klass=>order, :name=>'Oscar Wilde', :number=>'0234567')}
let(:order){double :order, :name=>:order}
it 'klass object to be the order class when customer is instantiated with a klass attribute' do
expect(customer_with_instantiation.klass).to be_a(order)
end
end
Class code as follows:
class Customer
attr_accessor :name, :number, :klass
DEFAULT_CUSTOMER_ORDER = {:order_detail => [{ :dish=>"",
:item_count=>0 }],
:order_total_cost=>0 }
def initialize(options={})
#name=options.fetch(:name, "")
#number=options.fetch(:number, "")
#klass=options.fetch(:klass, Object)
#customer_order=DEFAULT_CUSTOMER_ORDER
end
def place_order(menu)
#requires user input
customer_order=klass.new({:order_detail => [{:dish => :pizza, :item_count => 3},
{:dish => :burger, :item_count => 3}],
:order_total_cost => 210})
klass.test_customer_order(customer_order, self)
end
end
class Order
attr_reader :order_detail, :order_total_cost
attr_accessor :total_check
def initialize(options={})
#order_detail=options.fetch(:order_detail, Object)
#order_total_cost=options.fetch(:order_total_cost, Object)
end
def self.test_customer_order(customer_order, customer, menu, assistant)
customer_order.total_check = 0
customer_order.order_detail.each do |order_item|
menu.dishes.each do |dish|
if order_item[:dish]==dish.name
customer_order.total_check += dish.price*order_item[:item_count]
end
end
end
assistant.take_order(customer_order, customer, customer_order.total_check)
end
end
Any help gratefully appreciated!
By using be_a, you're testing that klass is an instance of klass, which is probably not what you want.
It seems to me that when testing the initialize method and the getter for klass (which is what you're doing, in effect), you should only be interested in confirming that whatever you send into Customer.new can be read afterwards.
So maybe something like this:
class Foo
attr_reader :klass
def initialize(args)
#klass = args.fetch(:klass)
end
end
describe Foo do
describe "#initialize" do
let(:klass) { double }
let(:instance) { Foo.new(klass: klass)}
it "sets klass" do
expect(instance.klass).to eq(klass)
end
end
end
Some general points:
If you want to test whether the order is an instance of klass, you should probably rewrite your code to make that easier to test
klass isn't a very useful name in this case. It isn't clear why a Customer would need a klass.
You want to decouple the order from the customer, but the customer is clearly making some assumptions about the interface of the order. Did you really achieve anything?
I'd recommend not putting test methods in the classes themselves, but rather in the test files.
Using Object as a default in fetch is probably not what you want. To begin with, you probably want them to be instances of some class, not class objects.
Is it really the job of an instance of the Customer class to create orders? If the point is to make sure that any kind of abstract order can be instantiated based on user input, maybe a separate OrderCreator class would be more appropriate? This class could accept user data and an order class and the affected customer.

Referencing class attributes in mixin

I'm fairly new to Ruby, and I have a code organisation problem.
I have a class, called Movie, which includes a module called IMDBMovieInfo.
class Movie
include IMDBMovieInfo
attr_accessor :name
attr_accessor :year
attr_accessor :movieID
end
IMDBMovieInfo has a method which takes the movieID and uses it to build an IMDB URL:
module IMDBMovieInfo
def imdb_url()
"http://www.imdb.com/title/tt#{self.movieID}/"
end
end
The problem here is I'm not sure I should be referencing something in the movie class, as IMDBMovieInfo doesn't know about that class, and shouldn't. I could add an argument of a movie ID, but then if you don't know about the Movie object, you'd be doing this, which doesn't make sense:
movie = Movie.new("Titanic", "1997", "0120338")
movie.imdb_url(movie.movieID)
What would be the correct way to organise this code?
You would only need to extract this code into a separate module if you were to use it in multiple classes. I can however reenact the wish to split up a large model into several small chunks.
You could for example establish a protocol that the including class must adhere to and leave it up to the including class where the id comes from. In the following example, the including class must implement the method imdb_id which is supposed to return the id that will be used for the url:
module IMDBMovieInfo
def imdb_id
raise NotImplementedError, 'including class needs to override imdb_id'
end
def imdb_url
"http://www.imdb.com/title/tt#{imdb_id}/"
end
end
class Movie
include IMDBMovieInfo
def imdb_id
self.movieID
end
attr_accessor :name
attr_accessor :year
attr_accessor :movieID
end
# In another classs
# (suppose IMDB lists computer games in the future)
class ComputerGame
include IMDBMovieInfo
def imdb_id
self.gameID
end
attr_accessor :name
attr_accessor :year
attr_accessor :gameID
end
I must however say that I find this whole extracting into mixins a bit clumsy. Another way would be to create a utility class that knows how to build an url but not where the id comes from:
class IMDBUtil
def initialize(imdb_id)
#imdb_id = imdb_id
end
def imdb_url
"http://www.imdb.com/title/tt#{#imdb_id}/"
end
end
class Movie
include IMDBMovieInfo
def imdb_url
IMDBUtil.new(self.movieId).imdb_url
end
attr_accessor :name
attr_accessor :year
attr_accessor :movieID
end
To wrap this up, here's a great blog post from CodeClimate on how to refactor fat Rails models.
In respect to your comment I would say, that the IMDBMovieInfo module shouldn't have a function that needs a movieID. You could just move it to your Movie class.

Variable scope definitions and inheritance

I'm working on a extended search feature for my webpage.
I looked at ransack, however it's lacking some functionalities I need, makes the url-query string very long and has some bugs (reported).
Thus I started to implement my own hack.
First I want to present my idea, afterwards I want to ask kindly how to fix my issue and in the end if there are other ways to improve this.
The idea:
A model defines something like this (additionally, the model is inside an engine):
module EngineName
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
:name is to define the query-param to use e.g. this would be ?q[name]=something
I know that this is not fully generic like ransack, but well...
:as is to build up the correct form-tag. :string would be for text_field, :integer for number_field and so on. I want to extend it further to implement auto-generating of collections for associations etc.
Now the block is a simple scope to use.
I run into several shortcomings with ransack when building up complex queries (like with count() etc.). Now I can specify my own optimized query in squeel.
I extended ActiveRecord::Base to set up the logic (the global one, not inside the engine. I want to use it everywhere).
I defined a scope :search so I can use Model.search(param[q]) like in ransack.
Also I tried to keep a list of keys which are "searchable" defined by the search_for calls.
class ActiveRecord::Base
##searchable_attributes = Hash.new({})
def self.search_for(name, *opts, &search_scope)
return unless search_scope
##searchable_attributes[name] = {
:type => opts[:as],
:condition => search_scope
}
unless ##searchable_attributes.has_key? :nil
##searchable_attributes[:nil] = Proc.new { scoped }
end
end
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = ##searchable_attributes[:nil].call
q.each do |key, search|
next unless base.class.searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
end
Now the issues:
It has mostly to do with inheritance of the classes. But even after reading and trying 3, 4 it does not worked.
Please take a look at the second line in the scope :search.
There I'm calling the simple Proc I definied above which only includes "scoped"
This is to get arround the issue that self returns "ActiveRecord::Base" and not the model itself like "Post" or "Comment".
It's because the scope is called on the Base class on inheritance, however I did not find anything to fix this.
As search_for is called on the model itself (e.g. Post) the scope-model returned there is "the right one".
Does anyone know how to circumvent this?
The next question would be, how to store the list of "searchable" scopes. I used ##variables. But as they are shared within every subclass, this would be a no-go.
However, it needs to be static as the search_for is called without initialize a instance (isn't it?)
Last but not least, it is somekind horrible to always specify the base-model to use on every scope so that I can chain them together.
Is there any other possibilities to improve this?
Ok, it seems I got it finally myself my putting several other answers from other questions together.
Model:
module EngineName
class Post < ActiveRecord::Base
searchable
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
My "Plugin" currently as an initializer:
class ActiveRecord::Base
def self.searchable
include Searchable
end
end
module Searchable
def self.included(base)
base.class_eval {
##searchable_attributes = Hash.new({})
def self.search_for(name, opts)
return unless block_given?
##searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
# Named scopes
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = self.scoped
q.each do |key, search|
key = key.to_sym
next unless ##searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
}
end
end
Hope it'll help some others working on the same problem.
Rails provides a helper for class_attribute. This provides inheritable class attributes, but allows subclassess to "change their own value and it will not impact parent class". However a hash which is mutated using []= for example would effect the parent, so you can ensure that a new copy is made when subclassing using rubys inherited method
Therefore you could declare and initialise on the base class like so:
module Searchable
extend ActiveSupport::Concern
included do
class_attribute :searchable_attributes
end
module ClassMethods
def inherited(subclass)
subclass.searchable_attributes = Hash.new({})
end
def search_for(name,opts)
return unless block_given?
searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
end
end
Note that I used ActiveSupport::Concern to gain the neater syntax for defining stuff directly on the class and also mixing in class methods. Then you can simply add this to active record base:
ActiveRecord::Base.send(:include, Searchable)
now any classes get their own attributes hash:
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end

List only ActiveRecord subclass methods

Given the following ActiveRecord model:
class User < ActiveRecord::Base
has_many :games
def name
"Joe"
end
def city
"Chicago"
end
end
I'd like to retrieve a list of the methods I added directly to the User class (and not those added by extending ActiveRecord and/or adding associations). Example output:
["name","city"]
Calling User.instance_methods(false) returns method added by ActiveRecord:
["validate_associated_records_for_games", "games", "game_ids", "games=", "game_ids=", "after_create_or_update_associated_records_for_games", "before_save_associated_records_for_games"]
Along with any model attributes from database columns. I'd like to exclude those and just get the custom methods on the subclass.
My purpose is method tracing: I'd like to trace my custom methods while excluding those added by ActiveRecord.
Any ideas?
User.instance_methods - ActiveRecord::Base.instance_methods #=> [:name,:city]
UPDATE:
the order of these methods are significant
class User < ActiveRecord::Base
def self.my_own_methods
self.instance_methods - ##im
end
has_many :games
##im = self.instance_methods
def name
"Joe"
end
def city
"Chicago"
end
end
User.my_own_methods #=> [:name, :city]
This one tested and it works
Use a method_added hook which adds method names to a list, and monkey patch Active Record so that methods added by AR are not added to that list.
If you don't want to crack AR open and start poking around, you could also define a "class macro" which defines a method and adds it to the list. For your own custom methods, use the class macro rather than def.
If you're not familiar with what I'm referring to as a "class macro", it's simply a method like this:
class Class
def mydef(name,&block)
(#methods || []) << name
define_method(name,&block)
end
end
Using something like mydef to define methods rather than def is definitely ugly, but it would solve the problem without requiring any monkey-patching.

Resources