Rails 3 serialized model field form_for and field_for not generating correct name - ruby

I have this model:
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_page_objects
entry_pages.map { |url| EntryPage.new(url) }
end
def entry_page_objects_attributes=(attributes)
# ...
end
....
end
This form to render the model:
.....
%p
%p
= crawler_form.label 'Entry pages'
= crawler_form.text_area :entry_pages_text, size: '80x6'
%ul.entry-pages
= crawler_form.fields_for :entry_page_objects do |entry_page_field|
%li=entry_page_field.text_field :url, size: 80
%a{href: '#', class: 'add-button'} Add Entry Page
The problem I have is that the form renders the entry_page_object input names incorrectly(e.g. company_crawler[entry_page_objects_attributes][0][url] instead of company_crawler[entry_page_objects][0][url]). I am really not sure what to do, I have read the documentation and the example says that just by defining attr_attributes=(attributes) and persisted? I will be able to use fields_for for collections just if they were associations defined with accept_nested_fields.
I have seen different solutions like just giving String 'entry_page_objects[]' to fields_for but I want to be consistent with rails naming convention and I know I can use form_tag instead of form_for but I want to make fields_for work as expected.

Here is some information for all that have not understood properly how nested_attributes works, like me.
What I have reported as issue is actually how it is supposed to work. When we have, let say, this model:
class Foo < ActiveRecord::Base # it has name attribute
has_many :larodis
accepts_nested_attributes_for :larodi
end
class Larodi < ActiveRecord::Base # it has name attribute
belongs_to :foo
end
This definition gives me the possibility to create Foo with many Larodi's just by giving a hash of parameters. For example:
x = Foo.create(name: 'Josh', larodi_attributes: [ {name: 'Wayne'} ]
x.larodis.map(&:name) # ['Wayne']
Now comes the part where #field_for understands if we have nested attribute to work with. We check this by looking for name_attributes= method. If it is defined #fields_for generates form of the type <input ... name=object[name][INDEX][method]>... where index is just an integer.
Keep in mind that when implementing custom name_attibutes(attributes) you must check attributes type - it can be Array like the example, it can be Hash of this type:
{ 1 => { ... } , 2 => { ... } }
Just like a hash representing array, where the key is index and value is the value for this index.
The answear looks like this:
_form.html.haml
....
= crawler_form.fields_for :entry_pages do |entry_page_field|
%li
=entry_page_field.text_field :url, size: 80
...
company_crawler.rb
class CompanyCrawler < ActiveRecord::Base
....
serialize :entry_pages, Array
def entry_pages_attributes=(attributes)
self.entry_pages = attributes_collection(attributes).map do |attribute|
EntryPage.new(attribute[:url])
end
end
def entry_pages=(entry_pages)
entry_pages = entry_pages.map do |entry_page|
cast_entry_page_to_entry_page_object(entry_page)
end
write_attribute(:entry_pages, entry_pages)
end
...
private
def attributes_collection(attributes)
case attributes
when Array
attributes
when Hash
attributes.values
end
end
def cast_entry_page_to_entry_page_object(entry_page)
case entry_page
when String
EntryPage.new(entry_page)
when EntryPage
entry_page
end
end
end
For clarity I have removed entry_page_objects and use only entry_pages.

Related

How can I set "global" variables that can be accessed in controllers and models in Rails

I have a table that has set entries. I would like to access those entries as variables in both my models and controllers without querying the database every time to set those variables.
I am able to get it to work by creating duplicate "concerns" for my models and controllers. I could also set global variables in my ApplicationController. Or i could initialize them in every place that I need them. What would be the correct rails way to set and access global variables that can be accessed in both controllers and models?
class ItemType
has_many :items
end
class Item
belongs_to :item_type
belongs_to :foo
end
class Foo
has_many :items
def build_item
bar_item_type = ItemType.find_by(:name => "bar")
self.items.build(
:foo_id => self.id,
:item_type_id => bar_item_type.id
)
end
end
class ItemsController
def update
bar_item_type = ItemType.find_by(:name => "bar")
#item.update(:item_type_id => bar_item_type.id)
end
end
In the example, you can see that I am declaring the bar_item_type variable in both my Foo model and my ItemsController. I would like to DRY up my code base by being able to create and access that variable once for my rails project instead of having to make that same database call everywhere.
I would advocate against such hard-coded or DB state-dependent code. If you must do it, here's how one of the ways I know it can be done:
# models
class ItemType < ActiveRecord::Base
has_many :items
# caches the value after first call
def self.with_bar
##with_bar ||= transaction { find_or_create_by(name: "bar") }
end
def self.with_bar_id
with_bar.id
end
end
class Item < ActiveRecord::Base
belongs_to :item_type
belongs_to :foo
scope :with_bar_types, -> { where(item_type_id: ItemType.with_bar_id) }
end
class Foo < ActiveRecord::Base
has_many :items
# automatically sets the foo_id, no need to mention explicitly
# the chained with_bar_types automatically sets the item_type_id to ItemType.with_bar_id
def build_item
self.items.with_bar_types.new
end
end
# Controller
class ItemsController
def update
#item.update(item_type_id: ItemType.with_bar_id)
end
end
If you MUST use a constant, there are a few ways to do it. But you must take into account that you are instantiating an ActiveRecord model object which is dependent on data being present in the database. This is not recommend, because you now have model and controller logic relying on data being present in the database. This might be ok if you have seeded your database and that it won't change.
class ItemType
BAR_TYPE ||= where(:name => "bar").limit(1).first
has_many :items
end
Now where ever you need this object you can call it like this:
bar_item_type = ItemType::BAR_TYPE

How to verify if an embedded field changed on before_save?

I am running Ruby 2.1 and Mongoid 5.0 (no Rails).
I want to track on a before_save callback whether or not an embedded field has changed.
I can use the document.attribute_changed? or document.changed methods to check normal fields, but somehow these don't work on relations (embed_one, has_one, etc).
Is there a way of detecting these changes before saving the document?
My model is something like this
class Company
include Mongoid::Document
include Mongoid::Attributes::Dynamic
field :name, type: String
#...
embeds_one :address, class_name: 'Address', inverse_of: :address
#...
before_save :activate_flags
def activate_flags
if self.changes.include? 'address'
#self.changes never includes "address"
end
if self.address_changed?
#This throws an exception
end
end
One example of how I save my document is:
#...
company.address = AddressUtilities.parse address
company.save
#After this, the callback is triggered, but self.changes is empty...
#...
I have read the documentation and Google the hell out of it, but I can't find a solution?
I have found this gem, but it's old and doesn't work with the newer versions of Mongoid. I want to check if there is another way of doing it before considering on trying to fix/pull request the gem...
Adding these two methods to your Model and calling get_embedded_document_changes should provide you an hash with the changes to all its embedded documents:
def get_embedded_document_changes
data = {}
relations.each do |name, relation|
next unless [:embeds_one, :embeds_many].include? relation.macro.to_sym
# only if changes are present
child = send(name.to_sym)
next unless child
next if child.previous_changes.empty?
child_data = get_previous_changes_for_model(child)
data[name] = child_data
end
data
end
def get_previous_changes_for_model(model)
data = {}
model.previous_changes.each do |key, change|
data[key] = {:from => change[0], :to => change[1]}
end
data
end
[ source: https://gist.github.com/derickbailey/1049304 ]

How do you handle serialized edit fields in an Active Admin resource?

I have a model, Domain, which has a text field, names.
> rails g model Domain names:text
invoke active_record
create db/migrate/20111117233221_create_domains.rb
create app/models/domain.rb
> rake db:migrate
== CreateDomains: migrating ==================================================
-- create_table(:domains)
-> 0.0015s
== CreateDomains: migrated (0.0066s) =========================================
I set this field as serialized into an array in the model.
# app/models/domain.rb
class Domain < ActiveRecord::Base
serialize :names, Array
end
Create the ActiveAdmin resource for this model
> rails g active_admin:resource Domain
create app/admin/domains.rb
then, in the app/admin/domains.rb, I setup the various blocks to handle the serialized field as such
# app/admin/domains.rb
ActiveAdmin.register Domain do
index do
id_column
column :names do |domain|
"#{domain.names.join( ", " ) unless domain.names.nil?}"
end
default_actions
end
show do |domain|
attributes_table do
row :names do
"#{domain.names.join( ", " ) unless domain.names.nil?}"
end
end
end
form do |f|
f.inputs "Domain" do
f.input :names
end
f.buttons
end
# before we save, take the param[:domain][:name] parameter,
# split and save it to our array
before_save do |domain|
domain.names = params[:domain][:names].split(",") unless params[:domain].nil? or params[:domain][:names].nil?
end
end
Nearly everything works great -- my names are displayed as comma separated in the index and show views. When I update a record with my names field set to "a,b,c", the before_save works to turn that into an array that is then saved via the ActiveRecord serialize.
What I can not solve is how to make the edit form put in a comma-separated list into the text field. I tried using a partial and using formtastic syntax directly as well as trying to make it work via the active_admin DLS syntax. Does anyone know how to make this work?
Specifically, if I have the following array saved in my domain.names field:
# array of names saved in the domain active_record
domain.names = ["a", "b", "c"]
how to change:
form do |f|
f.inputs "Domain" do
f.input :names
end
f.buttons
end
so that when the edit form is loaded, in the text field instead of seeing abc, you see a,b,c.
Here is a summary of how I handled this situation. I added an accessor to the model which can turn the Array into a string joined by a linefeed and split it back to an Array.
# app/models/domain.rb
class Domain < ActiveRecord::Base
serialize :names, Array
attr_accessor :names_raw
def names_raw
self.names.join("\n") unless self.names.nil?
end
def names_raw=(values)
self.names = []
self.names=values.split("\n")
end
end
then, in my admin resource for domain, instead of using the :names field, I used the :names_raw field. setting this value would save the names Array with the new values.
# app/admin/domains.rb
form do |f|
f.inputs "Domain" do
f.input :names_raw, :as => :text
end
f.buttons
end
Stumbled on this question looking for something to have access to a serialized Hash's YAML. I used this solution on Rails 3.2:
def target_raw
#attributes['target'].serialized_value
end
def target_raw=(new_value)
#attributes['target'].state = :serialized
#attributes['target'].value = new_value
end

Active Record to_json\as_json on Array of Models

First off, I am not using Rails. I am using Sinatra for this project with Active Record.
I want to be able to override either to_json or as_json on my Model class and have it define some 'default' options. For example I have the following:
class Vendor < ActiveRecord::Base
def to_json(options = {})
if options.empty?
super :only => [:id, :name]
else
super options
end
end
end
where Vendor has more attributes than just id and name. In my route I have something like the following:
#vendors = Vendor.where({})
#vendors.to_json
Here #vendors is an Array vendor objects (obviously). The returned json is, however, not invoking my to_json method and is returning all of the models attributes.
I don't really have the option of modifying the route because I am actually using a modified sinatra-rest gem (http://github.com/mikeycgto/sinatra-rest).
Any ideas on how to achieve this functionality? I could do something like the following in my sinatra-rest gem but this seems silly:
#PLURAL.collect! { |obj| obj.to_json }
Try overriding serializable_hash intead:
def serializable_hash(options = nil)
{ :id => id, :name => name }
end
More information here.
If you override as_json instead of to_json, each element in the array will format with as_json before the array is converted to JSON
I'm using the following to only expose only accessible attributes:
def as_json(options = {})
options[:only] ||= self.class.accessible_attributes.to_a
super(options)
end

ActiveRecord::Base Without Table

This came up a bit ago ( rails model attributes without corresponding column in db ) but it looks like the Rails plugin mentioned is not maintained ( http://agilewebdevelopment.com/plugins/activerecord_base_without_table ). Is there no way to do this with ActiveRecord as is?
If not, is there any way to get ActiveRecord validation rules without using ActiveRecord?
ActiveRecord wants the table to exist, of course.
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>>
Validations are simply a module within ActiveRecord. Have you tried mixing them into your non-ActiveRecord model?
class MyModel
include ActiveRecord::Validations
# ...
end
I figure the more answers the better since this is one of the first results in google when searching for "rails 3.1 models without tables"
I've implements the same thing without using ActiveRecord::Base while including the ActiveRecord::Validations
The main goal was to get everything working in formtastic, and below I've included a sample payment that will not get saved anywhere but still has the ability to be validated using the validations we all know and love.
class Payment
include ActiveModel::Validations
attr_accessor :cc_number, :payment_type, :exp_mm, :exp_yy, :card_security, :first_name, :last_name, :address_1, :address_2, :city, :state, :zip_code, :home_telephone, :email, :new_record
validates_presence_of :cc_number, :payment_type, :exp_mm, :exp_yy, :card_security, :first_name, :last_name, :address_1, :address_2, :city, :state
def initialize(options = {})
if options.blank?
new_record = true
else
new_record = false
end
options.each do |key, value|
method_object = self.method((key + "=").to_sym)
method_object.call(value)
end
end
def new_record?
return new_record
end
def to_key
end
def persisted?
return false
end
end
I hope this helps someone as I've spent a few hours trying to figure this out today.
UPDATE: For Rails 3 this can be done very easy. In Rails 3+ you can use the new ActiveModel module and its submodules. This should work now:
class Tableless
include ActiveModel::Validations
attr_accessor :name
validates_presence_of :name
end
For more info, you can check out the Railscast (or read about it on AsciiCasts) on the topic, as well as this blog post by Yehuda Katz.
OLD ANSWER FOLLOWS:
You may need to add this to the solution, proposed by John Topley in the previous comment:
class Tableless
class << self
def table_name
self.name.tableize
end
end
end
class Foo < Tableless; end
Foo.table_name # will return "foos"
This provides you with a "fake" table name, if you need one. Without this method, Foo::table_name will evaluate to "tablelesses".
Just an addition to the accepted answer:
Make your subclasses inherit the parent columns with:
class FakeAR < ActiveRecord::Base
def self.inherited(subclass)
subclass.instance_variable_set("#columns", columns)
super
end
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
# Overrides save to prevent exceptions.
def save(validate = true)
validate ? valid? : true
end
end
This is a search form that presents an object called criteria that has a nested period object with beginning and end attributes.
The action in the controller is really simple yet it loads values from nested objects on the form and re-renders the same values with error messages if necessary.
Works on Rails 3.1.
The model:
class Criteria < ActiveRecord::Base
class << self
def column_defaults
{}
end
def column_names
[]
end
end # of class methods
attr_reader :period
def initialize values
values ||= {}
#period = Period.new values[:period] || {}
super values
end
def period_attributes
#period
end
def period_attributes= new_values
#period.attributes = new_values
end
end
In the controller:
def search
#criteria = Criteria.new params[:criteria]
end
In the helper:
def criteria_index_path ct, options = {}
url_for :action => :search
end
In the view:
<%= form_for #criteria do |form| %>
<%= form.fields_for :period do |prf| %>
<%= prf.text_field :beginning_as_text %>
<%= prf.text_field :end_as_text %>
<% end %>
<%= form.submit "Search" %>
<% end %>
Produces the HTML:
<form action="/admin/search" id="new_criteria" method="post">
<input id="criteria_period_attributes_beginning_as_text" name="criteria[period_attributes][beginning_as_text]" type="text">
<input id="criteria_period_attributes_end_as_text" name="criteria[period_attributes][end_as_text]" type="text">
Note: The action attribute provided by the helper and the nested attributes naming format that makes it so simple for the controller to load all the values at once
There is the activerecord-tableless gem. It's a gem to create tableless ActiveRecord models, so it has support for validations, associations, types. It supports Active Record 2.3, 3.0, 3.2
The recommended way to do it in Rails 3.x (using ActiveModel) has no support for associations nor types.

Resources