I am trying to create a Active Record tableless Model. My user.rb looks like this
class User < ActiveRecord::Base
class_inheritable_accessor :columns
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
column :name, :text
column :exception, :text
serialize :exception
end
When creating the new object in controller
#user = User.new
I am getting the error
Mysql2::Error: Table 'Sampledb.users' doesn't exist: SHOW FIELDS FROM users
class Tableless
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
def self.attr_accessor(*vars)
#attributes ||= []
#attributes.concat( vars )
super
end
def self.attributes
#attributes
end
def initialize(attributes={})
attributes && attributes.each do |name, value|
send("#{name}=", value) if respond_to? name.to_sym
end
end
def persisted?
false
end
def self.inspect
"#<#{ self.to_s} #{ self.attributes.collect{ |e| ":#{ e }" }.join(', ') }>"
end
end
Few things:
Firstly you are using the Rails2 approach outlined in Railscast 193 when really you should be using the Rails 3 approach, outlined in Railscast 219
You probably don't want to inherit from ActiveRecord::Base when doing this sort of thing.
Read Yehuda Katz's blog post on this.
As mentioned by stephenmurdoch in rails 3.0+ you can use the method outlined in railscasts 219
I had to make a slight modification to get this to work:
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
unless attributes.nil?
attributes.each do |name, value|
send("#{name}=", value)
end
end
end
def persisted?
false
end
end
Don't inherit your class from ActiveRecord::Base.
If a model inherits from ActiveRecord::Base as you would expect a model class to,it wants to have a database back-end.
Just remove:
class_inheritable_accessor :columns
And it should work, even with associations just like a model with a table.
Just for anyone still struggling with this. For rails 2.x.x
class TestImp < ActiveRecord::Base
def self.columns
#columns ||= []
end
end
For rails 3.1.x you can either include ActiveModel (as explained by #ducktyped) without inheriting from ActiveRecord or If you do need to inherit from ActiveRecord::Base due to some reason then the above with one other addition:
class TestImp < ActiveRecord::Base
def attributes_from_column_definition
[]
end
def self.columns
#columns ||= []
end
end
For Rails >= 3.2 there is the activerecord-tableless gem. Its a gem to create tableless ActiveRecord models, so it has support for validations, associations, types.
When you are using the recommended way to do it in Rails 3.x there is no support for association nor types.
Related
We are looking to save JSON API puts with Rails 5 (we are using ember.js on the front end).
We are struggling to work out the best way to save/create relationship data.
Our model:
class ProductSpec < ApplicationRecord
belongs_to :organisation
end
Our serializer:
class ProductSpecSerializer < ActiveModel::Serializer
attributes :id, :name
has_one :organisation
end
Our controller:
class ProductSpecsController < ApplicationController
before_action :set_product_spec, only: [:show, :update, :destroy]
{...}
# PATCH/PUT /product_specs/1
def update
if #product_spec.update(product_spec_params)
render json: #product_spec
else
render json: #product_spec.errors, status: :unprocessable_entity
end
end
{...}
private
def set_product_spec
#product_spec = ProductSpec.find(params[:id])
end
def product_spec_params
params.require(:data)
.require(:attributes)
.permit(
:name
)
end
end
And our JSON
Parameters: {"data"=>{"id"=>"7", "attributes"=>{"name"=>"Tester"}, "relationships"=>{"organisation"=>{"data"=>{"type"=>"organisations", "id"=>"1"}}}, "type"=>"product-specs"}, "id"=>"7", "product_spec"=>{}}
Any changes to name are nicely saved. The question how to best design our controller so that we save the 'organisation' relationship (realise that we also have to permit this, too).
This one is a bit confusing.
I think the line that is problematic is in the controller and it's this line in particular:
recipe_tools = (recipe.recipe_tools + RecipeTool.generic)
My models:
class Recipe < ActiveRecord::Base
...
has_many :recipe_tools, dependent: :destroy
...
end
class RecipeTool < ActiveRecord::Base
belongs_to :story
end
class Story < ActiveRecord::Base
...
has_many :recipe_tools, dependent: :destroy
..
end
This is my controller:
module Api
module Recipes
class RecipeToolsController < Api::BaseController
before_filter :set_cache_buster
def index
# expires_in 30.minutes, public: true
recipe = Recipe.find(params[:recipe_id])
recipe_tools = (recipe.recipe_tools + RecipeTool.generic)
binding.pry
render json: recipe_tools, each_serializer: Api::V20150315::RecipeToolSerializer
end
end
end
end
This is my serializer:
module Api
module V20150315
class RecipeToolSerializer < ActiveModel::Serializer
cached
delegate :cache_key, to: :object
attributes :id,
:display_name,
:images,
:display_price,
:description,
:main_image,
:subtitle
def display_name
object.display_name
end
def images
object.story.get_spree_product.master.images
end
def display_price
object.story.get_spree_product.master.display_price
end
def description
object.story.description
end
def main_image
object.story.main_image
end
def subtitle
object.story.get_spree_product.subtitle
end
def spree_product
binding.pry
spree_product.nil? ? nil : spree_product.to_hash
end
private
def recipe_tool_spree_product
#spree_product ||= object.story.get_spree_product
end
end
end
end
This is my RecipeTool model:
class RecipeTool < ActiveRecord::Base
...
scope :generic, -> { where(generic: true) }
end
In the controller, we call recipe.recipe_tool only once and so I don't think we need to includes recipe_tool. We're not iterating through a collection of recipes and calling recipe_tool on each one so no N+1 problem.
However, we are creating a collection of recipe_tools in the controller by concatenating two collections of recipe_tools together. Recipe.generic is also a SQL query that generates generic recipe_tools.
I think the N+1 problem is happening in generating the JSON response via the serializer. We call recipe_tool.story a lot which would generate a SQL queries each time we call #story and we do that on a collection of recipe_tools.
First, I would fix your associations using :inverse_of, so that I wouldn't have to worry about rails reloading the objects if it happened to traverse back up to a parent object. ie
class Recipe < ActiveRecord::Base
...
has_many :recipe_tools, dependent: :destroy, :inverse_of=>:recipe
...
end
class RecipeTool < ActiveRecord::Base
belongs_to :story, :inverse_of => :recipe_tools
belongs_to :recipe, :inverse_of => :recipe_tools ## this one was missing???
end
class Story < ActiveRecord::Base
...
has_many :recipe_tools, dependent: :destroy, :inverse_of=>:story
..
end
Next I would eager_load the appropriate associations in the controller, something like:
ActiveRecord::Associations::Preloader.new.preload(recipe_tools, :story =>:recipe_tools, :recipe=>:recipe_tools)
before calling the serializer.
In my application I have a polymorphic Idea model. I want to use it with different entities like project, opportunity etc.
In the idea table it is differentiated using ideable_id and ideable_type.
So as an example, for a Project ideable_id is the project_id and ideable_type is Project.
For Opportunity ideable_id is the opportunity_id and ideable_type is Opportunity
I want to create an idea for a model called ideabank. ideabank is a virtual model, it's not an entity like project or opportunity. How should I create such a model without a database representation which will give me ideable_id and ideable_type?
Or should I leave the fields for ideable_id and ideable_type free while adding idea to ideabank?
If you are using rails 3, see this
http://railscasts.com/episodes/219-active-model
models/message.rb
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
messages_controller.rb
def create
#message = Message.new(params[:message])
if #message.valid?
# TODO send message here
flash[:notice] = "Message sent! Thank you for contacting us."
redirect_to root_url
else
render :action => 'new'
end
end
I have a model named User . I am using Devise for authentication.
The 'User' has many 'Profiles' and one default profile . So I have added a column called 'default' to the User model. I want to store the id of the default profile.
However the code fails at the current_user.default = statement.
Error - undefined method `default=' for #User:0x402c480
class User < ActiveRecord::Base
..........
has_many :profiles
...........
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :default
end
.......
class ProfilesController < ApplicationController
before_filter :authenticate_user! ,:except => [:show]
def create
#profile = current_user.profiles.new(params[:profile])
#profile.user = current_user
respond_to do |format|
if #profile.save
#current_user.default= #profile.id
............
end
How do I go about this ? Adding 'default' to the User model doesnt solve the problem.
I suggest you to use STI, it means add "type" column into 'profiles' table
class User < ActiveRecord::Base
has_many :profiles
has_one :profile_default
after_create do
create_profile_default
end
end
class Profile < ActiveRecord::Base
end
class ProfileDefault < Profile
end
def create
#profile = current_user.profiles.new(params[:profile])
#profile.user = current_user
respond_to do |format|
if #profile.save
...
end
I am trying to use a polymorphic comment model on the post model , upload model, etc. Ordinarily i would have a #parent resource to scope one to the other in order for Rails to build the relationship. But because this a multi-tenant subdomain styled application, where all resources will also need to be scoped to the curent_account. I am struggling with how to scope #parent resource under the current_accout.
In ApplicationController I have a current_account method, a find_parent and a parent_collection method:
#Application_controller
class ApplicationController < ActionController::Base
before_filter :current_account
def current_account
unless is_root_domain?
#current_account ||= Account.find_by_subdomain(request.subdomains.first)
end
#current_account
end
def find_parent
params.each do |name ,value|
#parent = $1.pluralize.classify.constantize.find(value) if name =~ /(.*?)_id/
return if #parent
end
end
def parent_collection
#parent_collection ||= current_account.send parent.pluralize
end
end
#comments_controller with only #parent resource without reference to current_account
class CommentsController < ApplicationController
before_filter :find_parent
def new
#comment = #parent.comments.build
end
def create
#comment = #parent.comments.build(params[:comment])
.....
.....
end
end
#comments_controller using only current_account resource without reference to #parent
class CommentsController < ApplicationController
before_filter :current_account
def new
#comment = current_account.comments.build
end
def create
#comment = current_account.comments.build(params[:comment])
.....
.....
end
end
Any guide on how to call current_accout in the controllers in a way that #parent is scoped to it and is there a need for the parent_collection method that i put in the applications_controller. Thanks
Let's make an assumption that your #parent resource is a Post model. Then I would imagine that:
a) your Account model has_many :posts
b) your Post model belongs_to :account
c) your Post model has_many :comments, :as => :commentable
c) your Comment model belongs_to :commentable, :polymorphic => true
So with that in mind you should be able to build scopes like this:
#parent.where(:account_id => current_account.id).comments
You can also refactor it to the commentable model:
def Post < ActiveRecord::Base
scope :by_account, lambda { |account_id| where(:account_id => account_id) }
end
and the use it in the controller like this:
#parent.by_account(current_account.id).comments