I want to create tableless model which doesn't need datebase. At example:
class Post < ActiveRecord::Base
attr_accessible :body, :title, :language_id
belong_to :language
end
class Language
has_many :post
...
end
Will be 2 or 3 language. I don't want to load DB, is it possible to create languges in model by hand?
It might help to read this article: http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/.
In general, your models need not inherit from ActiveRecord, because you can include ActiveModel instead.
On the other hand, you can keep it simple like so:
class Langauge
attr_accessor :posts
def initialize
#posts = []
end
def add_post(post)
#posts << post
end
end
lang = Language.new
lang.add_post(Post.new)
Related
I want to make a copy of an ActiveRecord object, changing a single field in the process (in addition to the id). What is the simplest way to accomplish this?
I realize I could create a new record, and then iterate over each of the fields copying the data field-by-field - but I figured there must be an easier way to do this.
Perhaps something like this:
new_record = Record.copy(:id)
To get a copy, use the dup (or clone for < rails 3.1+) method:
#rails >= 3.1
new_record = old_record.dup
# rails < 3.1
new_record = old_record.clone
Then you can change whichever fields you want.
ActiveRecord overrides the built-in Object#clone to give you a new (not saved to the DB) record with an unassigned ID.
Note that it does not copy associations, so you'll have to do this manually if you need to.
Rails 3.1 clone is a shallow copy, use dup instead...
Depending on your needs and programming style, you can also use a combination of the new method of the class and merge. For lack of a better simple example, suppose you have a task scheduled for a certain date and you want to duplicate it to another date. The actual attributes of the task aren't important, so:
old_task = Task.find(task_id)
new_task = Task.new(old_task.attributes.merge({:scheduled_on => some_new_date}))
will create a new task with :id => nil, :scheduled_on => some_new_date, and all other attributes the same as the original task. Using Task.new, you will have to explicitly call save, so if you want it saved automatically, change Task.new to Task.create.
Peace.
You may also like the Amoeba gem for ActiveRecord 3.2.
In your case, you probably want to make use of the nullify, regex or prefix options available in the configuration DSL.
It supports easy and automatic recursive duplication of has_one, has_many and has_and_belongs_to_many associations, field preprocessing and a highly flexible and powerful configuration DSL that can be applied both to the model and on the fly.
be sure to check out the Amoeba Documentation but usage is pretty easy...
just
gem install amoeba
or add
gem 'amoeba'
to your Gemfile
then add the amoeba block to your model and run the dup method as usual
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class PostsController < ActionController
def some_method
my_post = Post.find(params[:id])
new_post = my_post.dup
new_post.save
end
end
You can also control which fields get copied in numerous ways, but for example, if you wanted to prevent comments from being duplicated but you wanted to maintain the same tags, you could do something like this:
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
exclude_field :comments
end
end
You can also preprocess fields to help indicate uniqueness with both prefixes and suffixes as well as regexes. In addition, there are also numerous options so you can write in the most readable style for your purpose:
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
include_field :tags
prepend :title => "Copy of "
append :contents => " (copied version)"
regex :contents => {:replace => /dog/, :with => "cat"}
end
end
Recursive copying of associations is easy, just enable amoeba on child models as well
class Post < ActiveRecord::Base
has_many :comments
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
has_many :ratings
amoeba do
enable
end
end
class Rating < ActiveRecord::Base
belongs_to :comment
end
The configuration DSL has yet more options, so be sure to check out the documentation.
Enjoy! :)
Use ActiveRecord::Base#dup if you don't want to copy the id
I usually just copy the attributes, changing whatever I need changing:
new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
If you need a deep copy with associations, I recommend the deep_cloneable gem.
In Rails 5 you can simply create duplicate object or record like this.
new_user = old_user.dup
Here is a sample of overriding ActiveRecord #dup method to customize instance duplication and include relation duplication as well:
class Offer < ApplicationRecord
has_many :offer_items
def dup
super.tap do |new_offer|
# change title of the new instance
new_offer.title = "Copy of #{#offer.title}"
# duplicate offer_items as well
self.offer_items.each { |offer_item| new_offer.offer_items << offer_item.dup }
end
end
end
Note: this method doesn't require any external gem but it requires newer ActiveRecord version with #dup method implemented
The easily way is:
#your rails >= 3.1 (i was done it with Rails 5.0.0.1)
o = Model.find(id)
# (Range).each do |item|
(1..109).each do |item|
new_record = o.dup
new_record.save
end
Or
# if your rails < 3.1
o = Model.find(id)
(1..109).each do |item|
new_record = o.clone
new_record.save
end
You can also check the acts_as_inheritable gem.
"Acts As Inheritable is a Ruby Gem specifically written for Rails/ActiveRecord models. It is meant to be used with the Self-Referential Association, or with a model having a parent that share the inheritable attributes. This will let you inherit any attribute or relation from the parent model."
By adding acts_as_inheritable to your models you will have access to these methods:
inherit_attributes
class Person < ActiveRecord::Base
acts_as_inheritable attributes: %w(favorite_color last_name soccer_team)
# Associations
belongs_to :parent, class_name: 'Person'
has_many :children, class_name: 'Person', foreign_key: :parent_id
end
parent = Person.create(last_name: 'Arango', soccer_team: 'Verdolaga', favorite_color:'Green')
son = Person.create(parent: parent)
son.inherit_attributes
son.last_name # => Arango
son.soccer_team # => Verdolaga
son.favorite_color # => Green
inherit_relations
class Person < ActiveRecord::Base
acts_as_inheritable associations: %w(pet)
# Associations
has_one :pet
end
parent = Person.create(last_name: 'Arango')
parent_pet = Pet.create(person: parent, name: 'Mango', breed:'Golden Retriver')
parent_pet.inspect #=> #<Pet id: 1, person_id: 1, name: "Mango", breed: "Golden Retriver">
son = Person.create(parent: parent)
son.inherit_relations
son.pet.inspect # => #<Pet id: 2, person_id: 2, name: "Mango", breed: "Golden Retriver">
Hope this can help you.
Since there could be more logic, when duplicating a model, I would suggest to create a new class, where you handle all the needed logic.
To ease that, there's a gem that can help: clowne
As per their documentation examples, for a User model:
class User < ActiveRecord::Base
# create_table :users do |t|
# t.string :login
# t.string :email
# t.timestamps null: false
# end
has_one :profile
has_many :posts
end
You create your cloner class:
class UserCloner < Clowne::Cloner
adapter :active_record
include_association :profile, clone_with: SpecialProfileCloner
include_association :posts
nullify :login
# params here is an arbitrary Hash passed into cloner
finalize do |_source, record, params|
record.email = params[:email]
end
end
class SpecialProfileCloner < Clowne::Cloner
adapter :active_record
nullify :name
end
and then use it:
user = User.last
#=> <#User(login: 'clown', email: 'clown#circus.example.com')>
cloned = UserCloner.call(user, email: 'fake#example.com')
cloned.persisted?
# => false
cloned.save!
cloned.login
# => nil
cloned.email
# => "fake#example.com"
# associations:
cloned.posts.count == user.posts.count
# => true
cloned.profile.name
# => nil
Example copied from the project, but it will give a clear vision of what you can achieve.
For a quick and simple record I would go with:
Model.new(Model.last.attributes.reject {|k,_v| k.to_s == 'id'}
Try rails's dup method:
new_record = old_record.dup.save
I have used AMS (0.8) with Rails 3.2.19 but one place where I really struggle with them is how to control whether serializers include their associations or not. I obviously use AMS to build JSON
Api's. Sometimes a serializer is the leaf or furthest out element and sometimes it's the top level and needs to include associations. My question is what is the best way to do this or is the solution I do below work (or is best solution).
I have seen some of the discussions but I find them very confusing (and version based). It's clear that for Serializer attributes or associations, there is an an include_XXX? method for each and you can return either a truthy or falsey statement here.
Here's my proposed code - it's a winemaker that has many wine_items. Is this how you would do this?
Model Classes:
class WineItem < ActiveRecord::Base
attr_accessible :name, :winemaker_id
belongs_to :winemaker
end
class Winemaker < ActiveRecord::Base
attr_accessible :name
has_many :wine_items
attr_accessor :show_items
end
Serializers:
class WinemakerSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :wine_items
def include_wine_items?
object.show_items
end
end
class WineItemSerializer < ActiveModel::Serializer
attributes :id, :name
end
and in my controller:
class ApiWinemakersController < ApplicationController
def index
#winemakers=Winemaker.all
#winemakers.each { |wm| wm.show_items=true }
render json: #winemakers, each_serializer: WinemakerSerializer, root: "data"
end
end
I ran into this issue myself and this is the cleanest solution so far (but I'm not a fan of it).
This method allows you to do things like:
/parents/1?include_children=true
or using a cleaner syntax like:
/parents/1?include=[children], etc...
# app/controllers/application_controller.rb
class ApplicationController
# Override scope for ActiveModel-Serializer (method defined below)
# See: https://github.com/rails-api/active_model_serializers/tree/0-8-stable#customizing-scope
serialization_scope(:serializer_scope)
private
# Whatever is in this method is accessible in the serializer classes.
# Pass in params for conditional includes.
def serializer_scope
OpenStruct.new(params: params, current_user: current_user)
end
end
# app/serializers/parent_serializer.rb
class ParentSerializer < ActiveModel::Serializer
has_many :children
def include_children?
params[:include_children] == true
# or if using other syntax:
# params[:includes].include?("children")
end
end
Kinda hackish to me, but it works. Hope you find it useful!
I'm new to rails. I want to create simple url for the users, I don't want to use friendly_id, if possible. I can already change the url using this
#MyModel.rb
def to_param
address
end
#MyController.rb
#user = Admin::Account.find_by_address(params[:id])
but what I really want is to use name instead of address, but the problem is that, name does not belong to MyModel.rb it balongs to OtherModel.rb, is this even pissible? please help
here is a sample of what I want
#MyModel.rb
def to_param
name #name is a data/column from OtherModel.rb
end
#MyController.rb
#user = Admin::Account.find_by_name(params[:id])
#instead of using find_by_address I can now use find_by_name
here is a sample of my model relations
class Admin::MyModel < ActiveRecord::Base
has_many :other_model, :class_name => 'Admin::OtherModel', dependent: :destroy
end
class Admin::OtherModel < ActiveRecord::Base
belongs_to :my_model, :class_name => 'Admin::MyModel'
end
Let's say I have two models; Post & Comment
class Post < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
before_save :do_something
def do_something
# Please, let me do something!
end
end
I have a form for Post, with fields for comments. Everything works as expected, except for the filter. With the above configuration, before_save filter on Comment isn't triggered.
Could you explain why, and how I can fix this?
Rails doesn't instantiate and save the comments individually in this case. You would be better off adding a callback in your Post model to handle this for nested comments:
class Post < AR::Base
before_save :do_something_on_comments
def do_something_on_comments
comments.map &:do_something
end
end
According to Bryan Helmkamp, it's better to use the form object pattern than it is to use accepts_nested_attributes_for. Take a look at 7 Patterns to Refactor Fat ActiveRecord Models
Maybe you could do something like this?
class NewPost
include Virtus
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :post
attr_reader :comment
# Forms are never themselves persisted
def persisted?
false
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
#post = Post.create!
#comment = #post.comment.create!
end
end
do_something would get called when you create the comment.
I want to make a copy of an ActiveRecord object, changing a single field in the process (in addition to the id). What is the simplest way to accomplish this?
I realize I could create a new record, and then iterate over each of the fields copying the data field-by-field - but I figured there must be an easier way to do this.
Perhaps something like this:
new_record = Record.copy(:id)
To get a copy, use the dup (or clone for < rails 3.1+) method:
#rails >= 3.1
new_record = old_record.dup
# rails < 3.1
new_record = old_record.clone
Then you can change whichever fields you want.
ActiveRecord overrides the built-in Object#clone to give you a new (not saved to the DB) record with an unassigned ID.
Note that it does not copy associations, so you'll have to do this manually if you need to.
Rails 3.1 clone is a shallow copy, use dup instead...
Depending on your needs and programming style, you can also use a combination of the new method of the class and merge. For lack of a better simple example, suppose you have a task scheduled for a certain date and you want to duplicate it to another date. The actual attributes of the task aren't important, so:
old_task = Task.find(task_id)
new_task = Task.new(old_task.attributes.merge({:scheduled_on => some_new_date}))
will create a new task with :id => nil, :scheduled_on => some_new_date, and all other attributes the same as the original task. Using Task.new, you will have to explicitly call save, so if you want it saved automatically, change Task.new to Task.create.
Peace.
You may also like the Amoeba gem for ActiveRecord 3.2.
In your case, you probably want to make use of the nullify, regex or prefix options available in the configuration DSL.
It supports easy and automatic recursive duplication of has_one, has_many and has_and_belongs_to_many associations, field preprocessing and a highly flexible and powerful configuration DSL that can be applied both to the model and on the fly.
be sure to check out the Amoeba Documentation but usage is pretty easy...
just
gem install amoeba
or add
gem 'amoeba'
to your Gemfile
then add the amoeba block to your model and run the dup method as usual
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class PostsController < ActionController
def some_method
my_post = Post.find(params[:id])
new_post = my_post.dup
new_post.save
end
end
You can also control which fields get copied in numerous ways, but for example, if you wanted to prevent comments from being duplicated but you wanted to maintain the same tags, you could do something like this:
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
exclude_field :comments
end
end
You can also preprocess fields to help indicate uniqueness with both prefixes and suffixes as well as regexes. In addition, there are also numerous options so you can write in the most readable style for your purpose:
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
include_field :tags
prepend :title => "Copy of "
append :contents => " (copied version)"
regex :contents => {:replace => /dog/, :with => "cat"}
end
end
Recursive copying of associations is easy, just enable amoeba on child models as well
class Post < ActiveRecord::Base
has_many :comments
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
has_many :ratings
amoeba do
enable
end
end
class Rating < ActiveRecord::Base
belongs_to :comment
end
The configuration DSL has yet more options, so be sure to check out the documentation.
Enjoy! :)
Use ActiveRecord::Base#dup if you don't want to copy the id
I usually just copy the attributes, changing whatever I need changing:
new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
If you need a deep copy with associations, I recommend the deep_cloneable gem.
In Rails 5 you can simply create duplicate object or record like this.
new_user = old_user.dup
Here is a sample of overriding ActiveRecord #dup method to customize instance duplication and include relation duplication as well:
class Offer < ApplicationRecord
has_many :offer_items
def dup
super.tap do |new_offer|
# change title of the new instance
new_offer.title = "Copy of #{#offer.title}"
# duplicate offer_items as well
self.offer_items.each { |offer_item| new_offer.offer_items << offer_item.dup }
end
end
end
Note: this method doesn't require any external gem but it requires newer ActiveRecord version with #dup method implemented
The easily way is:
#your rails >= 3.1 (i was done it with Rails 5.0.0.1)
o = Model.find(id)
# (Range).each do |item|
(1..109).each do |item|
new_record = o.dup
new_record.save
end
Or
# if your rails < 3.1
o = Model.find(id)
(1..109).each do |item|
new_record = o.clone
new_record.save
end
You can also check the acts_as_inheritable gem.
"Acts As Inheritable is a Ruby Gem specifically written for Rails/ActiveRecord models. It is meant to be used with the Self-Referential Association, or with a model having a parent that share the inheritable attributes. This will let you inherit any attribute or relation from the parent model."
By adding acts_as_inheritable to your models you will have access to these methods:
inherit_attributes
class Person < ActiveRecord::Base
acts_as_inheritable attributes: %w(favorite_color last_name soccer_team)
# Associations
belongs_to :parent, class_name: 'Person'
has_many :children, class_name: 'Person', foreign_key: :parent_id
end
parent = Person.create(last_name: 'Arango', soccer_team: 'Verdolaga', favorite_color:'Green')
son = Person.create(parent: parent)
son.inherit_attributes
son.last_name # => Arango
son.soccer_team # => Verdolaga
son.favorite_color # => Green
inherit_relations
class Person < ActiveRecord::Base
acts_as_inheritable associations: %w(pet)
# Associations
has_one :pet
end
parent = Person.create(last_name: 'Arango')
parent_pet = Pet.create(person: parent, name: 'Mango', breed:'Golden Retriver')
parent_pet.inspect #=> #<Pet id: 1, person_id: 1, name: "Mango", breed: "Golden Retriver">
son = Person.create(parent: parent)
son.inherit_relations
son.pet.inspect # => #<Pet id: 2, person_id: 2, name: "Mango", breed: "Golden Retriver">
Hope this can help you.
Since there could be more logic, when duplicating a model, I would suggest to create a new class, where you handle all the needed logic.
To ease that, there's a gem that can help: clowne
As per their documentation examples, for a User model:
class User < ActiveRecord::Base
# create_table :users do |t|
# t.string :login
# t.string :email
# t.timestamps null: false
# end
has_one :profile
has_many :posts
end
You create your cloner class:
class UserCloner < Clowne::Cloner
adapter :active_record
include_association :profile, clone_with: SpecialProfileCloner
include_association :posts
nullify :login
# params here is an arbitrary Hash passed into cloner
finalize do |_source, record, params|
record.email = params[:email]
end
end
class SpecialProfileCloner < Clowne::Cloner
adapter :active_record
nullify :name
end
and then use it:
user = User.last
#=> <#User(login: 'clown', email: 'clown#circus.example.com')>
cloned = UserCloner.call(user, email: 'fake#example.com')
cloned.persisted?
# => false
cloned.save!
cloned.login
# => nil
cloned.email
# => "fake#example.com"
# associations:
cloned.posts.count == user.posts.count
# => true
cloned.profile.name
# => nil
Example copied from the project, but it will give a clear vision of what you can achieve.
For a quick and simple record I would go with:
Model.new(Model.last.attributes.reject {|k,_v| k.to_s == 'id'}
Try rails's dup method:
new_record = old_record.dup.save