I am developing RoR application that works with legacy database and uses ActiveScaffold plugin for fancy CRUD interface.
However one of the tables of my legacy db has composite primary key. I tried using Composite Keys plugin to handle it, but it seems to have conflicts with ACtiveScaffold: I get the following error:
ActionView::TemplateError (Could not find column contact,type) on line #3 of ven
dor/plugins/active_scaffold/frontends/default/views/_form.rhtml:
1: <ol class="form" <%= 'style="display: none;"' if columns.collapsed -%>>
2: <% columns.each :for => #record do |column| -%>
3: <% if is_subsection? column -%>
4: <li class="sub-section">
5: <h5><%= column.label %> (<%= link_to_visibility_toggle(:default_visible =
> !column.collapsed) -%>)</h5>
6: <%= render :partial => 'form', :locals => { :columns => column } %>
vendor/plugins/active_scaffold/lib/data_structures/sorting.rb:16:in `add'
while having in model code smth like:
set_primary_keys :contact, :type
I highly appreciate any idea how I can get composite keys capability with ActiveScaffold.
I think your best bet may be checking the ActiveScaffold Google Group as it's monitored by core developers of ActiveScaffold and they would ultimately be able to solve your problem and explain why composite keys with the plugin won't work with ActiveScaffold.
Good luck and be sure to post a follow-up if you do get results from the Google Group (which I have posted on before and received feedback very quickly).
One quick result I did find was this.
What I did was to create a facade class that does not inherit from
ActiveRecord then make the "id" show the primary key. In my case the
primary key was computed from other data and could change as a result
of an edit, so I had to override ActiveScaffold in a few places to
allow for the primary key changing after an update. But, all in all
it works and is fairly straightforward. Start with an empty class
and just resolve messages that are not understood. In your case you
could even just redirect all messages to a wrapped ActiveRecord while
replacing the id and id= methods, and filtering the [] and []= methods.
That may do the trick for you.
No, I have not received any reply from the group and I am not sure if ActiveScaffold is actively maintained yet.
After some time playing with ActiveScaffold, I ended up implementing my own CRUD interface from the scratch.
I have this working, with read-only models, using ActiveScaffold on a legacy DB.
The trick was to override the default 'id' field in the model and return a concatenated PK string.
If that's good enough, then here you go:
class CPKReadonlyModel < ActiveRecord::Base
set_primary_key :id_one # only half of it, but id overridden below...
def id
self.id_one.to_s + ',' + self.id_two.to_s
end
def readonly?
true
end
def before_destroy
raise ActiveRecord::ReadOnlyRecord
end
def delete
raise ActiveRecord::ReadOnlyRecord
end
def self.delete_all
raise ActiveRecord::ReadOnlyRecord
end
end
The controller has the following in the active_scaffold config block:
config.actions.exclude :create, :update, :delete
Related
Experienced Java developer, new to Rails - wondering about belongs_to relationship in scaffolding.
Saw another answer like this
Does rails scaffold command support generate belongs_to or many to many model middle table migration info?
and followed the rails generate scaffold_controller obj:references pattern.
The index/show page is showing #<MyClass:xxxx> instead of the string I want - is there a method in the target class (parent side of the belongs_to) I need to override to specify the identifier?
Also in the edit view, it looks like it's trying to modify the reference as a string rather than as drop-down - is there something I need to specify to make that happen?
Thanks!
BTW - I was able to get similar scaffolding to work in Django and Grails, where the foreign key turned into a drop-down; I'm hoping Rails is equally easy and I'm just missing it.
You can override the #to_s method on the instances, as it is the one being called.
class FooDoodle < ActiveRecord::Base
def to_s
name
end
end
That's when showing a record.
However, when you're actually using the form to set the associations, scaffold will only generate an input field in the view so you can enter the id. You could have a dropdown menu for example, but the options for that dropdown would somehow have to be selected in a manner.
For example, if there are 2000 possible associated records, which ones do you show? Do you show the 2000? Only the first 10? That logic would go into your controller.
So, for example:
class FooDoodlesController < ApplicationController
def edit
#foodoodle = FooDoodle.find(params[:id])
#friends = #foodoodle.possible_friends # or else
end
end
and using select and options_for_select as choices
# _form.html.erb
<%= form_for #foodoodle do |f| %>
<%= f.label :friend %>
<%= f.select :friend, #friends.map{ |p| [p.to_s, p.id] } %>
With the following Store and Service models, managed with MongoMapper:
class Store
include MongoMapper::Document
key :service_ids, Array, :typecast => 'ObjectId'
many :services, :in => :service_ids
end
class Service
include MongoMapper::Document
key :name, String
many :stores, :foreign_key => :service_ids
end
I have this form, done with Formtastic:
<%= semantic_form_for #store, :url => admin_store_path(#store), :method => :put do |form| %>
<%= form.input :service_ids, :label => "Select Store Services",
:as => :check_boxes,
:collection => Service.all %>
<% end -%>
The controller uses Inherited Resources, and the edit action is implicit.
When editing a #store with services already associated with it, the checkboxes for the latter don't show as checked.
Formtastic's README warns it doesn't support MongoMapper officially, but it also says people have been using both together successfully, and I've seen some examples of this online.
I suspect Inherited Resources also doesn't support it, from what I've seen from Devise + Simple Form, both from the same authors and which don't support MM. They're working towards using an ORM adapter in their gems, but it isn't ready yet AFAIK.
And I've had problems with it already, I'm overriding the update action to get it to work:
def update
store = Store.find(params[:id])
if store.update_attributes!(params[:store])
flash[:notice] = 'Store was successfully updated.'
redirect_to admin_store_path(store)
else
redirect_to new_store_path
end
end
Does anybody know where the conflict with MM is, either in Formtastic or IR, and a hack just to get these checkboxes checking?
Most likely a Formtastic issue. It looks like the problem is here: https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/inputs/check_boxes_input.rb#L122
Formtastic calls #store.service_ids to find the selected boxes. Service_ids returns an array of ObjectId's, but Formtastic was expecting an array of Store objects. If we follow Formtastic's code we'll see it tries a couple methods to find out how to get the "value" out of those ObjectId's and will eventually settle on "to_s" (see https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/form_builder.rb#L20). Unfortunately, the to_s of an ObjectId is not the same as the id of your Store objects.
A hack that might make it work is to add an "id" method to ObjectId that returns self (Formtastic looks for id before it looks for to_s). A more appropriate patch would be to override this method https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/inputs/base.rb#L104 to properly introspect MongoMapper associations, so that you could write form.input :services and it would turn that into an input with name of "service_ids" while still using the services method of your object. With that change it would still properly call #store.services and find the same kind of objects as Store.all and just work.
If you want to go that route, Store.associations[:services] should get you MongoMapper's definition of the association which you can introspect (see https://github.com/jnunemaker/mongomapper/blob/master/lib/mongo_mapper/plugins/associations/base.rb) but note that associations have been refactored a bit since the 0.8.6 gem, they're now in the separate classes BelongsToAssociation, OneAssociation, and ManyAssociation that each inherit from Associations::Base.
So, it doesn't seem like there's a simple fix. The other option is to generate your checkboxes by hand.
(Aside: I'm a little confused by your update method because I'd expect IR to do exactly what you've written internally, but if you had to write it that way to get it to work, so it is...)
On a Content model have an attribute named slug. When creating a new record, I want to use a helper to populate this field, but on an existing record I want to use the value from the database.
Currently I have:
<% if #content.new_record? %>
<%= f.text_field :slug, :value => "#{generate_slug(6)}" %>
<% else %>
<%= f.text_field :slug %>
<% end %>
But that seems a bit verbose. Is this the best way, or is there no other way? (Rails newb just trying to find the "Rails way" on issues I'm unsure of)
Edit
I should note that the helper is currently in /app/helpers/application_helper.rb Moved to be a private action in the Contents controller. David's answer worked great.
In your controller
#content.slug ||= generate_slug(6)
This will assign a value to the slug attribute if none is present
Then, in your view you can simply use
<%= f.text_field :slug %>
Options
Try after_initialize callback in your model.
Try creating a method in your model where you set defaults and call it in your new action in the controller. Also call this method if your create fails and you render new. Remember to set default only when no value exists by using the ||= operator.
Example to follow. I'm typing on phone!
I happen to use jQuery in my projects, so when I want some functionality like this, I usually use something like labelify. Then, I'd use something like <%= f.text_field :slug, :title => generate_slug(6) %>. (Hot tip, you don't need to put the #generate_slug call inside of a string if it returns something that will resolve to a string by itself, in fact it's more performant if you don't.)
If you don't want to go with jQuery approach, you might want to wrap this piece of logic in your model.
def Content < ActiveRecord::Base
def slug
self.new_record? ? self.slug_for_new_record : attributes[:slug]
end
private
def slug_for_new_record
# I don't know what you're doing in generate_slug, but it sounds model-
# related, so if so, put it here and not in a helper
end
end
If it really belongs in the view, still another option is to just make your Ruby a little bit more concise (you'll have to judge if this is more readable):
<%= f.text_field :slug, :value => (generate_slug(6) if #content.new_record?) %>
Don't forget the parens surrounding (generate_slug(6) if #content.new_record?). If you do, the if will be applied to the text_field, which is not what you want.
But there are still more ways to do it. The above line of code isn't great if your logic might change and you're pasting this code all over your rails project. When I wanted to add a 'required' class to my text fields but only if they were a new record (we had some legacy data that we didn't want to make people clean up), I created my own form builder with a required_field method that just called text_field and added a 'required' class if the item was a new record. This might seem like a work, but we have around 20 different forms, each with potentially multiple required fields, and it's a lot easier to change the business logic in one place. So if you really think this logic belongs in the view but you've got a ton of these lines of code and you don't want to have to change it in a million places, then FormBuilder is the way to go. I think this is in most cases prettier and more appropriate than a helper, but again, beauty is in the eye of the beholder. Here's my code somewhat adapted for your case:
# config/environment.rb
ActionView::Base.default_form_builder = NamespacesAreFun::FormBuilder
# lib/namespaces_are_fun/form_builder.rb
module NamespacesAreFun
class FormBuilder < ActionView::Helpers::FormBuilder
def slug_field(method, options = {})
opts = options.to_options
opts.merge!(:value => generate_slug) if self.object.new_record?
text_field(method, opts)
end
end
end
# views/.../your_view.html.erb
<%= f.slug_field :slug %>
Hopefully in all of these different approaches is one that fits your project.
I am really really stuck and annoyed with this right now.
I am running Rails 2.3.5
My View/pages/customers.html.erb simply has:
<% form_tag do %>
First Name
<%= text_field_tag :firstName, params[:firstName] %>
Last Name
<%= text_field_tag :lastName, params[:lastName] %>
<%= submit_tag "Enter" %>
<%end%>
My Models/customer.rb simply has:
class Customer < ActiveRecord::Base
attr_accessible :firstName, :lastName
end
My Controller/pages_controller has
class PagesController < ApplicationController
def custs
#cust = Customer.new(params[:firstName], params[:lastName])
#cust.save
end
end
so as you see I am just trying to enter two fields from front end and then save them to the DB. However, whenever i load my page it give me error:
wrong number of arguments (2 for 1)
pages_controller.rb:3:in new'
pages_controller.rb:3:incusts'
weird thing is that when I use the sandbox script/console I am able to insert data fine.
What is going on here? please someone explain!
http://apidock.com/rails/ActiveRecord/Base/new/class here is a little explanation of the new function. The crucial part - "pass a hash with key names matching the associated table column name". Instead of #cust = Customer.new(params[:firstName], params[:lastName]) you should have #cust = Customer.new(:firstName => params[:firstName], :lastName => params[:lastName]). This should do the trick.
The quick fix is to change line 3 of pages_controller to this:
#cust = Customer.new({:firstName => params[:firstName], :lastName => params[:lastName]})
Without proper keys Rails has no idea what values you are passing and in what order.
The bigger problem seems to be that your form is not setup properly. You might have a great reason for it, but if not, I would recommend creating a blank Rails project, and using generate scaffold to see how a normal Rails form/controller is setup.
Since new takes a hash, from which attributes will be set where the hash has the corresponding keys, Customer.new(params) should be sufficient, shouldn't it? Unless params also has keys for attributes that you don't want to be set in this case, I suppose.
Obviously your sample code may have been edited-down to better present the problem, but as shown, the #new/#save pair can usually be condensed down to Customer#create(params)
I want to do store the parameters from a form in a model. Since I don't want the model to use any database, it does not inherit from ActiveRecord::Base. I'm thinking it should look something like this:
# in view:
<% form_for :question, :url => {:action => "ask"} do |f| %>
<%= f.text_field(:q) %>
<%= submit_tag %>
<% end %>
# in controller:
def ask
# I want this to magically set all variables in #question using
# values from params.
#question = params[:question]
end
# in model:
class Question
attr_accessor :q
def initialize
#q = ""
end
end
But after spending 1½ days on it, it doesn't seem to be the right way to do it. Any suggestions would be much appreciated.
Take a look at this article:
http://pullmonkey.com/2008/1/6/convert-a-ruby-hash-into-a-class-object
It shows how to create a class that will dynamically create a class from the passed in Hash.
Even if you set your Question properly, how do you plan to persist this? A file?
I think it is a much better approach to get a deep understanding of ActiveRecord before going for fancy models that have custom persistence
You might want to check out Ryan Bates' Railscast on creating a non ActiveRecord model
http://railscasts.com/episodes/121-non-active-record-model
... however I'd suggest that if you're thinking RESTfully about this, it sounds from your comment to Sam's answer like you may have another RESTful resource at work - i.e. you don't actually want to use a QuestionsController... but instead something to do with what you're actually creating (the method call you mention). You can still initialize your Question object as part of that process.